2f7ee57d472bb087c1f2a9a0bde0a8f618b9f720
[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; // [HGM] should we set this to 0, and not print it in advance?
2165         next_out = 0;
2166         leftover_start = 0;
2167         
2168         i = 0;
2169         while (i < buf_len) {
2170             /* Deal with part of the TELNET option negotiation
2171                protocol.  We refuse to do anything beyond the
2172                defaults, except that we allow the WILL ECHO option,
2173                which ICS uses to turn off password echoing when we are
2174                directly connected to it.  We reject this option
2175                if localLineEditing mode is on (always on in xboard)
2176                and we are talking to port 23, which might be a real
2177                telnet server that will try to keep WILL ECHO on permanently.
2178              */
2179             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2180                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2181                 unsigned char option;
2182                 oldi = i;
2183                 switch ((unsigned char) buf[++i]) {
2184                   case TN_WILL:
2185                     if (appData.debugMode)
2186                       fprintf(debugFP, "\n<WILL ");
2187                     switch (option = (unsigned char) buf[++i]) {
2188                       case TN_ECHO:
2189                         if (appData.debugMode)
2190                           fprintf(debugFP, "ECHO ");
2191                         /* Reply only if this is a change, according
2192                            to the protocol rules. */
2193                         if (remoteEchoOption) break;
2194                         if (appData.localLineEditing &&
2195                             atoi(appData.icsPort) == TN_PORT) {
2196                             TelnetRequest(TN_DONT, TN_ECHO);
2197                         } else {
2198                             EchoOff();
2199                             TelnetRequest(TN_DO, TN_ECHO);
2200                             remoteEchoOption = TRUE;
2201                         }
2202                         break;
2203                       default:
2204                         if (appData.debugMode)
2205                           fprintf(debugFP, "%d ", option);
2206                         /* Whatever this is, we don't want it. */
2207                         TelnetRequest(TN_DONT, option);
2208                         break;
2209                     }
2210                     break;
2211                   case TN_WONT:
2212                     if (appData.debugMode)
2213                       fprintf(debugFP, "\n<WONT ");
2214                     switch (option = (unsigned char) buf[++i]) {
2215                       case TN_ECHO:
2216                         if (appData.debugMode)
2217                           fprintf(debugFP, "ECHO ");
2218                         /* Reply only if this is a change, according
2219                            to the protocol rules. */
2220                         if (!remoteEchoOption) break;
2221                         EchoOn();
2222                         TelnetRequest(TN_DONT, TN_ECHO);
2223                         remoteEchoOption = FALSE;
2224                         break;
2225                       default:
2226                         if (appData.debugMode)
2227                           fprintf(debugFP, "%d ", (unsigned char) option);
2228                         /* Whatever this is, it must already be turned
2229                            off, because we never agree to turn on
2230                            anything non-default, so according to the
2231                            protocol rules, we don't reply. */
2232                         break;
2233                     }
2234                     break;
2235                   case TN_DO:
2236                     if (appData.debugMode)
2237                       fprintf(debugFP, "\n<DO ");
2238                     switch (option = (unsigned char) buf[++i]) {
2239                       default:
2240                         /* Whatever this is, we refuse to do it. */
2241                         if (appData.debugMode)
2242                           fprintf(debugFP, "%d ", option);
2243                         TelnetRequest(TN_WONT, option);
2244                         break;
2245                     }
2246                     break;
2247                   case TN_DONT:
2248                     if (appData.debugMode)
2249                       fprintf(debugFP, "\n<DONT ");
2250                     switch (option = (unsigned char) buf[++i]) {
2251                       default:
2252                         if (appData.debugMode)
2253                           fprintf(debugFP, "%d ", option);
2254                         /* Whatever this is, we are already not doing
2255                            it, because we never agree to do anything
2256                            non-default, so according to the protocol
2257                            rules, we don't reply. */
2258                         break;
2259                     }
2260                     break;
2261                   case TN_IAC:
2262                     if (appData.debugMode)
2263                       fprintf(debugFP, "\n<IAC ");
2264                     /* Doubled IAC; pass it through */
2265                     i--;
2266                     break;
2267                   default:
2268                     if (appData.debugMode)
2269                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2270                     /* Drop all other telnet commands on the floor */
2271                     break;
2272                 }
2273                 if (oldi > next_out)
2274                   SendToPlayer(&buf[next_out], oldi - next_out);
2275                 if (++i > next_out)
2276                   next_out = i;
2277                 continue;
2278             }
2279                 
2280             /* OK, this at least will *usually* work */
2281             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2282                 loggedOn = TRUE;
2283             }
2284             
2285             if (loggedOn && !intfSet) {
2286                 if (ics_type == ICS_ICC) {
2287                   sprintf(str,
2288                           "/set-quietly interface %s\n/set-quietly style 12\n",
2289                           programVersion);
2290                 } else if (ics_type == ICS_CHESSNET) {
2291                   sprintf(str, "/style 12\n");
2292                 } else {
2293                   strcpy(str, "alias $ @\n$set interface ");
2294                   strcat(str, programVersion);
2295                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2296 #ifdef WIN32
2297                   strcat(str, "$iset nohighlight 1\n");
2298 #endif
2299                   strcat(str, "$iset lock 1\n$style 12\n");
2300                 }
2301                 SendToICS(str);
2302                 NotifyFrontendLogin();
2303                 intfSet = TRUE;
2304             }
2305
2306             if (started == STARTED_COMMENT) {
2307                 /* Accumulate characters in comment */
2308                 parse[parse_pos++] = buf[i];
2309                 if (buf[i] == '\n') {
2310                     parse[parse_pos] = NULLCHAR;
2311                     if(chattingPartner>=0) {
2312                         char mess[MSG_SIZ];
2313                         sprintf(mess, "%s%s", talker, parse);
2314                         OutputChatMessage(chattingPartner, mess);
2315                         chattingPartner = -1;
2316                     } else
2317                     if(!suppressKibitz) // [HGM] kibitz
2318                         AppendComment(forwardMostMove, StripHighlight(parse));
2319                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2320                         int nrDigit = 0, nrAlph = 0, j;
2321                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2322                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2323                         parse[parse_pos] = NULLCHAR;
2324                         // try to be smart: if it does not look like search info, it should go to
2325                         // ICS interaction window after all, not to engine-output window.
2326                         for(j=0; j<parse_pos; j++) { // count letters and digits
2327                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2328                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2329                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2330                         }
2331                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2332                             int depth=0; float score;
2333                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2334                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2335                                 pvInfoList[forwardMostMove-1].depth = depth;
2336                                 pvInfoList[forwardMostMove-1].score = 100*score;
2337                             }
2338                             OutputKibitz(suppressKibitz, parse);
2339                             next_out = i+1; // [HGM] suppress printing in ICS window
2340                         } else {
2341                             char tmp[MSG_SIZ];
2342                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2343                             SendToPlayer(tmp, strlen(tmp));
2344                         }
2345                     }
2346                     started = STARTED_NONE;
2347                 } else {
2348                     /* Don't match patterns against characters in comment */
2349                     i++;
2350                     continue;
2351                 }
2352             }
2353             if (started == STARTED_CHATTER) {
2354                 if (buf[i] != '\n') {
2355                     /* Don't match patterns against characters in chatter */
2356                     i++;
2357                     continue;
2358                 }
2359                 started = STARTED_NONE;
2360             }
2361
2362             /* Kludge to deal with rcmd protocol */
2363             if (firstTime && looking_at(buf, &i, "\001*")) {
2364                 DisplayFatalError(&buf[1], 0, 1);
2365                 continue;
2366             } else {
2367                 firstTime = FALSE;
2368             }
2369
2370             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2371                 ics_type = ICS_ICC;
2372                 ics_prefix = "/";
2373                 if (appData.debugMode)
2374                   fprintf(debugFP, "ics_type %d\n", ics_type);
2375                 continue;
2376             }
2377             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2378                 ics_type = ICS_FICS;
2379                 ics_prefix = "$";
2380                 if (appData.debugMode)
2381                   fprintf(debugFP, "ics_type %d\n", ics_type);
2382                 continue;
2383             }
2384             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2385                 ics_type = ICS_CHESSNET;
2386                 ics_prefix = "/";
2387                 if (appData.debugMode)
2388                   fprintf(debugFP, "ics_type %d\n", ics_type);
2389                 continue;
2390             }
2391
2392             if (!loggedOn &&
2393                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2394                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2395                  looking_at(buf, &i, "will be \"*\""))) {
2396               strcpy(ics_handle, star_match[0]);
2397               continue;
2398             }
2399
2400             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2401               char buf[MSG_SIZ];
2402               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2403               DisplayIcsInteractionTitle(buf);
2404               have_set_title = TRUE;
2405             }
2406
2407             /* skip finger notes */
2408             if (started == STARTED_NONE &&
2409                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2410                  (buf[i] == '1' && buf[i+1] == '0')) &&
2411                 buf[i+2] == ':' && buf[i+3] == ' ') {
2412               started = STARTED_CHATTER;
2413               i += 3;
2414               continue;
2415             }
2416
2417             /* skip formula vars */
2418             if (started == STARTED_NONE &&
2419                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2420               started = STARTED_CHATTER;
2421               i += 3;
2422               continue;
2423             }
2424
2425             oldi = i;
2426             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2427             if (appData.autoKibitz && started == STARTED_NONE && 
2428                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2429                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2430                 if(looking_at(buf, &i, "* kibitzes: ") &&
2431                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2432                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2433                         suppressKibitz = TRUE;
2434                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2435                                 && (gameMode == IcsPlayingWhite)) ||
2436                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2437                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2438                             started = STARTED_CHATTER; // own kibitz we simply discard
2439                         else {
2440                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2441                             parse_pos = 0; parse[0] = NULLCHAR;
2442                             savingComment = TRUE;
2443                             suppressKibitz = gameMode != IcsObserving ? 2 :
2444                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2445                         } 
2446                         continue;
2447                 } else
2448                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2449                     // suppress the acknowledgements of our own autoKibitz
2450                     char *p;
2451                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2452                     SendToPlayer(star_match[0], strlen(star_match[0]));
2453                     looking_at(buf, &i, "*% "); // eat prompt
2454                     next_out = i;
2455                 }
2456             } // [HGM] kibitz: end of patch
2457
2458 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2459
2460             // [HGM] chat: intercept tells by users for which we have an open chat window
2461             channel = -1;
2462             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2463                                            looking_at(buf, &i, "* whispers:") ||
2464                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2465                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2466                 int p;
2467                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2468                 chattingPartner = -1;
2469
2470                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2471                 for(p=0; p<MAX_CHAT; p++) {
2472                     if(channel == atoi(chatPartner[p])) {
2473                     talker[0] = '['; strcat(talker, "] ");
2474                     chattingPartner = p; break;
2475                     }
2476                 } else
2477                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2478                 for(p=0; p<MAX_CHAT; p++) {
2479                     if(!strcmp("WHISPER", chatPartner[p])) {
2480                         talker[0] = '['; strcat(talker, "] ");
2481                         chattingPartner = p; break;
2482                     }
2483                 }
2484                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2485                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2486                     talker[0] = 0;
2487                     chattingPartner = p; break;
2488                 }
2489                 if(chattingPartner<0) i = oldi; else {
2490                     started = STARTED_COMMENT;
2491                     parse_pos = 0; parse[0] = NULLCHAR;
2492                     savingComment = 3 + chattingPartner; // counts as TRUE
2493                     suppressKibitz = TRUE;
2494                 }
2495             } // [HGM] chat: end of patch
2496
2497             if (appData.zippyTalk || appData.zippyPlay) {
2498                 /* [DM] Backup address for color zippy lines */
2499                 backup = i;
2500 #if ZIPPY
2501        #ifdef WIN32
2502                if (loggedOn == TRUE)
2503                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2504                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2505        #else
2506                 if (ZippyControl(buf, &i) ||
2507                     ZippyConverse(buf, &i) ||
2508                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2509                       loggedOn = TRUE;
2510                       if (!appData.colorize) continue;
2511                 }
2512        #endif
2513 #endif
2514             } // [DM] 'else { ' deleted
2515                 if (
2516                     /* Regular tells and says */
2517                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2518                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2519                     looking_at(buf, &i, "* says: ") ||
2520                     /* Don't color "message" or "messages" output */
2521                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2522                     looking_at(buf, &i, "*. * at *:*: ") ||
2523                     looking_at(buf, &i, "--* (*:*): ") ||
2524                     /* Message notifications (same color as tells) */
2525                     looking_at(buf, &i, "* has left a message ") ||
2526                     looking_at(buf, &i, "* just sent you a message:\n") ||
2527                     /* Whispers and kibitzes */
2528                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2529                     looking_at(buf, &i, "* kibitzes: ") ||
2530                     /* Channel tells */
2531                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2532
2533                   if (tkind == 1 && strchr(star_match[0], ':')) {
2534                       /* Avoid "tells you:" spoofs in channels */
2535                      tkind = 3;
2536                   }
2537                   if (star_match[0][0] == NULLCHAR ||
2538                       strchr(star_match[0], ' ') ||
2539                       (tkind == 3 && strchr(star_match[1], ' '))) {
2540                     /* Reject bogus matches */
2541                     i = oldi;
2542                   } else {
2543                     if (appData.colorize) {
2544                       if (oldi > next_out) {
2545                         SendToPlayer(&buf[next_out], oldi - next_out);
2546                         next_out = oldi;
2547                       }
2548                       switch (tkind) {
2549                       case 1:
2550                         Colorize(ColorTell, FALSE);
2551                         curColor = ColorTell;
2552                         break;
2553                       case 2:
2554                         Colorize(ColorKibitz, FALSE);
2555                         curColor = ColorKibitz;
2556                         break;
2557                       case 3:
2558                         p = strrchr(star_match[1], '(');
2559                         if (p == NULL) {
2560                           p = star_match[1];
2561                         } else {
2562                           p++;
2563                         }
2564                         if (atoi(p) == 1) {
2565                           Colorize(ColorChannel1, FALSE);
2566                           curColor = ColorChannel1;
2567                         } else {
2568                           Colorize(ColorChannel, FALSE);
2569                           curColor = ColorChannel;
2570                         }
2571                         break;
2572                       case 5:
2573                         curColor = ColorNormal;
2574                         break;
2575                       }
2576                     }
2577                     if (started == STARTED_NONE && appData.autoComment &&
2578                         (gameMode == IcsObserving ||
2579                          gameMode == IcsPlayingWhite ||
2580                          gameMode == IcsPlayingBlack)) {
2581                       parse_pos = i - oldi;
2582                       memcpy(parse, &buf[oldi], parse_pos);
2583                       parse[parse_pos] = NULLCHAR;
2584                       started = STARTED_COMMENT;
2585                       savingComment = TRUE;
2586                     } else {
2587                       started = STARTED_CHATTER;
2588                       savingComment = FALSE;
2589                     }
2590                     loggedOn = TRUE;
2591                     continue;
2592                   }
2593                 }
2594
2595                 if (looking_at(buf, &i, "* s-shouts: ") ||
2596                     looking_at(buf, &i, "* c-shouts: ")) {
2597                     if (appData.colorize) {
2598                         if (oldi > next_out) {
2599                             SendToPlayer(&buf[next_out], oldi - next_out);
2600                             next_out = oldi;
2601                         }
2602                         Colorize(ColorSShout, FALSE);
2603                         curColor = ColorSShout;
2604                     }
2605                     loggedOn = TRUE;
2606                     started = STARTED_CHATTER;
2607                     continue;
2608                 }
2609
2610                 if (looking_at(buf, &i, "--->")) {
2611                     loggedOn = TRUE;
2612                     continue;
2613                 }
2614
2615                 if (looking_at(buf, &i, "* shouts: ") ||
2616                     looking_at(buf, &i, "--> ")) {
2617                     if (appData.colorize) {
2618                         if (oldi > next_out) {
2619                             SendToPlayer(&buf[next_out], oldi - next_out);
2620                             next_out = oldi;
2621                         }
2622                         Colorize(ColorShout, FALSE);
2623                         curColor = ColorShout;
2624                     }
2625                     loggedOn = TRUE;
2626                     started = STARTED_CHATTER;
2627                     continue;
2628                 }
2629
2630                 if (looking_at( buf, &i, "Challenge:")) {
2631                     if (appData.colorize) {
2632                         if (oldi > next_out) {
2633                             SendToPlayer(&buf[next_out], oldi - next_out);
2634                             next_out = oldi;
2635                         }
2636                         Colorize(ColorChallenge, FALSE);
2637                         curColor = ColorChallenge;
2638                     }
2639                     loggedOn = TRUE;
2640                     continue;
2641                 }
2642
2643                 if (looking_at(buf, &i, "* offers you") ||
2644                     looking_at(buf, &i, "* offers to be") ||
2645                     looking_at(buf, &i, "* would like to") ||
2646                     looking_at(buf, &i, "* requests to") ||
2647                     looking_at(buf, &i, "Your opponent offers") ||
2648                     looking_at(buf, &i, "Your opponent requests")) {
2649
2650                     if (appData.colorize) {
2651                         if (oldi > next_out) {
2652                             SendToPlayer(&buf[next_out], oldi - next_out);
2653                             next_out = oldi;
2654                         }
2655                         Colorize(ColorRequest, FALSE);
2656                         curColor = ColorRequest;
2657                     }
2658                     continue;
2659                 }
2660
2661                 if (looking_at(buf, &i, "* (*) seeking")) {
2662                     if (appData.colorize) {
2663                         if (oldi > next_out) {
2664                             SendToPlayer(&buf[next_out], oldi - next_out);
2665                             next_out = oldi;
2666                         }
2667                         Colorize(ColorSeek, FALSE);
2668                         curColor = ColorSeek;
2669                     }
2670                     continue;
2671             }
2672
2673             if (looking_at(buf, &i, "\\   ")) {
2674                 if (prevColor != ColorNormal) {
2675                     if (oldi > next_out) {
2676                         SendToPlayer(&buf[next_out], oldi - next_out);
2677                         next_out = oldi;
2678                     }
2679                     Colorize(prevColor, TRUE);
2680                     curColor = prevColor;
2681                 }
2682                 if (savingComment) {
2683                     parse_pos = i - oldi;
2684                     memcpy(parse, &buf[oldi], parse_pos);
2685                     parse[parse_pos] = NULLCHAR;
2686                     started = STARTED_COMMENT;
2687                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2688                         chattingPartner = savingComment - 3; // kludge to remember the box
2689                 } else {
2690                     started = STARTED_CHATTER;
2691                 }
2692                 continue;
2693             }
2694
2695             if (looking_at(buf, &i, "Black Strength :") ||
2696                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2697                 looking_at(buf, &i, "<10>") ||
2698                 looking_at(buf, &i, "#@#")) {
2699                 /* Wrong board style */
2700                 loggedOn = TRUE;
2701                 SendToICS(ics_prefix);
2702                 SendToICS("set style 12\n");
2703                 SendToICS(ics_prefix);
2704                 SendToICS("refresh\n");
2705                 continue;
2706             }
2707             
2708             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2709                 ICSInitScript();
2710                 have_sent_ICS_logon = 1;
2711                 continue;
2712             }
2713               
2714             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2715                 (looking_at(buf, &i, "\n<12> ") ||
2716                  looking_at(buf, &i, "<12> "))) {
2717                 loggedOn = TRUE;
2718                 if (oldi > next_out) {
2719                     SendToPlayer(&buf[next_out], oldi - next_out);
2720                 }
2721                 next_out = i;
2722                 started = STARTED_BOARD;
2723                 parse_pos = 0;
2724                 continue;
2725             }
2726
2727             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2728                 looking_at(buf, &i, "<b1> ")) {
2729                 if (oldi > next_out) {
2730                     SendToPlayer(&buf[next_out], oldi - next_out);
2731                 }
2732                 next_out = i;
2733                 started = STARTED_HOLDINGS;
2734                 parse_pos = 0;
2735                 continue;
2736             }
2737
2738             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2739                 loggedOn = TRUE;
2740                 /* Header for a move list -- first line */
2741
2742                 switch (ics_getting_history) {
2743                   case H_FALSE:
2744                     switch (gameMode) {
2745                       case IcsIdle:
2746                       case BeginningOfGame:
2747                         /* User typed "moves" or "oldmoves" while we
2748                            were idle.  Pretend we asked for these
2749                            moves and soak them up so user can step
2750                            through them and/or save them.
2751                            */
2752                         Reset(FALSE, TRUE);
2753                         gameMode = IcsObserving;
2754                         ModeHighlight();
2755                         ics_gamenum = -1;
2756                         ics_getting_history = H_GOT_UNREQ_HEADER;
2757                         break;
2758                       case EditGame: /*?*/
2759                       case EditPosition: /*?*/
2760                         /* Should above feature work in these modes too? */
2761                         /* For now it doesn't */
2762                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2763                         break;
2764                       default:
2765                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2766                         break;
2767                     }
2768                     break;
2769                   case H_REQUESTED:
2770                     /* Is this the right one? */
2771                     if (gameInfo.white && gameInfo.black &&
2772                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2773                         strcmp(gameInfo.black, star_match[2]) == 0) {
2774                         /* All is well */
2775                         ics_getting_history = H_GOT_REQ_HEADER;
2776                     }
2777                     break;
2778                   case H_GOT_REQ_HEADER:
2779                   case H_GOT_UNREQ_HEADER:
2780                   case H_GOT_UNWANTED_HEADER:
2781                   case H_GETTING_MOVES:
2782                     /* Should not happen */
2783                     DisplayError(_("Error gathering move list: two headers"), 0);
2784                     ics_getting_history = H_FALSE;
2785                     break;
2786                 }
2787
2788                 /* Save player ratings into gameInfo if needed */
2789                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2790                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2791                     (gameInfo.whiteRating == -1 ||
2792                      gameInfo.blackRating == -1)) {
2793
2794                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2795                     gameInfo.blackRating = string_to_rating(star_match[3]);
2796                     if (appData.debugMode)
2797                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2798                               gameInfo.whiteRating, gameInfo.blackRating);
2799                 }
2800                 continue;
2801             }
2802
2803             if (looking_at(buf, &i,
2804               "* * match, initial time: * minute*, increment: * second")) {
2805                 /* Header for a move list -- second line */
2806                 /* Initial board will follow if this is a wild game */
2807                 if (gameInfo.event != NULL) free(gameInfo.event);
2808                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2809                 gameInfo.event = StrSave(str);
2810                 /* [HGM] we switched variant. Translate boards if needed. */
2811                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2812                 continue;
2813             }
2814
2815             if (looking_at(buf, &i, "Move  ")) {
2816                 /* Beginning of a move list */
2817                 switch (ics_getting_history) {
2818                   case H_FALSE:
2819                     /* Normally should not happen */
2820                     /* Maybe user hit reset while we were parsing */
2821                     break;
2822                   case H_REQUESTED:
2823                     /* Happens if we are ignoring a move list that is not
2824                      * the one we just requested.  Common if the user
2825                      * tries to observe two games without turning off
2826                      * getMoveList */
2827                     break;
2828                   case H_GETTING_MOVES:
2829                     /* Should not happen */
2830                     DisplayError(_("Error gathering move list: nested"), 0);
2831                     ics_getting_history = H_FALSE;
2832                     break;
2833                   case H_GOT_REQ_HEADER:
2834                     ics_getting_history = H_GETTING_MOVES;
2835                     started = STARTED_MOVES;
2836                     parse_pos = 0;
2837                     if (oldi > next_out) {
2838                         SendToPlayer(&buf[next_out], oldi - next_out);
2839                     }
2840                     break;
2841                   case H_GOT_UNREQ_HEADER:
2842                     ics_getting_history = H_GETTING_MOVES;
2843                     started = STARTED_MOVES_NOHIDE;
2844                     parse_pos = 0;
2845                     break;
2846                   case H_GOT_UNWANTED_HEADER:
2847                     ics_getting_history = H_FALSE;
2848                     break;
2849                 }
2850                 continue;
2851             }                           
2852             
2853             if (looking_at(buf, &i, "% ") ||
2854                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2855                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2856                 if(suppressKibitz) next_out = i;
2857                 savingComment = FALSE;
2858                 suppressKibitz = 0;
2859                 switch (started) {
2860                   case STARTED_MOVES:
2861                   case STARTED_MOVES_NOHIDE:
2862                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2863                     parse[parse_pos + i - oldi] = NULLCHAR;
2864                     ParseGameHistory(parse);
2865 #if ZIPPY
2866                     if (appData.zippyPlay && first.initDone) {
2867                         FeedMovesToProgram(&first, forwardMostMove);
2868                         if (gameMode == IcsPlayingWhite) {
2869                             if (WhiteOnMove(forwardMostMove)) {
2870                                 if (first.sendTime) {
2871                                   if (first.useColors) {
2872                                     SendToProgram("black\n", &first); 
2873                                   }
2874                                   SendTimeRemaining(&first, TRUE);
2875                                 }
2876                                 if (first.useColors) {
2877                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2878                                 }
2879                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2880                                 first.maybeThinking = TRUE;
2881                             } else {
2882                                 if (first.usePlayother) {
2883                                   if (first.sendTime) {
2884                                     SendTimeRemaining(&first, TRUE);
2885                                   }
2886                                   SendToProgram("playother\n", &first);
2887                                   firstMove = FALSE;
2888                                 } else {
2889                                   firstMove = TRUE;
2890                                 }
2891                             }
2892                         } else if (gameMode == IcsPlayingBlack) {
2893                             if (!WhiteOnMove(forwardMostMove)) {
2894                                 if (first.sendTime) {
2895                                   if (first.useColors) {
2896                                     SendToProgram("white\n", &first);
2897                                   }
2898                                   SendTimeRemaining(&first, FALSE);
2899                                 }
2900                                 if (first.useColors) {
2901                                   SendToProgram("black\n", &first);
2902                                 }
2903                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2904                                 first.maybeThinking = TRUE;
2905                             } else {
2906                                 if (first.usePlayother) {
2907                                   if (first.sendTime) {
2908                                     SendTimeRemaining(&first, FALSE);
2909                                   }
2910                                   SendToProgram("playother\n", &first);
2911                                   firstMove = FALSE;
2912                                 } else {
2913                                   firstMove = TRUE;
2914                                 }
2915                             }
2916                         }                       
2917                     }
2918 #endif
2919                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2920                         /* Moves came from oldmoves or moves command
2921                            while we weren't doing anything else.
2922                            */
2923                         currentMove = forwardMostMove;
2924                         ClearHighlights();/*!!could figure this out*/
2925                         flipView = appData.flipView;
2926                         DrawPosition(TRUE, boards[currentMove]);
2927                         DisplayBothClocks();
2928                         sprintf(str, "%s vs. %s",
2929                                 gameInfo.white, gameInfo.black);
2930                         DisplayTitle(str);
2931                         gameMode = IcsIdle;
2932                     } else {
2933                         /* Moves were history of an active game */
2934                         if (gameInfo.resultDetails != NULL) {
2935                             free(gameInfo.resultDetails);
2936                             gameInfo.resultDetails = NULL;
2937                         }
2938                     }
2939                     HistorySet(parseList, backwardMostMove,
2940                                forwardMostMove, currentMove-1);
2941                     DisplayMove(currentMove - 1);
2942                     if (started == STARTED_MOVES) next_out = i;
2943                     started = STARTED_NONE;
2944                     ics_getting_history = H_FALSE;
2945                     break;
2946
2947                   case STARTED_OBSERVE:
2948                     started = STARTED_NONE;
2949                     SendToICS(ics_prefix);
2950                     SendToICS("refresh\n");
2951                     break;
2952
2953                   default:
2954                     break;
2955                 }
2956                 if(bookHit) { // [HGM] book: simulate book reply
2957                     static char bookMove[MSG_SIZ]; // a bit generous?
2958
2959                     programStats.nodes = programStats.depth = programStats.time = 
2960                     programStats.score = programStats.got_only_move = 0;
2961                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2962
2963                     strcpy(bookMove, "move ");
2964                     strcat(bookMove, bookHit);
2965                     HandleMachineMove(bookMove, &first);
2966                 }
2967                 continue;
2968             }
2969             
2970             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2971                  started == STARTED_HOLDINGS ||
2972                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2973                 /* Accumulate characters in move list or board */
2974                 parse[parse_pos++] = buf[i];
2975             }
2976             
2977             /* Start of game messages.  Mostly we detect start of game
2978                when the first board image arrives.  On some versions
2979                of the ICS, though, we need to do a "refresh" after starting
2980                to observe in order to get the current board right away. */
2981             if (looking_at(buf, &i, "Adding game * to observation list")) {
2982                 started = STARTED_OBSERVE;
2983                 continue;
2984             }
2985
2986             /* Handle auto-observe */
2987             if (appData.autoObserve &&
2988                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2989                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2990                 char *player;
2991                 /* Choose the player that was highlighted, if any. */
2992                 if (star_match[0][0] == '\033' ||
2993                     star_match[1][0] != '\033') {
2994                     player = star_match[0];
2995                 } else {
2996                     player = star_match[2];
2997                 }
2998                 sprintf(str, "%sobserve %s\n",
2999                         ics_prefix, StripHighlightAndTitle(player));
3000                 SendToICS(str);
3001
3002                 /* Save ratings from notify string */
3003                 strcpy(player1Name, star_match[0]);
3004                 player1Rating = string_to_rating(star_match[1]);
3005                 strcpy(player2Name, star_match[2]);
3006                 player2Rating = string_to_rating(star_match[3]);
3007
3008                 if (appData.debugMode)
3009                   fprintf(debugFP, 
3010                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3011                           player1Name, player1Rating,
3012                           player2Name, player2Rating);
3013
3014                 continue;
3015             }
3016
3017             /* Deal with automatic examine mode after a game,
3018                and with IcsObserving -> IcsExamining transition */
3019             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3020                 looking_at(buf, &i, "has made you an examiner of game *")) {
3021
3022                 int gamenum = atoi(star_match[0]);
3023                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3024                     gamenum == ics_gamenum) {
3025                     /* We were already playing or observing this game;
3026                        no need to refetch history */
3027                     gameMode = IcsExamining;
3028                     if (pausing) {
3029                         pauseExamForwardMostMove = forwardMostMove;
3030                     } else if (currentMove < forwardMostMove) {
3031                         ForwardInner(forwardMostMove);
3032                     }
3033                 } else {
3034                     /* I don't think this case really can happen */
3035                     SendToICS(ics_prefix);
3036                     SendToICS("refresh\n");
3037                 }
3038                 continue;
3039             }    
3040             
3041             /* Error messages */
3042 //          if (ics_user_moved) {
3043             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3044                 if (looking_at(buf, &i, "Illegal move") ||
3045                     looking_at(buf, &i, "Not a legal move") ||
3046                     looking_at(buf, &i, "Your king is in check") ||
3047                     looking_at(buf, &i, "It isn't your turn") ||
3048                     looking_at(buf, &i, "It is not your move")) {
3049                     /* Illegal move */
3050                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3051                         currentMove = --forwardMostMove;
3052                         DisplayMove(currentMove - 1); /* before DMError */
3053                         DrawPosition(FALSE, boards[currentMove]);
3054                         SwitchClocks();
3055                         DisplayBothClocks();
3056                     }
3057                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3058                     ics_user_moved = 0;
3059                     continue;
3060                 }
3061             }
3062
3063             if (looking_at(buf, &i, "still have time") ||
3064                 looking_at(buf, &i, "not out of time") ||
3065                 looking_at(buf, &i, "either player is out of time") ||
3066                 looking_at(buf, &i, "has timeseal; checking")) {
3067                 /* We must have called his flag a little too soon */
3068                 whiteFlag = blackFlag = FALSE;
3069                 continue;
3070             }
3071
3072             if (looking_at(buf, &i, "added * seconds to") ||
3073                 looking_at(buf, &i, "seconds were added to")) {
3074                 /* Update the clocks */
3075                 SendToICS(ics_prefix);
3076                 SendToICS("refresh\n");
3077                 continue;
3078             }
3079
3080             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3081                 ics_clock_paused = TRUE;
3082                 StopClocks();
3083                 continue;
3084             }
3085
3086             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3087                 ics_clock_paused = FALSE;
3088                 StartClocks();
3089                 continue;
3090             }
3091
3092             /* Grab player ratings from the Creating: message.
3093                Note we have to check for the special case when
3094                the ICS inserts things like [white] or [black]. */
3095             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3096                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3097                 /* star_matches:
3098                    0    player 1 name (not necessarily white)
3099                    1    player 1 rating
3100                    2    empty, white, or black (IGNORED)
3101                    3    player 2 name (not necessarily black)
3102                    4    player 2 rating
3103                    
3104                    The names/ratings are sorted out when the game
3105                    actually starts (below).
3106                 */
3107                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3108                 player1Rating = string_to_rating(star_match[1]);
3109                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3110                 player2Rating = string_to_rating(star_match[4]);
3111
3112                 if (appData.debugMode)
3113                   fprintf(debugFP, 
3114                           "Ratings from 'Creating:' %s %d, %s %d\n",
3115                           player1Name, player1Rating,
3116                           player2Name, player2Rating);
3117
3118                 continue;
3119             }
3120             
3121             /* Improved generic start/end-of-game messages */
3122             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3123                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3124                 /* If tkind == 0: */
3125                 /* star_match[0] is the game number */
3126                 /*           [1] is the white player's name */
3127                 /*           [2] is the black player's name */
3128                 /* For end-of-game: */
3129                 /*           [3] is the reason for the game end */
3130                 /*           [4] is a PGN end game-token, preceded by " " */
3131                 /* For start-of-game: */
3132                 /*           [3] begins with "Creating" or "Continuing" */
3133                 /*           [4] is " *" or empty (don't care). */
3134                 int gamenum = atoi(star_match[0]);
3135                 char *whitename, *blackname, *why, *endtoken;
3136                 ChessMove endtype = (ChessMove) 0;
3137
3138                 if (tkind == 0) {
3139                   whitename = star_match[1];
3140                   blackname = star_match[2];
3141                   why = star_match[3];
3142                   endtoken = star_match[4];
3143                 } else {
3144                   whitename = star_match[1];
3145                   blackname = star_match[3];
3146                   why = star_match[5];
3147                   endtoken = star_match[6];
3148                 }
3149
3150                 /* Game start messages */
3151                 if (strncmp(why, "Creating ", 9) == 0 ||
3152                     strncmp(why, "Continuing ", 11) == 0) {
3153                     gs_gamenum = gamenum;
3154                     strcpy(gs_kind, strchr(why, ' ') + 1);
3155                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3156 #if ZIPPY
3157                     if (appData.zippyPlay) {
3158                         ZippyGameStart(whitename, blackname);
3159                     }
3160 #endif /*ZIPPY*/
3161                     continue;
3162                 }
3163
3164                 /* Game end messages */
3165                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3166                     ics_gamenum != gamenum) {
3167                     continue;
3168                 }
3169                 while (endtoken[0] == ' ') endtoken++;
3170                 switch (endtoken[0]) {
3171                   case '*':
3172                   default:
3173                     endtype = GameUnfinished;
3174                     break;
3175                   case '0':
3176                     endtype = BlackWins;
3177                     break;
3178                   case '1':
3179                     if (endtoken[1] == '/')
3180                       endtype = GameIsDrawn;
3181                     else
3182                       endtype = WhiteWins;
3183                     break;
3184                 }
3185                 GameEnds(endtype, why, GE_ICS);
3186 #if ZIPPY
3187                 if (appData.zippyPlay && first.initDone) {
3188                     ZippyGameEnd(endtype, why);
3189                     if (first.pr == NULL) {
3190                       /* Start the next process early so that we'll
3191                          be ready for the next challenge */
3192                       StartChessProgram(&first);
3193                     }
3194                     /* Send "new" early, in case this command takes
3195                        a long time to finish, so that we'll be ready
3196                        for the next challenge. */
3197                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3198                     Reset(TRUE, TRUE);
3199                 }
3200 #endif /*ZIPPY*/
3201                 continue;
3202             }
3203
3204             if (looking_at(buf, &i, "Removing game * from observation") ||
3205                 looking_at(buf, &i, "no longer observing game *") ||
3206                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3207                 if (gameMode == IcsObserving &&
3208                     atoi(star_match[0]) == ics_gamenum)
3209                   {
3210                       /* icsEngineAnalyze */
3211                       if (appData.icsEngineAnalyze) {
3212                             ExitAnalyzeMode();
3213                             ModeHighlight();
3214                       }
3215                       StopClocks();
3216                       gameMode = IcsIdle;
3217                       ics_gamenum = -1;
3218                       ics_user_moved = FALSE;
3219                   }
3220                 continue;
3221             }
3222
3223             if (looking_at(buf, &i, "no longer examining game *")) {
3224                 if (gameMode == IcsExamining &&
3225                     atoi(star_match[0]) == ics_gamenum)
3226                   {
3227                       gameMode = IcsIdle;
3228                       ics_gamenum = -1;
3229                       ics_user_moved = FALSE;
3230                   }
3231                 continue;
3232             }
3233
3234             /* Advance leftover_start past any newlines we find,
3235                so only partial lines can get reparsed */
3236             if (looking_at(buf, &i, "\n")) {
3237                 prevColor = curColor;
3238                 if (curColor != ColorNormal) {
3239                     if (oldi > next_out) {
3240                         SendToPlayer(&buf[next_out], oldi - next_out);
3241                         next_out = oldi;
3242                     }
3243                     Colorize(ColorNormal, FALSE);
3244                     curColor = ColorNormal;
3245                 }
3246                 if (started == STARTED_BOARD) {
3247                     started = STARTED_NONE;
3248                     parse[parse_pos] = NULLCHAR;
3249                     ParseBoard12(parse);
3250                     ics_user_moved = 0;
3251
3252                     /* Send premove here */
3253                     if (appData.premove) {
3254                       char str[MSG_SIZ];
3255                       if (currentMove == 0 &&
3256                           gameMode == IcsPlayingWhite &&
3257                           appData.premoveWhite) {
3258                         sprintf(str, "%s\n", appData.premoveWhiteText);
3259                         if (appData.debugMode)
3260                           fprintf(debugFP, "Sending premove:\n");
3261                         SendToICS(str);
3262                       } else if (currentMove == 1 &&
3263                                  gameMode == IcsPlayingBlack &&
3264                                  appData.premoveBlack) {
3265                         sprintf(str, "%s\n", appData.premoveBlackText);
3266                         if (appData.debugMode)
3267                           fprintf(debugFP, "Sending premove:\n");
3268                         SendToICS(str);
3269                       } else if (gotPremove) {
3270                         gotPremove = 0;
3271                         ClearPremoveHighlights();
3272                         if (appData.debugMode)
3273                           fprintf(debugFP, "Sending premove:\n");
3274                           UserMoveEvent(premoveFromX, premoveFromY, 
3275                                         premoveToX, premoveToY, 
3276                                         premovePromoChar);
3277                       }
3278                     }
3279
3280                     /* Usually suppress following prompt */
3281                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3282                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3283                         if (looking_at(buf, &i, "*% ")) {
3284                             savingComment = FALSE;
3285                             suppressKibitz = 0;
3286                         }
3287                     }
3288                     next_out = i;
3289                 } else if (started == STARTED_HOLDINGS) {
3290                     int gamenum;
3291                     char new_piece[MSG_SIZ];
3292                     started = STARTED_NONE;
3293                     parse[parse_pos] = NULLCHAR;
3294                     if (appData.debugMode)
3295                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3296                                                         parse, currentMove);
3297                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3298                         gamenum == ics_gamenum) {
3299                         if (gameInfo.variant == VariantNormal) {
3300                           /* [HGM] We seem to switch variant during a game!
3301                            * Presumably no holdings were displayed, so we have
3302                            * to move the position two files to the right to
3303                            * create room for them!
3304                            */
3305                           VariantClass newVariant;
3306                           switch(gameInfo.boardWidth) { // base guess on board width
3307                                 case 9:  newVariant = VariantShogi; break;
3308                                 case 10: newVariant = VariantGreat; break;
3309                                 default: newVariant = VariantCrazyhouse; break;
3310                           }
3311                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3312                           /* Get a move list just to see the header, which
3313                              will tell us whether this is really bug or zh */
3314                           if (ics_getting_history == H_FALSE) {
3315                             ics_getting_history = H_REQUESTED;
3316                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3317                             SendToICS(str);
3318                           }
3319                         }
3320                         new_piece[0] = NULLCHAR;
3321                         sscanf(parse, "game %d white [%s black [%s <- %s",
3322                                &gamenum, white_holding, black_holding,
3323                                new_piece);
3324                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3325                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3326                         /* [HGM] copy holdings to board holdings area */
3327                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3328                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3329                         boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3330 #if ZIPPY
3331                         if (appData.zippyPlay && first.initDone) {
3332                             ZippyHoldings(white_holding, black_holding,
3333                                           new_piece);
3334                         }
3335 #endif /*ZIPPY*/
3336                         if (tinyLayout || smallLayout) {
3337                             char wh[16], bh[16];
3338                             PackHolding(wh, white_holding);
3339                             PackHolding(bh, black_holding);
3340                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3341                                     gameInfo.white, gameInfo.black);
3342                         } else {
3343                             sprintf(str, "%s [%s] vs. %s [%s]",
3344                                     gameInfo.white, white_holding,
3345                                     gameInfo.black, black_holding);
3346                         }
3347
3348                         DrawPosition(FALSE, boards[currentMove]);
3349                         DisplayTitle(str);
3350                     }
3351                     /* Suppress following prompt */
3352                     if (looking_at(buf, &i, "*% ")) {
3353                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3354                         savingComment = FALSE;
3355                         suppressKibitz = 0;
3356                     }
3357                     next_out = i;
3358                 }
3359                 continue;
3360             }
3361
3362             i++;                /* skip unparsed character and loop back */
3363         }
3364         
3365         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3366 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3367 //          SendToPlayer(&buf[next_out], i - next_out);
3368             started != STARTED_HOLDINGS && leftover_start > next_out) {
3369             SendToPlayer(&buf[next_out], leftover_start - next_out);
3370             next_out = i;
3371         }
3372         
3373         leftover_len = buf_len - leftover_start;
3374         /* if buffer ends with something we couldn't parse,
3375            reparse it after appending the next read */
3376         
3377     } else if (count == 0) {
3378         RemoveInputSource(isr);
3379         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3380     } else {
3381         DisplayFatalError(_("Error reading from ICS"), error, 1);
3382     }
3383 }
3384
3385
3386 /* Board style 12 looks like this:
3387    
3388    <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
3389    
3390  * The "<12> " is stripped before it gets to this routine.  The two
3391  * trailing 0's (flip state and clock ticking) are later addition, and
3392  * some chess servers may not have them, or may have only the first.
3393  * Additional trailing fields may be added in the future.  
3394  */
3395
3396 #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"
3397
3398 #define RELATION_OBSERVING_PLAYED    0
3399 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3400 #define RELATION_PLAYING_MYMOVE      1
3401 #define RELATION_PLAYING_NOTMYMOVE  -1
3402 #define RELATION_EXAMINING           2
3403 #define RELATION_ISOLATED_BOARD     -3
3404 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3405
3406 void
3407 ParseBoard12(string)
3408      char *string;
3409
3410     GameMode newGameMode;
3411     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3412     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3413     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3414     char to_play, board_chars[200];
3415     char move_str[500], str[500], elapsed_time[500];
3416     char black[32], white[32];
3417     Board board;
3418     int prevMove = currentMove;
3419     int ticking = 2;
3420     ChessMove moveType;
3421     int fromX, fromY, toX, toY;
3422     char promoChar;
3423     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3424     char *bookHit = NULL; // [HGM] book
3425     Boolean weird = FALSE, reqFlag = FALSE;
3426
3427     fromX = fromY = toX = toY = -1;
3428     
3429     newGame = FALSE;
3430
3431     if (appData.debugMode)
3432       fprintf(debugFP, _("Parsing board: %s\n"), string);
3433
3434     move_str[0] = NULLCHAR;
3435     elapsed_time[0] = NULLCHAR;
3436     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3437         int  i = 0, j;
3438         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3439             if(string[i] == ' ') { ranks++; files = 0; }
3440             else files++;
3441             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3442             i++;
3443         }
3444         for(j = 0; j <i; j++) board_chars[j] = string[j];
3445         board_chars[i] = '\0';
3446         string += i + 1;
3447     }
3448     n = sscanf(string, PATTERN, &to_play, &double_push,
3449                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3450                &gamenum, white, black, &relation, &basetime, &increment,
3451                &white_stren, &black_stren, &white_time, &black_time,
3452                &moveNum, str, elapsed_time, move_str, &ics_flip,
3453                &ticking);
3454
3455     if (n < 21) {
3456         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3457         DisplayError(str, 0);
3458         return;
3459     }
3460
3461     /* Convert the move number to internal form */
3462     moveNum = (moveNum - 1) * 2;
3463     if (to_play == 'B') moveNum++;
3464     if (moveNum >= MAX_MOVES) {
3465       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3466                         0, 1);
3467       return;
3468     }
3469     
3470     switch (relation) {
3471       case RELATION_OBSERVING_PLAYED:
3472       case RELATION_OBSERVING_STATIC:
3473         if (gamenum == -1) {
3474             /* Old ICC buglet */
3475             relation = RELATION_OBSERVING_STATIC;
3476         }
3477         newGameMode = IcsObserving;
3478         break;
3479       case RELATION_PLAYING_MYMOVE:
3480       case RELATION_PLAYING_NOTMYMOVE:
3481         newGameMode =
3482           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3483             IcsPlayingWhite : IcsPlayingBlack;
3484         break;
3485       case RELATION_EXAMINING:
3486         newGameMode = IcsExamining;
3487         break;
3488       case RELATION_ISOLATED_BOARD:
3489       default:
3490         /* Just display this board.  If user was doing something else,
3491            we will forget about it until the next board comes. */ 
3492         newGameMode = IcsIdle;
3493         break;
3494       case RELATION_STARTING_POSITION:
3495         newGameMode = gameMode;
3496         break;
3497     }
3498     
3499     /* Modify behavior for initial board display on move listing
3500        of wild games.
3501        */
3502     switch (ics_getting_history) {
3503       case H_FALSE:
3504       case H_REQUESTED:
3505         break;
3506       case H_GOT_REQ_HEADER:
3507       case H_GOT_UNREQ_HEADER:
3508         /* This is the initial position of the current game */
3509         gamenum = ics_gamenum;
3510         moveNum = 0;            /* old ICS bug workaround */
3511         if (to_play == 'B') {
3512           startedFromSetupPosition = TRUE;
3513           blackPlaysFirst = TRUE;
3514           moveNum = 1;
3515           if (forwardMostMove == 0) forwardMostMove = 1;
3516           if (backwardMostMove == 0) backwardMostMove = 1;
3517           if (currentMove == 0) currentMove = 1;
3518         }
3519         newGameMode = gameMode;
3520         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3521         break;
3522       case H_GOT_UNWANTED_HEADER:
3523         /* This is an initial board that we don't want */
3524         return;
3525       case H_GETTING_MOVES:
3526         /* Should not happen */
3527         DisplayError(_("Error gathering move list: extra board"), 0);
3528         ics_getting_history = H_FALSE;
3529         return;
3530     }
3531
3532    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3533                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3534      /* [HGM] We seem to have switched variant unexpectedly
3535       * Try to guess new variant from board size
3536       */
3537           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3538           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3539           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3540           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3541           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3542           if(!weird) newVariant = VariantNormal;
3543           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3544           /* Get a move list just to see the header, which
3545              will tell us whether this is really bug or zh */
3546           if (ics_getting_history == H_FALSE) {
3547             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3548             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3549             SendToICS(str);
3550           }
3551     }
3552     
3553     /* Take action if this is the first board of a new game, or of a
3554        different game than is currently being displayed.  */
3555     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3556         relation == RELATION_ISOLATED_BOARD) {
3557         
3558         /* Forget the old game and get the history (if any) of the new one */
3559         if (gameMode != BeginningOfGame) {
3560           Reset(TRUE, TRUE);
3561         }
3562         newGame = TRUE;
3563         if (appData.autoRaiseBoard) BoardToTop();
3564         prevMove = -3;
3565         if (gamenum == -1) {
3566             newGameMode = IcsIdle;
3567         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3568                    appData.getMoveList && !reqFlag) {
3569             /* Need to get game history */
3570             ics_getting_history = H_REQUESTED;
3571             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3572             SendToICS(str);
3573         }
3574         
3575         /* Initially flip the board to have black on the bottom if playing
3576            black or if the ICS flip flag is set, but let the user change
3577            it with the Flip View button. */
3578         flipView = appData.autoFlipView ? 
3579           (newGameMode == IcsPlayingBlack) || ics_flip :
3580           appData.flipView;
3581         
3582         /* Done with values from previous mode; copy in new ones */
3583         gameMode = newGameMode;
3584         ModeHighlight();
3585         ics_gamenum = gamenum;
3586         if (gamenum == gs_gamenum) {
3587             int klen = strlen(gs_kind);
3588             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3589             sprintf(str, "ICS %s", gs_kind);
3590             gameInfo.event = StrSave(str);
3591         } else {
3592             gameInfo.event = StrSave("ICS game");
3593         }
3594         gameInfo.site = StrSave(appData.icsHost);
3595         gameInfo.date = PGNDate();
3596         gameInfo.round = StrSave("-");
3597         gameInfo.white = StrSave(white);
3598         gameInfo.black = StrSave(black);
3599         timeControl = basetime * 60 * 1000;
3600         timeControl_2 = 0;
3601         timeIncrement = increment * 1000;
3602         movesPerSession = 0;
3603         gameInfo.timeControl = TimeControlTagValue();
3604         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3605   if (appData.debugMode) {
3606     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3607     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3608     setbuf(debugFP, NULL);
3609   }
3610
3611         gameInfo.outOfBook = NULL;
3612         
3613         /* Do we have the ratings? */
3614         if (strcmp(player1Name, white) == 0 &&
3615             strcmp(player2Name, black) == 0) {
3616             if (appData.debugMode)
3617               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3618                       player1Rating, player2Rating);
3619             gameInfo.whiteRating = player1Rating;
3620             gameInfo.blackRating = player2Rating;
3621         } else if (strcmp(player2Name, white) == 0 &&
3622                    strcmp(player1Name, black) == 0) {
3623             if (appData.debugMode)
3624               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3625                       player2Rating, player1Rating);
3626             gameInfo.whiteRating = player2Rating;
3627             gameInfo.blackRating = player1Rating;
3628         }
3629         player1Name[0] = player2Name[0] = NULLCHAR;
3630
3631         /* Silence shouts if requested */
3632         if (appData.quietPlay &&
3633             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3634             SendToICS(ics_prefix);
3635             SendToICS("set shout 0\n");
3636         }
3637     }
3638     
3639     /* Deal with midgame name changes */
3640     if (!newGame) {
3641         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3642             if (gameInfo.white) free(gameInfo.white);
3643             gameInfo.white = StrSave(white);
3644         }
3645         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3646             if (gameInfo.black) free(gameInfo.black);
3647             gameInfo.black = StrSave(black);
3648         }
3649     }
3650     
3651     /* Throw away game result if anything actually changes in examine mode */
3652     if (gameMode == IcsExamining && !newGame) {
3653         gameInfo.result = GameUnfinished;
3654         if (gameInfo.resultDetails != NULL) {
3655             free(gameInfo.resultDetails);
3656             gameInfo.resultDetails = NULL;
3657         }
3658     }
3659     
3660     /* In pausing && IcsExamining mode, we ignore boards coming
3661        in if they are in a different variation than we are. */
3662     if (pauseExamInvalid) return;
3663     if (pausing && gameMode == IcsExamining) {
3664         if (moveNum <= pauseExamForwardMostMove) {
3665             pauseExamInvalid = TRUE;
3666             forwardMostMove = pauseExamForwardMostMove;
3667             return;
3668         }
3669     }
3670     
3671   if (appData.debugMode) {
3672     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3673   }
3674     /* Parse the board */
3675     for (k = 0; k < ranks; k++) {
3676       for (j = 0; j < files; j++)
3677         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3678       if(gameInfo.holdingsWidth > 1) {
3679            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3680            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3681       }
3682     }
3683     CopyBoard(boards[moveNum], board);
3684     boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3685     if (moveNum == 0) {
3686         startedFromSetupPosition =
3687           !CompareBoards(board, initialPosition);
3688         if(startedFromSetupPosition)
3689             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3690     }
3691
3692     /* [HGM] Set castling rights. Take the outermost Rooks,
3693        to make it also work for FRC opening positions. Note that board12
3694        is really defective for later FRC positions, as it has no way to
3695        indicate which Rook can castle if they are on the same side of King.
3696        For the initial position we grant rights to the outermost Rooks,
3697        and remember thos rights, and we then copy them on positions
3698        later in an FRC game. This means WB might not recognize castlings with
3699        Rooks that have moved back to their original position as illegal,
3700        but in ICS mode that is not its job anyway.
3701     */
3702     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3703     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3704
3705         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3706             if(board[0][i] == WhiteRook) j = i;
3707         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3708         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3709             if(board[0][i] == WhiteRook) j = i;
3710         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3711         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3712             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3713         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3714         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3715             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3716         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3717
3718         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3719         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3720             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3721         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3722             if(board[BOARD_HEIGHT-1][k] == bKing)
3723                 initialRights[5] = castlingRights[moveNum][5] = k;
3724         if(gameInfo.variant == VariantTwoKings) {
3725             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3726             if(board[0][4] == wKing) initialRights[2] = castlingRights[moveNum][2] = 4;
3727             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = castlingRights[moveNum][5] = 4;
3728         }
3729     } else { int r;
3730         r = castlingRights[moveNum][0] = initialRights[0];
3731         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3732         r = castlingRights[moveNum][1] = initialRights[1];
3733         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3734         r = castlingRights[moveNum][3] = initialRights[3];
3735         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3736         r = castlingRights[moveNum][4] = initialRights[4];
3737         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3738         /* wildcastle kludge: always assume King has rights */
3739         r = castlingRights[moveNum][2] = initialRights[2];
3740         r = castlingRights[moveNum][5] = initialRights[5];
3741     }
3742     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3743     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3744
3745     
3746     if (ics_getting_history == H_GOT_REQ_HEADER ||
3747         ics_getting_history == H_GOT_UNREQ_HEADER) {
3748         /* This was an initial position from a move list, not
3749            the current position */
3750         return;
3751     }
3752     
3753     /* Update currentMove and known move number limits */
3754     newMove = newGame || moveNum > forwardMostMove;
3755
3756     if (newGame) {
3757         forwardMostMove = backwardMostMove = currentMove = moveNum;
3758         if (gameMode == IcsExamining && moveNum == 0) {
3759           /* Workaround for ICS limitation: we are not told the wild
3760              type when starting to examine a game.  But if we ask for
3761              the move list, the move list header will tell us */
3762             ics_getting_history = H_REQUESTED;
3763             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3764             SendToICS(str);
3765         }
3766     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3767                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3768 #if ZIPPY
3769         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3770         /* [HGM] applied this also to an engine that is silently watching        */
3771         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3772             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3773             gameInfo.variant == currentlyInitializedVariant) {
3774           takeback = forwardMostMove - moveNum;
3775           for (i = 0; i < takeback; i++) {
3776             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3777             SendToProgram("undo\n", &first);
3778           }
3779         }
3780 #endif
3781
3782         forwardMostMove = moveNum;
3783         if (!pausing || currentMove > forwardMostMove)
3784           currentMove = forwardMostMove;
3785     } else {
3786         /* New part of history that is not contiguous with old part */ 
3787         if (pausing && gameMode == IcsExamining) {
3788             pauseExamInvalid = TRUE;
3789             forwardMostMove = pauseExamForwardMostMove;
3790             return;
3791         }
3792         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3793 #if ZIPPY
3794             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3795                 // [HGM] when we will receive the move list we now request, it will be
3796                 // fed to the engine from the first move on. So if the engine is not
3797                 // in the initial position now, bring it there.
3798                 InitChessProgram(&first, 0);
3799             }
3800 #endif
3801             ics_getting_history = H_REQUESTED;
3802             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3803             SendToICS(str);
3804         }
3805         forwardMostMove = backwardMostMove = currentMove = moveNum;
3806     }
3807     
3808     /* Update the clocks */
3809     if (strchr(elapsed_time, '.')) {
3810       /* Time is in ms */
3811       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3812       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3813     } else {
3814       /* Time is in seconds */
3815       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3816       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3817     }
3818       
3819
3820 #if ZIPPY
3821     if (appData.zippyPlay && newGame &&
3822         gameMode != IcsObserving && gameMode != IcsIdle &&
3823         gameMode != IcsExamining)
3824       ZippyFirstBoard(moveNum, basetime, increment);
3825 #endif
3826     
3827     /* Put the move on the move list, first converting
3828        to canonical algebraic form. */
3829     if (moveNum > 0) {
3830   if (appData.debugMode) {
3831     if (appData.debugMode) { int f = forwardMostMove;
3832         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3833                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3834     }
3835     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3836     fprintf(debugFP, "moveNum = %d\n", moveNum);
3837     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3838     setbuf(debugFP, NULL);
3839   }
3840         if (moveNum <= backwardMostMove) {
3841             /* We don't know what the board looked like before
3842                this move.  Punt. */
3843             strcpy(parseList[moveNum - 1], move_str);
3844             strcat(parseList[moveNum - 1], " ");
3845             strcat(parseList[moveNum - 1], elapsed_time);
3846             moveList[moveNum - 1][0] = NULLCHAR;
3847         } else if (strcmp(move_str, "none") == 0) {
3848             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3849             /* Again, we don't know what the board looked like;
3850                this is really the start of the game. */
3851             parseList[moveNum - 1][0] = NULLCHAR;
3852             moveList[moveNum - 1][0] = NULLCHAR;
3853             backwardMostMove = moveNum;
3854             startedFromSetupPosition = TRUE;
3855             fromX = fromY = toX = toY = -1;
3856         } else {
3857           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3858           //                 So we parse the long-algebraic move string in stead of the SAN move
3859           int valid; char buf[MSG_SIZ], *prom;
3860
3861           // str looks something like "Q/a1-a2"; kill the slash
3862           if(str[1] == '/') 
3863                 sprintf(buf, "%c%s", str[0], str+2);
3864           else  strcpy(buf, str); // might be castling
3865           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3866                 strcat(buf, prom); // long move lacks promo specification!
3867           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3868                 if(appData.debugMode) 
3869                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3870                 strcpy(move_str, buf);
3871           }
3872           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3873                                 &fromX, &fromY, &toX, &toY, &promoChar)
3874                || ParseOneMove(buf, moveNum - 1, &moveType,
3875                                 &fromX, &fromY, &toX, &toY, &promoChar);
3876           // end of long SAN patch
3877           if (valid) {
3878             (void) CoordsToAlgebraic(boards[moveNum - 1],
3879                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3880                                      fromY, fromX, toY, toX, promoChar,
3881                                      parseList[moveNum-1]);
3882             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3883                              castlingRights[moveNum]) ) {
3884               case MT_NONE:
3885               case MT_STALEMATE:
3886               default:
3887                 break;
3888               case MT_CHECK:
3889                 if(gameInfo.variant != VariantShogi)
3890                     strcat(parseList[moveNum - 1], "+");
3891                 break;
3892               case MT_CHECKMATE:
3893               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3894                 strcat(parseList[moveNum - 1], "#");
3895                 break;
3896             }
3897             strcat(parseList[moveNum - 1], " ");
3898             strcat(parseList[moveNum - 1], elapsed_time);
3899             /* currentMoveString is set as a side-effect of ParseOneMove */
3900             strcpy(moveList[moveNum - 1], currentMoveString);
3901             strcat(moveList[moveNum - 1], "\n");
3902           } else {
3903             /* Move from ICS was illegal!?  Punt. */
3904   if (appData.debugMode) {
3905     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3906     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3907   }
3908             strcpy(parseList[moveNum - 1], move_str);
3909             strcat(parseList[moveNum - 1], " ");
3910             strcat(parseList[moveNum - 1], elapsed_time);
3911             moveList[moveNum - 1][0] = NULLCHAR;
3912             fromX = fromY = toX = toY = -1;
3913           }
3914         }
3915   if (appData.debugMode) {
3916     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3917     setbuf(debugFP, NULL);
3918   }
3919
3920 #if ZIPPY
3921         /* Send move to chess program (BEFORE animating it). */
3922         if (appData.zippyPlay && !newGame && newMove && 
3923            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3924
3925             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3926                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3927                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3928                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3929                             move_str);
3930                     DisplayError(str, 0);
3931                 } else {
3932                     if (first.sendTime) {
3933                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3934                     }
3935                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3936                     if (firstMove && !bookHit) {
3937                         firstMove = FALSE;
3938                         if (first.useColors) {
3939                           SendToProgram(gameMode == IcsPlayingWhite ?
3940                                         "white\ngo\n" :
3941                                         "black\ngo\n", &first);
3942                         } else {
3943                           SendToProgram("go\n", &first);
3944                         }
3945                         first.maybeThinking = TRUE;
3946                     }
3947                 }
3948             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3949               if (moveList[moveNum - 1][0] == NULLCHAR) {
3950                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3951                 DisplayError(str, 0);
3952               } else {
3953                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3954                 SendMoveToProgram(moveNum - 1, &first);
3955               }
3956             }
3957         }
3958 #endif
3959     }
3960
3961     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3962         /* If move comes from a remote source, animate it.  If it
3963            isn't remote, it will have already been animated. */
3964         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3965             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3966         }
3967         if (!pausing && appData.highlightLastMove) {
3968             SetHighlights(fromX, fromY, toX, toY);
3969         }
3970     }
3971     
3972     /* Start the clocks */
3973     whiteFlag = blackFlag = FALSE;
3974     appData.clockMode = !(basetime == 0 && increment == 0);
3975     if (ticking == 0) {
3976       ics_clock_paused = TRUE;
3977       StopClocks();
3978     } else if (ticking == 1) {
3979       ics_clock_paused = FALSE;
3980     }
3981     if (gameMode == IcsIdle ||
3982         relation == RELATION_OBSERVING_STATIC ||
3983         relation == RELATION_EXAMINING ||
3984         ics_clock_paused)
3985       DisplayBothClocks();
3986     else
3987       StartClocks();
3988     
3989     /* Display opponents and material strengths */
3990     if (gameInfo.variant != VariantBughouse &&
3991         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3992         if (tinyLayout || smallLayout) {
3993             if(gameInfo.variant == VariantNormal)
3994                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3995                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3996                     basetime, increment);
3997             else
3998                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3999                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4000                     basetime, increment, (int) gameInfo.variant);
4001         } else {
4002             if(gameInfo.variant == VariantNormal)
4003                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4004                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4005                     basetime, increment);
4006             else
4007                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4008                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4009                     basetime, increment, VariantName(gameInfo.variant));
4010         }
4011         DisplayTitle(str);
4012   if (appData.debugMode) {
4013     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4014   }
4015     }
4016
4017    
4018     /* Display the board */
4019     if (!pausing && !appData.noGUI) {
4020       
4021       if (appData.premove)
4022           if (!gotPremove || 
4023              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4024              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4025               ClearPremoveHighlights();
4026
4027       DrawPosition(FALSE, boards[currentMove]);
4028       DisplayMove(moveNum - 1);
4029       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4030             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4031               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4032         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4033       }
4034     }
4035
4036     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4037 #if ZIPPY
4038     if(bookHit) { // [HGM] book: simulate book reply
4039         static char bookMove[MSG_SIZ]; // a bit generous?
4040
4041         programStats.nodes = programStats.depth = programStats.time = 
4042         programStats.score = programStats.got_only_move = 0;
4043         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4044
4045         strcpy(bookMove, "move ");
4046         strcat(bookMove, bookHit);
4047         HandleMachineMove(bookMove, &first);
4048     }
4049 #endif
4050 }
4051
4052 void
4053 GetMoveListEvent()
4054 {
4055     char buf[MSG_SIZ];
4056     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4057         ics_getting_history = H_REQUESTED;
4058         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4059         SendToICS(buf);
4060     }
4061 }
4062
4063 void
4064 AnalysisPeriodicEvent(force)
4065      int force;
4066 {
4067     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4068          && !force) || !appData.periodicUpdates)
4069       return;
4070
4071     /* Send . command to Crafty to collect stats */
4072     SendToProgram(".\n", &first);
4073
4074     /* Don't send another until we get a response (this makes
4075        us stop sending to old Crafty's which don't understand
4076        the "." command (sending illegal cmds resets node count & time,
4077        which looks bad)) */
4078     programStats.ok_to_send = 0;
4079 }
4080
4081 void ics_update_width(new_width)
4082         int new_width;
4083 {
4084         ics_printf("set width %d\n", new_width);
4085 }
4086
4087 void
4088 SendMoveToProgram(moveNum, cps)
4089      int moveNum;
4090      ChessProgramState *cps;
4091 {
4092     char buf[MSG_SIZ];
4093
4094     if (cps->useUsermove) {
4095       SendToProgram("usermove ", cps);
4096     }
4097     if (cps->useSAN) {
4098       char *space;
4099       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4100         int len = space - parseList[moveNum];
4101         memcpy(buf, parseList[moveNum], len);
4102         buf[len++] = '\n';
4103         buf[len] = NULLCHAR;
4104       } else {
4105         sprintf(buf, "%s\n", parseList[moveNum]);
4106       }
4107       SendToProgram(buf, cps);
4108     } else {
4109       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4110         AlphaRank(moveList[moveNum], 4);
4111         SendToProgram(moveList[moveNum], cps);
4112         AlphaRank(moveList[moveNum], 4); // and back
4113       } else
4114       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4115        * the engine. It would be nice to have a better way to identify castle 
4116        * moves here. */
4117       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4118                                                                          && cps->useOOCastle) {
4119         int fromX = moveList[moveNum][0] - AAA; 
4120         int fromY = moveList[moveNum][1] - ONE;
4121         int toX = moveList[moveNum][2] - AAA; 
4122         int toY = moveList[moveNum][3] - ONE;
4123         if((boards[moveNum][fromY][fromX] == WhiteKing 
4124             && boards[moveNum][toY][toX] == WhiteRook)
4125            || (boards[moveNum][fromY][fromX] == BlackKing 
4126                && boards[moveNum][toY][toX] == BlackRook)) {
4127           if(toX > fromX) SendToProgram("O-O\n", cps);
4128           else SendToProgram("O-O-O\n", cps);
4129         }
4130         else SendToProgram(moveList[moveNum], cps);
4131       }
4132       else SendToProgram(moveList[moveNum], cps);
4133       /* End of additions by Tord */
4134     }
4135
4136     /* [HGM] setting up the opening has brought engine in force mode! */
4137     /*       Send 'go' if we are in a mode where machine should play. */
4138     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4139         (gameMode == TwoMachinesPlay   ||
4140 #ifdef ZIPPY
4141          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4142 #endif
4143          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4144         SendToProgram("go\n", cps);
4145   if (appData.debugMode) {
4146     fprintf(debugFP, "(extra)\n");
4147   }
4148     }
4149     setboardSpoiledMachineBlack = 0;
4150 }
4151
4152 void
4153 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4154      ChessMove moveType;
4155      int fromX, fromY, toX, toY;
4156 {
4157     char user_move[MSG_SIZ];
4158
4159     switch (moveType) {
4160       default:
4161         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4162                 (int)moveType, fromX, fromY, toX, toY);
4163         DisplayError(user_move + strlen("say "), 0);
4164         break;
4165       case WhiteKingSideCastle:
4166       case BlackKingSideCastle:
4167       case WhiteQueenSideCastleWild:
4168       case BlackQueenSideCastleWild:
4169       /* PUSH Fabien */
4170       case WhiteHSideCastleFR:
4171       case BlackHSideCastleFR:
4172       /* POP Fabien */
4173         sprintf(user_move, "o-o\n");
4174         break;
4175       case WhiteQueenSideCastle:
4176       case BlackQueenSideCastle:
4177       case WhiteKingSideCastleWild:
4178       case BlackKingSideCastleWild:
4179       /* PUSH Fabien */
4180       case WhiteASideCastleFR:
4181       case BlackASideCastleFR:
4182       /* POP Fabien */
4183         sprintf(user_move, "o-o-o\n");
4184         break;
4185       case WhitePromotionQueen:
4186       case BlackPromotionQueen:
4187       case WhitePromotionRook:
4188       case BlackPromotionRook:
4189       case WhitePromotionBishop:
4190       case BlackPromotionBishop:
4191       case WhitePromotionKnight:
4192       case BlackPromotionKnight:
4193       case WhitePromotionKing:
4194       case BlackPromotionKing:
4195       case WhitePromotionChancellor:
4196       case BlackPromotionChancellor:
4197       case WhitePromotionArchbishop:
4198       case BlackPromotionArchbishop:
4199         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4200             sprintf(user_move, "%c%c%c%c=%c\n",
4201                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4202                 PieceToChar(WhiteFerz));
4203         else if(gameInfo.variant == VariantGreat)
4204             sprintf(user_move, "%c%c%c%c=%c\n",
4205                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4206                 PieceToChar(WhiteMan));
4207         else
4208             sprintf(user_move, "%c%c%c%c=%c\n",
4209                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4210                 PieceToChar(PromoPiece(moveType)));
4211         break;
4212       case WhiteDrop:
4213       case BlackDrop:
4214         sprintf(user_move, "%c@%c%c\n",
4215                 ToUpper(PieceToChar((ChessSquare) fromX)),
4216                 AAA + toX, ONE + toY);
4217         break;
4218       case NormalMove:
4219       case WhiteCapturesEnPassant:
4220       case BlackCapturesEnPassant:
4221       case IllegalMove:  /* could be a variant we don't quite understand */
4222         sprintf(user_move, "%c%c%c%c\n",
4223                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4224         break;
4225     }
4226     SendToICS(user_move);
4227     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4228         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4229 }
4230
4231 void
4232 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4233      int rf, ff, rt, ft;
4234      char promoChar;
4235      char move[7];
4236 {
4237     if (rf == DROP_RANK) {
4238         sprintf(move, "%c@%c%c\n",
4239                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4240     } else {
4241         if (promoChar == 'x' || promoChar == NULLCHAR) {
4242             sprintf(move, "%c%c%c%c\n",
4243                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4244         } else {
4245             sprintf(move, "%c%c%c%c%c\n",
4246                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4247         }
4248     }
4249 }
4250
4251 void
4252 ProcessICSInitScript(f)
4253      FILE *f;
4254 {
4255     char buf[MSG_SIZ];
4256
4257     while (fgets(buf, MSG_SIZ, f)) {
4258         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4259     }
4260
4261     fclose(f);
4262 }
4263
4264
4265 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4266 void
4267 AlphaRank(char *move, int n)
4268 {
4269 //    char *p = move, c; int x, y;
4270
4271     if (appData.debugMode) {
4272         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4273     }
4274
4275     if(move[1]=='*' && 
4276        move[2]>='0' && move[2]<='9' &&
4277        move[3]>='a' && move[3]<='x'    ) {
4278         move[1] = '@';
4279         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4280         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4281     } else
4282     if(move[0]>='0' && move[0]<='9' &&
4283        move[1]>='a' && move[1]<='x' &&
4284        move[2]>='0' && move[2]<='9' &&
4285        move[3]>='a' && move[3]<='x'    ) {
4286         /* input move, Shogi -> normal */
4287         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4288         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4289         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4290         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4291     } else
4292     if(move[1]=='@' &&
4293        move[3]>='0' && move[3]<='9' &&
4294        move[2]>='a' && move[2]<='x'    ) {
4295         move[1] = '*';
4296         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4297         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4298     } else
4299     if(
4300        move[0]>='a' && move[0]<='x' &&
4301        move[3]>='0' && move[3]<='9' &&
4302        move[2]>='a' && move[2]<='x'    ) {
4303          /* output move, normal -> Shogi */
4304         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4305         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4306         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4307         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4308         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4309     }
4310     if (appData.debugMode) {
4311         fprintf(debugFP, "   out = '%s'\n", move);
4312     }
4313 }
4314
4315 /* Parser for moves from gnuchess, ICS, or user typein box */
4316 Boolean
4317 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4318      char *move;
4319      int moveNum;
4320      ChessMove *moveType;
4321      int *fromX, *fromY, *toX, *toY;
4322      char *promoChar;
4323 {       
4324     if (appData.debugMode) {
4325         fprintf(debugFP, "move to parse: %s\n", move);
4326     }
4327     *moveType = yylexstr(moveNum, move);
4328
4329     switch (*moveType) {
4330       case WhitePromotionChancellor:
4331       case BlackPromotionChancellor:
4332       case WhitePromotionArchbishop:
4333       case BlackPromotionArchbishop:
4334       case WhitePromotionQueen:
4335       case BlackPromotionQueen:
4336       case WhitePromotionRook:
4337       case BlackPromotionRook:
4338       case WhitePromotionBishop:
4339       case BlackPromotionBishop:
4340       case WhitePromotionKnight:
4341       case BlackPromotionKnight:
4342       case WhitePromotionKing:
4343       case BlackPromotionKing:
4344       case NormalMove:
4345       case WhiteCapturesEnPassant:
4346       case BlackCapturesEnPassant:
4347       case WhiteKingSideCastle:
4348       case WhiteQueenSideCastle:
4349       case BlackKingSideCastle:
4350       case BlackQueenSideCastle:
4351       case WhiteKingSideCastleWild:
4352       case WhiteQueenSideCastleWild:
4353       case BlackKingSideCastleWild:
4354       case BlackQueenSideCastleWild:
4355       /* Code added by Tord: */
4356       case WhiteHSideCastleFR:
4357       case WhiteASideCastleFR:
4358       case BlackHSideCastleFR:
4359       case BlackASideCastleFR:
4360       /* End of code added by Tord */
4361       case IllegalMove:         /* bug or odd chess variant */
4362         *fromX = currentMoveString[0] - AAA;
4363         *fromY = currentMoveString[1] - ONE;
4364         *toX = currentMoveString[2] - AAA;
4365         *toY = currentMoveString[3] - ONE;
4366         *promoChar = currentMoveString[4];
4367         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4368             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4369     if (appData.debugMode) {
4370         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4371     }
4372             *fromX = *fromY = *toX = *toY = 0;
4373             return FALSE;
4374         }
4375         if (appData.testLegality) {
4376           return (*moveType != IllegalMove);
4377         } else {
4378           return !(*fromX == *toX && *fromY == *toY);
4379         }
4380
4381       case WhiteDrop:
4382       case BlackDrop:
4383         *fromX = *moveType == WhiteDrop ?
4384           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4385           (int) CharToPiece(ToLower(currentMoveString[0]));
4386         *fromY = DROP_RANK;
4387         *toX = currentMoveString[2] - AAA;
4388         *toY = currentMoveString[3] - ONE;
4389         *promoChar = NULLCHAR;
4390         return TRUE;
4391
4392       case AmbiguousMove:
4393       case ImpossibleMove:
4394       case (ChessMove) 0:       /* end of file */
4395       case ElapsedTime:
4396       case Comment:
4397       case PGNTag:
4398       case NAG:
4399       case WhiteWins:
4400       case BlackWins:
4401       case GameIsDrawn:
4402       default:
4403     if (appData.debugMode) {
4404         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4405     }
4406         /* bug? */
4407         *fromX = *fromY = *toX = *toY = 0;
4408         *promoChar = NULLCHAR;
4409         return FALSE;
4410     }
4411 }
4412
4413 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4414 // All positions will have equal probability, but the current method will not provide a unique
4415 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4416 #define DARK 1
4417 #define LITE 2
4418 #define ANY 3
4419
4420 int squaresLeft[4];
4421 int piecesLeft[(int)BlackPawn];
4422 int seed, nrOfShuffles;
4423
4424 void GetPositionNumber()
4425 {       // sets global variable seed
4426         int i;
4427
4428         seed = appData.defaultFrcPosition;
4429         if(seed < 0) { // randomize based on time for negative FRC position numbers
4430                 for(i=0; i<50; i++) seed += random();
4431                 seed = random() ^ random() >> 8 ^ random() << 8;
4432                 if(seed<0) seed = -seed;
4433         }
4434 }
4435
4436 int put(Board board, int pieceType, int rank, int n, int shade)
4437 // put the piece on the (n-1)-th empty squares of the given shade
4438 {
4439         int i;
4440
4441         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4442                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4443                         board[rank][i] = (ChessSquare) pieceType;
4444                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4445                         squaresLeft[ANY]--;
4446                         piecesLeft[pieceType]--; 
4447                         return i;
4448                 }
4449         }
4450         return -1;
4451 }
4452
4453
4454 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4455 // calculate where the next piece goes, (any empty square), and put it there
4456 {
4457         int i;
4458
4459         i = seed % squaresLeft[shade];
4460         nrOfShuffles *= squaresLeft[shade];
4461         seed /= squaresLeft[shade];
4462         put(board, pieceType, rank, i, shade);
4463 }
4464
4465 void AddTwoPieces(Board board, int pieceType, int rank)
4466 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4467 {
4468         int i, n=squaresLeft[ANY], j=n-1, k;
4469
4470         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4471         i = seed % k;  // pick one
4472         nrOfShuffles *= k;
4473         seed /= k;
4474         while(i >= j) i -= j--;
4475         j = n - 1 - j; i += j;
4476         put(board, pieceType, rank, j, ANY);
4477         put(board, pieceType, rank, i, ANY);
4478 }
4479
4480 void SetUpShuffle(Board board, int number)
4481 {
4482         int i, p, first=1;
4483
4484         GetPositionNumber(); nrOfShuffles = 1;
4485
4486         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4487         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4488         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4489
4490         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4491
4492         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4493             p = (int) board[0][i];
4494             if(p < (int) BlackPawn) piecesLeft[p] ++;
4495             board[0][i] = EmptySquare;
4496         }
4497
4498         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4499             // shuffles restricted to allow normal castling put KRR first
4500             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4501                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4502             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4503                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4504             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4505                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4506             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4507                 put(board, WhiteRook, 0, 0, ANY);
4508             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4509         }
4510
4511         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4512             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4513             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4514                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4515                 while(piecesLeft[p] >= 2) {
4516                     AddOnePiece(board, p, 0, LITE);
4517                     AddOnePiece(board, p, 0, DARK);
4518                 }
4519                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4520             }
4521
4522         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4523             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4524             // but we leave King and Rooks for last, to possibly obey FRC restriction
4525             if(p == (int)WhiteRook) continue;
4526             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4527             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4528         }
4529
4530         // now everything is placed, except perhaps King (Unicorn) and Rooks
4531
4532         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4533             // Last King gets castling rights
4534             while(piecesLeft[(int)WhiteUnicorn]) {
4535                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4536                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4537             }
4538
4539             while(piecesLeft[(int)WhiteKing]) {
4540                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4541                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4542             }
4543
4544
4545         } else {
4546             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4547             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4548         }
4549
4550         // Only Rooks can be left; simply place them all
4551         while(piecesLeft[(int)WhiteRook]) {
4552                 i = put(board, WhiteRook, 0, 0, ANY);
4553                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4554                         if(first) {
4555                                 first=0;
4556                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4557                         }
4558                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4559                 }
4560         }
4561         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4562             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4563         }
4564
4565         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4566 }
4567
4568 int SetCharTable( char *table, const char * map )
4569 /* [HGM] moved here from winboard.c because of its general usefulness */
4570 /*       Basically a safe strcpy that uses the last character as King */
4571 {
4572     int result = FALSE; int NrPieces;
4573
4574     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4575                     && NrPieces >= 12 && !(NrPieces&1)) {
4576         int i; /* [HGM] Accept even length from 12 to 34 */
4577
4578         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4579         for( i=0; i<NrPieces/2-1; i++ ) {
4580             table[i] = map[i];
4581             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4582         }
4583         table[(int) WhiteKing]  = map[NrPieces/2-1];
4584         table[(int) BlackKing]  = map[NrPieces-1];
4585
4586         result = TRUE;
4587     }
4588
4589     return result;
4590 }
4591
4592 void Prelude(Board board)
4593 {       // [HGM] superchess: random selection of exo-pieces
4594         int i, j, k; ChessSquare p; 
4595         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4596
4597         GetPositionNumber(); // use FRC position number
4598
4599         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4600             SetCharTable(pieceToChar, appData.pieceToCharTable);
4601             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4602                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4603         }
4604
4605         j = seed%4;                 seed /= 4; 
4606         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4607         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4608         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4609         j = seed%3 + (seed%3 >= j); seed /= 3; 
4610         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4611         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4612         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4613         j = seed%3;                 seed /= 3; 
4614         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4615         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4616         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4617         j = seed%2 + (seed%2 >= j); seed /= 2; 
4618         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4619         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4620         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4621         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4622         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4623         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4624         put(board, exoPieces[0],    0, 0, ANY);
4625         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4626 }
4627
4628 void
4629 InitPosition(redraw)
4630      int redraw;
4631 {
4632     ChessSquare (* pieces)[BOARD_SIZE];
4633     int i, j, pawnRow, overrule,
4634     oldx = gameInfo.boardWidth,
4635     oldy = gameInfo.boardHeight,
4636     oldh = gameInfo.holdingsWidth,
4637     oldv = gameInfo.variant;
4638
4639     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4640
4641     /* [AS] Initialize pv info list [HGM] and game status */
4642     {
4643         for( i=0; i<MAX_MOVES; i++ ) {
4644             pvInfoList[i].depth = 0;
4645             epStatus[i]=EP_NONE;
4646             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4647         }
4648
4649         initialRulePlies = 0; /* 50-move counter start */
4650
4651         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4652         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4653     }
4654
4655     
4656     /* [HGM] logic here is completely changed. In stead of full positions */
4657     /* the initialized data only consist of the two backranks. The switch */
4658     /* selects which one we will use, which is than copied to the Board   */
4659     /* initialPosition, which for the rest is initialized by Pawns and    */
4660     /* empty squares. This initial position is then copied to boards[0],  */
4661     /* possibly after shuffling, so that it remains available.            */
4662
4663     gameInfo.holdingsWidth = 0; /* default board sizes */
4664     gameInfo.boardWidth    = 8;
4665     gameInfo.boardHeight   = 8;
4666     gameInfo.holdingsSize  = 0;
4667     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4668     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4669     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4670
4671     switch (gameInfo.variant) {
4672     case VariantFischeRandom:
4673       shuffleOpenings = TRUE;
4674     default:
4675       pieces = FIDEArray;
4676       break;
4677     case VariantShatranj:
4678       pieces = ShatranjArray;
4679       nrCastlingRights = 0;
4680       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4681       break;
4682     case VariantTwoKings:
4683       pieces = twoKingsArray;
4684       break;
4685     case VariantCapaRandom:
4686       shuffleOpenings = TRUE;
4687     case VariantCapablanca:
4688       pieces = CapablancaArray;
4689       gameInfo.boardWidth = 10;
4690       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4691       break;
4692     case VariantGothic:
4693       pieces = GothicArray;
4694       gameInfo.boardWidth = 10;
4695       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4696       break;
4697     case VariantJanus:
4698       pieces = JanusArray;
4699       gameInfo.boardWidth = 10;
4700       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4701       nrCastlingRights = 6;
4702         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4703         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4704         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4705         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4706         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4707         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4708       break;
4709     case VariantFalcon:
4710       pieces = FalconArray;
4711       gameInfo.boardWidth = 10;
4712       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4713       break;
4714     case VariantXiangqi:
4715       pieces = XiangqiArray;
4716       gameInfo.boardWidth  = 9;
4717       gameInfo.boardHeight = 10;
4718       nrCastlingRights = 0;
4719       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4720       break;
4721     case VariantShogi:
4722       pieces = ShogiArray;
4723       gameInfo.boardWidth  = 9;
4724       gameInfo.boardHeight = 9;
4725       gameInfo.holdingsSize = 7;
4726       nrCastlingRights = 0;
4727       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4728       break;
4729     case VariantCourier:
4730       pieces = CourierArray;
4731       gameInfo.boardWidth  = 12;
4732       nrCastlingRights = 0;
4733       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4734       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4735       break;
4736     case VariantKnightmate:
4737       pieces = KnightmateArray;
4738       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4739       break;
4740     case VariantFairy:
4741       pieces = fairyArray;
4742       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4743       break;
4744     case VariantGreat:
4745       pieces = GreatArray;
4746       gameInfo.boardWidth = 10;
4747       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4748       gameInfo.holdingsSize = 8;
4749       break;
4750     case VariantSuper:
4751       pieces = FIDEArray;
4752       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4753       gameInfo.holdingsSize = 8;
4754       startedFromSetupPosition = TRUE;
4755       break;
4756     case VariantCrazyhouse:
4757     case VariantBughouse:
4758       pieces = FIDEArray;
4759       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4760       gameInfo.holdingsSize = 5;
4761       break;
4762     case VariantWildCastle:
4763       pieces = FIDEArray;
4764       /* !!?shuffle with kings guaranteed to be on d or e file */
4765       shuffleOpenings = 1;
4766       break;
4767     case VariantNoCastle:
4768       pieces = FIDEArray;
4769       nrCastlingRights = 0;
4770       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4771       /* !!?unconstrained back-rank shuffle */
4772       shuffleOpenings = 1;
4773       break;
4774     }
4775
4776     overrule = 0;
4777     if(appData.NrFiles >= 0) {
4778         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4779         gameInfo.boardWidth = appData.NrFiles;
4780     }
4781     if(appData.NrRanks >= 0) {
4782         gameInfo.boardHeight = appData.NrRanks;
4783     }
4784     if(appData.holdingsSize >= 0) {
4785         i = appData.holdingsSize;
4786         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4787         gameInfo.holdingsSize = i;
4788     }
4789     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4790     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4791         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4792
4793     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4794     if(pawnRow < 1) pawnRow = 1;
4795
4796     /* User pieceToChar list overrules defaults */
4797     if(appData.pieceToCharTable != NULL)
4798         SetCharTable(pieceToChar, appData.pieceToCharTable);
4799
4800     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4801
4802         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4803             s = (ChessSquare) 0; /* account holding counts in guard band */
4804         for( i=0; i<BOARD_HEIGHT; i++ )
4805             initialPosition[i][j] = s;
4806
4807         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4808         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4809         initialPosition[pawnRow][j] = WhitePawn;
4810         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4811         if(gameInfo.variant == VariantXiangqi) {
4812             if(j&1) {
4813                 initialPosition[pawnRow][j] = 
4814                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4815                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4816                    initialPosition[2][j] = WhiteCannon;
4817                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4818                 }
4819             }
4820         }
4821         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4822     }
4823     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4824
4825             j=BOARD_LEFT+1;
4826             initialPosition[1][j] = WhiteBishop;
4827             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4828             j=BOARD_RGHT-2;
4829             initialPosition[1][j] = WhiteRook;
4830             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4831     }
4832
4833     if( nrCastlingRights == -1) {
4834         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4835         /*       This sets default castling rights from none to normal corners   */
4836         /* Variants with other castling rights must set them themselves above    */
4837         nrCastlingRights = 6;
4838        
4839         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4840         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4841         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4842         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4843         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4844         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4845      }
4846
4847      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4848      if(gameInfo.variant == VariantGreat) { // promotion commoners
4849         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4850         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4851         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4852         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4853      }
4854   if (appData.debugMode) {
4855     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4856   }
4857     if(shuffleOpenings) {
4858         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4859         startedFromSetupPosition = TRUE;
4860     }
4861     if(startedFromPositionFile) {
4862       /* [HGM] loadPos: use PositionFile for every new game */
4863       CopyBoard(initialPosition, filePosition);
4864       for(i=0; i<nrCastlingRights; i++)
4865           castlingRights[0][i] = initialRights[i] = fileRights[i];
4866       startedFromSetupPosition = TRUE;
4867     }
4868
4869     CopyBoard(boards[0], initialPosition);
4870
4871     if(oldx != gameInfo.boardWidth ||
4872        oldy != gameInfo.boardHeight ||
4873        oldh != gameInfo.holdingsWidth
4874 #ifdef GOTHIC
4875        || oldv == VariantGothic ||        // For licensing popups
4876        gameInfo.variant == VariantGothic
4877 #endif
4878 #ifdef FALCON
4879        || oldv == VariantFalcon ||
4880        gameInfo.variant == VariantFalcon
4881 #endif
4882                                          )
4883             InitDrawingSizes(-2 ,0);
4884
4885     if (redraw)
4886       DrawPosition(TRUE, boards[currentMove]);
4887 }
4888
4889 void
4890 SendBoard(cps, moveNum)
4891      ChessProgramState *cps;
4892      int moveNum;
4893 {
4894     char message[MSG_SIZ];
4895     
4896     if (cps->useSetboard) {
4897       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4898       sprintf(message, "setboard %s\n", fen);
4899       SendToProgram(message, cps);
4900       free(fen);
4901
4902     } else {
4903       ChessSquare *bp;
4904       int i, j;
4905       /* Kludge to set black to move, avoiding the troublesome and now
4906        * deprecated "black" command.
4907        */
4908       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4909
4910       SendToProgram("edit\n", cps);
4911       SendToProgram("#\n", cps);
4912       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4913         bp = &boards[moveNum][i][BOARD_LEFT];
4914         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4915           if ((int) *bp < (int) BlackPawn) {
4916             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4917                     AAA + j, ONE + i);
4918             if(message[0] == '+' || message[0] == '~') {
4919                 sprintf(message, "%c%c%c+\n",
4920                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4921                         AAA + j, ONE + i);
4922             }
4923             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4924                 message[1] = BOARD_RGHT   - 1 - j + '1';
4925                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4926             }
4927             SendToProgram(message, cps);
4928           }
4929         }
4930       }
4931     
4932       SendToProgram("c\n", cps);
4933       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4934         bp = &boards[moveNum][i][BOARD_LEFT];
4935         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4936           if (((int) *bp != (int) EmptySquare)
4937               && ((int) *bp >= (int) BlackPawn)) {
4938             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4939                     AAA + j, ONE + i);
4940             if(message[0] == '+' || message[0] == '~') {
4941                 sprintf(message, "%c%c%c+\n",
4942                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4943                         AAA + j, ONE + i);
4944             }
4945             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4946                 message[1] = BOARD_RGHT   - 1 - j + '1';
4947                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4948             }
4949             SendToProgram(message, cps);
4950           }
4951         }
4952       }
4953     
4954       SendToProgram(".\n", cps);
4955     }
4956     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4957 }
4958
4959 int
4960 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4961 {
4962     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4963     /* [HGM] add Shogi promotions */
4964     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4965     ChessSquare piece;
4966     ChessMove moveType;
4967     Boolean premove;
4968
4969     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4970     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4971
4972     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4973       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4974         return FALSE;
4975
4976     piece = boards[currentMove][fromY][fromX];
4977     if(gameInfo.variant == VariantShogi) {
4978         promotionZoneSize = 3;
4979         highestPromotingPiece = (int)WhiteFerz;
4980     }
4981
4982     // next weed out all moves that do not touch the promotion zone at all
4983     if((int)piece >= BlackPawn) {
4984         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4985              return FALSE;
4986         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4987     } else {
4988         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4989            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4990     }
4991
4992     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4993
4994     // weed out mandatory Shogi promotions
4995     if(gameInfo.variant == VariantShogi) {
4996         if(piece >= BlackPawn) {
4997             if(toY == 0 && piece == BlackPawn ||
4998                toY == 0 && piece == BlackQueen ||
4999                toY <= 1 && piece == BlackKnight) {
5000                 *promoChoice = '+';
5001                 return FALSE;
5002             }
5003         } else {
5004             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5005                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5006                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5007                 *promoChoice = '+';
5008                 return FALSE;
5009             }
5010         }
5011     }
5012
5013     // weed out obviously illegal Pawn moves
5014     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5015         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5016         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5017         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5018         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5019         // note we are not allowed to test for valid (non-)capture, due to premove
5020     }
5021
5022     // we either have a choice what to promote to, or (in Shogi) whether to promote
5023     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5024         *promoChoice = PieceToChar(BlackFerz);  // no choice
5025         return FALSE;
5026     }
5027     if(appData.alwaysPromoteToQueen) { // predetermined
5028         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5029              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5030         else *promoChoice = PieceToChar(BlackQueen);
5031         return FALSE;
5032     }
5033
5034     // suppress promotion popup on illegal moves that are not premoves
5035     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5036               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5037     if(appData.testLegality && !premove) {
5038         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5039                         epStatus[currentMove], castlingRights[currentMove],
5040                         fromY, fromX, toY, toX, NULLCHAR);
5041         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5042            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5043             return FALSE;
5044     }
5045
5046     return TRUE;
5047 }
5048
5049 int
5050 InPalace(row, column)
5051      int row, column;
5052 {   /* [HGM] for Xiangqi */
5053     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5054          column < (BOARD_WIDTH + 4)/2 &&
5055          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5056     return FALSE;
5057 }
5058
5059 int
5060 PieceForSquare (x, y)
5061      int x;
5062      int y;
5063 {
5064   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5065      return -1;
5066   else
5067      return boards[currentMove][y][x];
5068 }
5069
5070 int
5071 OKToStartUserMove(x, y)
5072      int x, y;
5073 {
5074     ChessSquare from_piece;
5075     int white_piece;
5076
5077     if (matchMode) return FALSE;
5078     if (gameMode == EditPosition) return TRUE;
5079
5080     if (x >= 0 && y >= 0)
5081       from_piece = boards[currentMove][y][x];
5082     else
5083       from_piece = EmptySquare;
5084
5085     if (from_piece == EmptySquare) return FALSE;
5086
5087     white_piece = (int)from_piece >= (int)WhitePawn &&
5088       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5089
5090     switch (gameMode) {
5091       case PlayFromGameFile:
5092       case AnalyzeFile:
5093       case TwoMachinesPlay:
5094       case EndOfGame:
5095         return FALSE;
5096
5097       case IcsObserving:
5098       case IcsIdle:
5099         return FALSE;
5100
5101       case MachinePlaysWhite:
5102       case IcsPlayingBlack:
5103         if (appData.zippyPlay) return FALSE;
5104         if (white_piece) {
5105             DisplayMoveError(_("You are playing Black"));
5106             return FALSE;
5107         }
5108         break;
5109
5110       case MachinePlaysBlack:
5111       case IcsPlayingWhite:
5112         if (appData.zippyPlay) return FALSE;
5113         if (!white_piece) {
5114             DisplayMoveError(_("You are playing White"));
5115             return FALSE;
5116         }
5117         break;
5118
5119       case EditGame:
5120         if (!white_piece && WhiteOnMove(currentMove)) {
5121             DisplayMoveError(_("It is White's turn"));
5122             return FALSE;
5123         }           
5124         if (white_piece && !WhiteOnMove(currentMove)) {
5125             DisplayMoveError(_("It is Black's turn"));
5126             return FALSE;
5127         }           
5128         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5129             /* Editing correspondence game history */
5130             /* Could disallow this or prompt for confirmation */
5131             cmailOldMove = -1;
5132         }
5133         if (currentMove < forwardMostMove) {
5134             /* Discarding moves */
5135             /* Could prompt for confirmation here,
5136                but I don't think that's such a good idea */
5137             forwardMostMove = currentMove;
5138         }
5139         break;
5140
5141       case BeginningOfGame:
5142         if (appData.icsActive) return FALSE;
5143         if (!appData.noChessProgram) {
5144             if (!white_piece) {
5145                 DisplayMoveError(_("You are playing White"));
5146                 return FALSE;
5147             }
5148         }
5149         break;
5150         
5151       case Training:
5152         if (!white_piece && WhiteOnMove(currentMove)) {
5153             DisplayMoveError(_("It is White's turn"));
5154             return FALSE;
5155         }           
5156         if (white_piece && !WhiteOnMove(currentMove)) {
5157             DisplayMoveError(_("It is Black's turn"));
5158             return FALSE;
5159         }           
5160         break;
5161
5162       default:
5163       case IcsExamining:
5164         break;
5165     }
5166     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5167         && gameMode != AnalyzeFile && gameMode != Training) {
5168         DisplayMoveError(_("Displayed position is not current"));
5169         return FALSE;
5170     }
5171     return TRUE;
5172 }
5173
5174 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5175 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5176 int lastLoadGameUseList = FALSE;
5177 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5178 ChessMove lastLoadGameStart = (ChessMove) 0;
5179
5180 ChessMove
5181 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5182      int fromX, fromY, toX, toY;
5183      int promoChar;
5184      Boolean captureOwn;
5185 {
5186     ChessMove moveType;
5187     ChessSquare pdown, pup;
5188
5189     /* Check if the user is playing in turn.  This is complicated because we
5190        let the user "pick up" a piece before it is his turn.  So the piece he
5191        tried to pick up may have been captured by the time he puts it down!
5192        Therefore we use the color the user is supposed to be playing in this
5193        test, not the color of the piece that is currently on the starting
5194        square---except in EditGame mode, where the user is playing both
5195        sides; fortunately there the capture race can't happen.  (It can
5196        now happen in IcsExamining mode, but that's just too bad.  The user
5197        will get a somewhat confusing message in that case.)
5198        */
5199
5200     switch (gameMode) {
5201       case PlayFromGameFile:
5202       case AnalyzeFile:
5203       case TwoMachinesPlay:
5204       case EndOfGame:
5205       case IcsObserving:
5206       case IcsIdle:
5207         /* We switched into a game mode where moves are not accepted,
5208            perhaps while the mouse button was down. */
5209         return ImpossibleMove;
5210
5211       case MachinePlaysWhite:
5212         /* User is moving for Black */
5213         if (WhiteOnMove(currentMove)) {
5214             DisplayMoveError(_("It is White's turn"));
5215             return ImpossibleMove;
5216         }
5217         break;
5218
5219       case MachinePlaysBlack:
5220         /* User is moving for White */
5221         if (!WhiteOnMove(currentMove)) {
5222             DisplayMoveError(_("It is Black's turn"));
5223             return ImpossibleMove;
5224         }
5225         break;
5226
5227       case EditGame:
5228       case IcsExamining:
5229       case BeginningOfGame:
5230       case AnalyzeMode:
5231       case Training:
5232         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5233             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5234             /* User is moving for Black */
5235             if (WhiteOnMove(currentMove)) {
5236                 DisplayMoveError(_("It is White's turn"));
5237                 return ImpossibleMove;
5238             }
5239         } else {
5240             /* User is moving for White */
5241             if (!WhiteOnMove(currentMove)) {
5242                 DisplayMoveError(_("It is Black's turn"));
5243                 return ImpossibleMove;
5244             }
5245         }
5246         break;
5247
5248       case IcsPlayingBlack:
5249         /* User is moving for Black */
5250         if (WhiteOnMove(currentMove)) {
5251             if (!appData.premove) {
5252                 DisplayMoveError(_("It is White's turn"));
5253             } else if (toX >= 0 && toY >= 0) {
5254                 premoveToX = toX;
5255                 premoveToY = toY;
5256                 premoveFromX = fromX;
5257                 premoveFromY = fromY;
5258                 premovePromoChar = promoChar;
5259                 gotPremove = 1;
5260                 if (appData.debugMode) 
5261                     fprintf(debugFP, "Got premove: fromX %d,"
5262                             "fromY %d, toX %d, toY %d\n",
5263                             fromX, fromY, toX, toY);
5264             }
5265             return ImpossibleMove;
5266         }
5267         break;
5268
5269       case IcsPlayingWhite:
5270         /* User is moving for White */
5271         if (!WhiteOnMove(currentMove)) {
5272             if (!appData.premove) {
5273                 DisplayMoveError(_("It is Black's turn"));
5274             } else if (toX >= 0 && toY >= 0) {
5275                 premoveToX = toX;
5276                 premoveToY = toY;
5277                 premoveFromX = fromX;
5278                 premoveFromY = fromY;
5279                 premovePromoChar = promoChar;
5280                 gotPremove = 1;
5281                 if (appData.debugMode) 
5282                     fprintf(debugFP, "Got premove: fromX %d,"
5283                             "fromY %d, toX %d, toY %d\n",
5284                             fromX, fromY, toX, toY);
5285             }
5286             return ImpossibleMove;
5287         }
5288         break;
5289
5290       default:
5291         break;
5292
5293       case EditPosition:
5294         /* EditPosition, empty square, or different color piece;
5295            click-click move is possible */
5296         if (toX == -2 || toY == -2) {
5297             boards[0][fromY][fromX] = EmptySquare;
5298             return AmbiguousMove;
5299         } else if (toX >= 0 && toY >= 0) {
5300             boards[0][toY][toX] = boards[0][fromY][fromX];
5301             boards[0][fromY][fromX] = EmptySquare;
5302             return AmbiguousMove;
5303         }
5304         return ImpossibleMove;
5305     }
5306
5307     if(toX < 0 || toY < 0) return ImpossibleMove;
5308     pdown = boards[currentMove][fromY][fromX];
5309     pup = boards[currentMove][toY][toX];
5310
5311     /* [HGM] If move started in holdings, it means a drop */
5312     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5313          if( pup != EmptySquare ) return ImpossibleMove;
5314          if(appData.testLegality) {
5315              /* it would be more logical if LegalityTest() also figured out
5316               * which drops are legal. For now we forbid pawns on back rank.
5317               * Shogi is on its own here...
5318               */
5319              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5320                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5321                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5322          }
5323          return WhiteDrop; /* Not needed to specify white or black yet */
5324     }
5325
5326     userOfferedDraw = FALSE;
5327         
5328     /* [HGM] always test for legality, to get promotion info */
5329     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5330                           epStatus[currentMove], castlingRights[currentMove],
5331                                          fromY, fromX, toY, toX, promoChar);
5332     /* [HGM] but possibly ignore an IllegalMove result */
5333     if (appData.testLegality) {
5334         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5335             DisplayMoveError(_("Illegal move"));
5336             return ImpossibleMove;
5337         }
5338     }
5339
5340     return moveType;
5341     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5342        function is made into one that returns an OK move type if FinishMove
5343        should be called. This to give the calling driver routine the
5344        opportunity to finish the userMove input with a promotion popup,
5345        without bothering the user with this for invalid or illegal moves */
5346
5347 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5348 }
5349
5350 /* Common tail of UserMoveEvent and DropMenuEvent */
5351 int
5352 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5353      ChessMove moveType;
5354      int fromX, fromY, toX, toY;
5355      /*char*/int promoChar;
5356 {
5357     char *bookHit = 0;
5358
5359     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5360         // [HGM] superchess: suppress promotions to non-available piece
5361         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5362         if(WhiteOnMove(currentMove)) {
5363             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5364         } else {
5365             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5366         }
5367     }
5368
5369     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5370        move type in caller when we know the move is a legal promotion */
5371     if(moveType == NormalMove && promoChar)
5372         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5373
5374     /* [HGM] convert drag-and-drop piece drops to standard form */
5375     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5376          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5377            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5378                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5379            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5380            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5381            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5382            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5383          fromY = DROP_RANK;
5384     }
5385
5386     /* [HGM] <popupFix> The following if has been moved here from
5387        UserMoveEvent(). Because it seemed to belong here (why not allow
5388        piece drops in training games?), and because it can only be
5389        performed after it is known to what we promote. */
5390     if (gameMode == Training) {
5391       /* compare the move played on the board to the next move in the
5392        * game. If they match, display the move and the opponent's response. 
5393        * If they don't match, display an error message.
5394        */
5395       int saveAnimate;
5396       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5397       CopyBoard(testBoard, boards[currentMove]);
5398       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5399
5400       if (CompareBoards(testBoard, boards[currentMove+1])) {
5401         ForwardInner(currentMove+1);
5402
5403         /* Autoplay the opponent's response.
5404          * if appData.animate was TRUE when Training mode was entered,
5405          * the response will be animated.
5406          */
5407         saveAnimate = appData.animate;
5408         appData.animate = animateTraining;
5409         ForwardInner(currentMove+1);
5410         appData.animate = saveAnimate;
5411
5412         /* check for the end of the game */
5413         if (currentMove >= forwardMostMove) {
5414           gameMode = PlayFromGameFile;
5415           ModeHighlight();
5416           SetTrainingModeOff();
5417           DisplayInformation(_("End of game"));
5418         }
5419       } else {
5420         DisplayError(_("Incorrect move"), 0);
5421       }
5422       return 1;
5423     }
5424
5425   /* Ok, now we know that the move is good, so we can kill
5426      the previous line in Analysis Mode */
5427   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5428     forwardMostMove = currentMove;
5429   }
5430
5431   /* If we need the chess program but it's dead, restart it */
5432   ResurrectChessProgram();
5433
5434   /* A user move restarts a paused game*/
5435   if (pausing)
5436     PauseEvent();
5437
5438   thinkOutput[0] = NULLCHAR;
5439
5440   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5441
5442   if (gameMode == BeginningOfGame) {
5443     if (appData.noChessProgram) {
5444       gameMode = EditGame;
5445       SetGameInfo();
5446     } else {
5447       char buf[MSG_SIZ];
5448       gameMode = MachinePlaysBlack;
5449       StartClocks();
5450       SetGameInfo();
5451       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5452       DisplayTitle(buf);
5453       if (first.sendName) {
5454         sprintf(buf, "name %s\n", gameInfo.white);
5455         SendToProgram(buf, &first);
5456       }
5457       StartClocks();
5458     }
5459     ModeHighlight();
5460   }
5461
5462   /* Relay move to ICS or chess engine */
5463   if (appData.icsActive) {
5464     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5465         gameMode == IcsExamining) {
5466       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5467       ics_user_moved = 1;
5468     }
5469   } else {
5470     if (first.sendTime && (gameMode == BeginningOfGame ||
5471                            gameMode == MachinePlaysWhite ||
5472                            gameMode == MachinePlaysBlack)) {
5473       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5474     }
5475     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5476          // [HGM] book: if program might be playing, let it use book
5477         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5478         first.maybeThinking = TRUE;
5479     } else SendMoveToProgram(forwardMostMove-1, &first);
5480     if (currentMove == cmailOldMove + 1) {
5481       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5482     }
5483   }
5484
5485   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5486
5487   switch (gameMode) {
5488   case EditGame:
5489     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5490                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5491     case MT_NONE:
5492     case MT_CHECK:
5493       break;
5494     case MT_CHECKMATE:
5495     case MT_STAINMATE:
5496       if (WhiteOnMove(currentMove)) {
5497         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5498       } else {
5499         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5500       }
5501       break;
5502     case MT_STALEMATE:
5503       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5504       break;
5505     }
5506     break;
5507     
5508   case MachinePlaysBlack:
5509   case MachinePlaysWhite:
5510     /* disable certain menu options while machine is thinking */
5511     SetMachineThinkingEnables();
5512     break;
5513
5514   default:
5515     break;
5516   }
5517
5518   if(bookHit) { // [HGM] book: simulate book reply
5519         static char bookMove[MSG_SIZ]; // a bit generous?
5520
5521         programStats.nodes = programStats.depth = programStats.time = 
5522         programStats.score = programStats.got_only_move = 0;
5523         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5524
5525         strcpy(bookMove, "move ");
5526         strcat(bookMove, bookHit);
5527         HandleMachineMove(bookMove, &first);
5528   }
5529   return 1;
5530 }
5531
5532 void
5533 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5534      int fromX, fromY, toX, toY;
5535      int promoChar;
5536 {
5537     /* [HGM] This routine was added to allow calling of its two logical
5538        parts from other modules in the old way. Before, UserMoveEvent()
5539        automatically called FinishMove() if the move was OK, and returned
5540        otherwise. I separated the two, in order to make it possible to
5541        slip a promotion popup in between. But that it always needs two
5542        calls, to the first part, (now called UserMoveTest() ), and to
5543        FinishMove if the first part succeeded. Calls that do not need
5544        to do anything in between, can call this routine the old way. 
5545     */
5546     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5547 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5548     if(moveType == AmbiguousMove)
5549         DrawPosition(FALSE, boards[currentMove]);
5550     else if(moveType != ImpossibleMove && moveType != Comment)
5551         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5552 }
5553
5554 void LeftClick(ClickType clickType, int xPix, int yPix)
5555 {
5556     int x, y;
5557     Boolean saveAnimate;
5558     static int second = 0, promotionChoice = 0;
5559     char promoChoice = NULLCHAR;
5560
5561     if (clickType == Press) ErrorPopDown();
5562
5563     x = EventToSquare(xPix, BOARD_WIDTH);
5564     y = EventToSquare(yPix, BOARD_HEIGHT);
5565     if (!flipView && y >= 0) {
5566         y = BOARD_HEIGHT - 1 - y;
5567     }
5568     if (flipView && x >= 0) {
5569         x = BOARD_WIDTH - 1 - x;
5570     }
5571
5572     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5573         if(clickType == Release) return; // ignore upclick of click-click destination
5574         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5575         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5576         if(gameInfo.holdingsWidth && 
5577                 (WhiteOnMove(currentMove) 
5578                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5579                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5580             // click in right holdings, for determining promotion piece
5581             ChessSquare p = boards[currentMove][y][x];
5582             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5583             if(p != EmptySquare) {
5584                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5585                 fromX = fromY = -1;
5586                 return;
5587             }
5588         }
5589         DrawPosition(FALSE, boards[currentMove]);
5590         return;
5591     }
5592
5593     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5594     if(clickType == Press
5595             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5596               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5597               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5598         return;
5599
5600     if (fromX == -1) {
5601         if (clickType == Press) {
5602             /* First square */
5603             if (OKToStartUserMove(x, y)) {
5604                 fromX = x;
5605                 fromY = y;
5606                 second = 0;
5607                 DragPieceBegin(xPix, yPix);
5608                 if (appData.highlightDragging) {
5609                     SetHighlights(x, y, -1, -1);
5610                 }
5611             }
5612         }
5613         return;
5614     }
5615
5616     /* fromX != -1 */
5617     if (clickType == Press && gameMode != EditPosition) {
5618         ChessSquare fromP;
5619         ChessSquare toP;
5620         int frc;
5621
5622         // ignore off-board to clicks
5623         if(y < 0 || x < 0) return;
5624
5625         /* Check if clicking again on the same color piece */
5626         fromP = boards[currentMove][fromY][fromX];
5627         toP = boards[currentMove][y][x];
5628         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5629         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5630              WhitePawn <= toP && toP <= WhiteKing &&
5631              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5632              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5633             (BlackPawn <= fromP && fromP <= BlackKing && 
5634              BlackPawn <= toP && toP <= BlackKing &&
5635              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5636              !(fromP == BlackKing && toP == BlackRook && frc))) {
5637             /* Clicked again on same color piece -- changed his mind */
5638             second = (x == fromX && y == fromY);
5639             if (appData.highlightDragging) {
5640                 SetHighlights(x, y, -1, -1);
5641             } else {
5642                 ClearHighlights();
5643             }
5644             if (OKToStartUserMove(x, y)) {
5645                 fromX = x;
5646                 fromY = y;
5647                 DragPieceBegin(xPix, yPix);
5648             }
5649             return;
5650         }
5651         // ignore clicks on holdings
5652         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5653     }
5654
5655     if (clickType == Release && x == fromX && y == fromY) {
5656         DragPieceEnd(xPix, yPix);
5657         if (appData.animateDragging) {
5658             /* Undo animation damage if any */
5659             DrawPosition(FALSE, NULL);
5660         }
5661         if (second) {
5662             /* Second up/down in same square; just abort move */
5663             second = 0;
5664             fromX = fromY = -1;
5665             ClearHighlights();
5666             gotPremove = 0;
5667             ClearPremoveHighlights();
5668         } else {
5669             /* First upclick in same square; start click-click mode */
5670             SetHighlights(x, y, -1, -1);
5671         }
5672         return;
5673     }
5674
5675     /* we now have a different from- and (possibly off-board) to-square */
5676     /* Completed move */
5677     toX = x;
5678     toY = y;
5679     saveAnimate = appData.animate;
5680     if (clickType == Press) {
5681         /* Finish clickclick move */
5682         if (appData.animate || appData.highlightLastMove) {
5683             SetHighlights(fromX, fromY, toX, toY);
5684         } else {
5685             ClearHighlights();
5686         }
5687     } else {
5688         /* Finish drag move */
5689         if (appData.highlightLastMove) {
5690             SetHighlights(fromX, fromY, toX, toY);
5691         } else {
5692             ClearHighlights();
5693         }
5694         DragPieceEnd(xPix, yPix);
5695         /* Don't animate move and drag both */
5696         appData.animate = FALSE;
5697     }
5698
5699     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5700     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5701         ClearHighlights();
5702         fromX = fromY = -1;
5703         DrawPosition(TRUE, NULL);
5704         return;
5705     }
5706
5707     // off-board moves should not be highlighted
5708     if(x < 0 || x < 0) ClearHighlights();
5709
5710     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5711         SetHighlights(fromX, fromY, toX, toY);
5712         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5713             // [HGM] super: promotion to captured piece selected from holdings
5714             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5715             promotionChoice = TRUE;
5716             // kludge follows to temporarily execute move on display, without promoting yet
5717             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5718             boards[currentMove][toY][toX] = p;
5719             DrawPosition(FALSE, boards[currentMove]);
5720             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5721             boards[currentMove][toY][toX] = q;
5722             DisplayMessage("Click in holdings to choose piece", "");
5723             return;
5724         }
5725         PromotionPopUp();
5726     } else {
5727         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5728         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5729         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5730         fromX = fromY = -1;
5731     }
5732     appData.animate = saveAnimate;
5733     if (appData.animate || appData.animateDragging) {
5734         /* Undo animation damage if needed */
5735         DrawPosition(FALSE, NULL);
5736     }
5737 }
5738
5739 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5740 {
5741 //    char * hint = lastHint;
5742     FrontEndProgramStats stats;
5743
5744     stats.which = cps == &first ? 0 : 1;
5745     stats.depth = cpstats->depth;
5746     stats.nodes = cpstats->nodes;
5747     stats.score = cpstats->score;
5748     stats.time = cpstats->time;
5749     stats.pv = cpstats->movelist;
5750     stats.hint = lastHint;
5751     stats.an_move_index = 0;
5752     stats.an_move_count = 0;
5753
5754     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5755         stats.hint = cpstats->move_name;
5756         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5757         stats.an_move_count = cpstats->nr_moves;
5758     }
5759
5760     SetProgramStats( &stats );
5761 }
5762
5763 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5764 {   // [HGM] book: this routine intercepts moves to simulate book replies
5765     char *bookHit = NULL;
5766
5767     //first determine if the incoming move brings opponent into his book
5768     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5769         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5770     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5771     if(bookHit != NULL && !cps->bookSuspend) {
5772         // make sure opponent is not going to reply after receiving move to book position
5773         SendToProgram("force\n", cps);
5774         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5775     }
5776     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5777     // now arrange restart after book miss
5778     if(bookHit) {
5779         // after a book hit we never send 'go', and the code after the call to this routine
5780         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5781         char buf[MSG_SIZ];
5782         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5783         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5784         SendToProgram(buf, cps);
5785         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5786     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5787         SendToProgram("go\n", cps);
5788         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5789     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5790         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5791             SendToProgram("go\n", cps); 
5792         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5793     }
5794     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5795 }
5796
5797 char *savedMessage;
5798 ChessProgramState *savedState;
5799 void DeferredBookMove(void)
5800 {
5801         if(savedState->lastPing != savedState->lastPong)
5802                     ScheduleDelayedEvent(DeferredBookMove, 10);
5803         else
5804         HandleMachineMove(savedMessage, savedState);
5805 }
5806
5807 void
5808 HandleMachineMove(message, cps)
5809      char *message;
5810      ChessProgramState *cps;
5811 {
5812     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5813     char realname[MSG_SIZ];
5814     int fromX, fromY, toX, toY;
5815     ChessMove moveType;
5816     char promoChar;
5817     char *p;
5818     int machineWhite;
5819     char *bookHit;
5820
5821 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5822     /*
5823      * Kludge to ignore BEL characters
5824      */
5825     while (*message == '\007') message++;
5826
5827     /*
5828      * [HGM] engine debug message: ignore lines starting with '#' character
5829      */
5830     if(cps->debug && *message == '#') return;
5831
5832     /*
5833      * Look for book output
5834      */
5835     if (cps == &first && bookRequested) {
5836         if (message[0] == '\t' || message[0] == ' ') {
5837             /* Part of the book output is here; append it */
5838             strcat(bookOutput, message);
5839             strcat(bookOutput, "  \n");
5840             return;
5841         } else if (bookOutput[0] != NULLCHAR) {
5842             /* All of book output has arrived; display it */
5843             char *p = bookOutput;
5844             while (*p != NULLCHAR) {
5845                 if (*p == '\t') *p = ' ';
5846                 p++;
5847             }
5848             DisplayInformation(bookOutput);
5849             bookRequested = FALSE;
5850             /* Fall through to parse the current output */
5851         }
5852     }
5853
5854     /*
5855      * Look for machine move.
5856      */
5857     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5858         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5859     {
5860         /* This method is only useful on engines that support ping */
5861         if (cps->lastPing != cps->lastPong) {
5862           if (gameMode == BeginningOfGame) {
5863             /* Extra move from before last new; ignore */
5864             if (appData.debugMode) {
5865                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5866             }
5867           } else {
5868             if (appData.debugMode) {
5869                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5870                         cps->which, gameMode);
5871             }
5872
5873             SendToProgram("undo\n", cps);
5874           }
5875           return;
5876         }
5877
5878         switch (gameMode) {
5879           case BeginningOfGame:
5880             /* Extra move from before last reset; ignore */
5881             if (appData.debugMode) {
5882                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5883             }
5884             return;
5885
5886           case EndOfGame:
5887           case IcsIdle:
5888           default:
5889             /* Extra move after we tried to stop.  The mode test is
5890                not a reliable way of detecting this problem, but it's
5891                the best we can do on engines that don't support ping.
5892             */
5893             if (appData.debugMode) {
5894                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5895                         cps->which, gameMode);
5896             }
5897             SendToProgram("undo\n", cps);
5898             return;
5899
5900           case MachinePlaysWhite:
5901           case IcsPlayingWhite:
5902             machineWhite = TRUE;
5903             break;
5904
5905           case MachinePlaysBlack:
5906           case IcsPlayingBlack:
5907             machineWhite = FALSE;
5908             break;
5909
5910           case TwoMachinesPlay:
5911             machineWhite = (cps->twoMachinesColor[0] == 'w');
5912             break;
5913         }
5914         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5915             if (appData.debugMode) {
5916                 fprintf(debugFP,
5917                         "Ignoring move out of turn by %s, gameMode %d"
5918                         ", forwardMost %d\n",
5919                         cps->which, gameMode, forwardMostMove);
5920             }
5921             return;
5922         }
5923
5924     if (appData.debugMode) { int f = forwardMostMove;
5925         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5926                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5927     }
5928         if(cps->alphaRank) AlphaRank(machineMove, 4);
5929         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5930                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5931             /* Machine move could not be parsed; ignore it. */
5932             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5933                     machineMove, cps->which);
5934             DisplayError(buf1, 0);
5935             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5936                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5937             if (gameMode == TwoMachinesPlay) {
5938               GameEnds(machineWhite ? BlackWins : WhiteWins,
5939                        buf1, GE_XBOARD);
5940             }
5941             return;
5942         }
5943
5944         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5945         /* So we have to redo legality test with true e.p. status here,  */
5946         /* to make sure an illegal e.p. capture does not slip through,   */
5947         /* to cause a forfeit on a justified illegal-move complaint      */
5948         /* of the opponent.                                              */
5949         if( gameMode==TwoMachinesPlay && appData.testLegality
5950             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5951                                                               ) {
5952            ChessMove moveType;
5953            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5954                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5955                              fromY, fromX, toY, toX, promoChar);
5956             if (appData.debugMode) {
5957                 int i;
5958                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5959                     castlingRights[forwardMostMove][i], castlingRank[i]);
5960                 fprintf(debugFP, "castling rights\n");
5961             }
5962             if(moveType == IllegalMove) {
5963                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5964                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5965                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5966                            buf1, GE_XBOARD);
5967                 return;
5968            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5969            /* [HGM] Kludge to handle engines that send FRC-style castling
5970               when they shouldn't (like TSCP-Gothic) */
5971            switch(moveType) {
5972              case WhiteASideCastleFR:
5973              case BlackASideCastleFR:
5974                toX+=2;
5975                currentMoveString[2]++;
5976                break;
5977              case WhiteHSideCastleFR:
5978              case BlackHSideCastleFR:
5979                toX--;
5980                currentMoveString[2]--;
5981                break;
5982              default: ; // nothing to do, but suppresses warning of pedantic compilers
5983            }
5984         }
5985         hintRequested = FALSE;
5986         lastHint[0] = NULLCHAR;
5987         bookRequested = FALSE;
5988         /* Program may be pondering now */
5989         cps->maybeThinking = TRUE;
5990         if (cps->sendTime == 2) cps->sendTime = 1;
5991         if (cps->offeredDraw) cps->offeredDraw--;
5992
5993 #if ZIPPY
5994         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5995             first.initDone) {
5996           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5997           ics_user_moved = 1;
5998           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5999                 char buf[3*MSG_SIZ];
6000
6001                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6002                         programStats.score / 100.,
6003                         programStats.depth,
6004                         programStats.time / 100.,
6005                         (unsigned int)programStats.nodes,
6006                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6007                         programStats.movelist);
6008                 SendToICS(buf);
6009 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6010           }
6011         }
6012 #endif
6013         /* currentMoveString is set as a side-effect of ParseOneMove */
6014         strcpy(machineMove, currentMoveString);
6015         strcat(machineMove, "\n");
6016         strcpy(moveList[forwardMostMove], machineMove);
6017
6018         /* [AS] Save move info and clear stats for next move */
6019         pvInfoList[ forwardMostMove ].score = programStats.score;
6020         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6021         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6022         ClearProgramStats();
6023         thinkOutput[0] = NULLCHAR;
6024         hiddenThinkOutputState = 0;
6025
6026         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6027
6028         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6029         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6030             int count = 0;
6031
6032             while( count < adjudicateLossPlies ) {
6033                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6034
6035                 if( count & 1 ) {
6036                     score = -score; /* Flip score for winning side */
6037                 }
6038
6039                 if( score > adjudicateLossThreshold ) {
6040                     break;
6041                 }
6042
6043                 count++;
6044             }
6045
6046             if( count >= adjudicateLossPlies ) {
6047                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6048
6049                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6050                     "Xboard adjudication", 
6051                     GE_XBOARD );
6052
6053                 return;
6054             }
6055         }
6056
6057         if( gameMode == TwoMachinesPlay ) {
6058           // [HGM] some adjudications useful with buggy engines
6059             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6060           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6061
6062
6063             if( appData.testLegality )
6064             {   /* [HGM] Some more adjudications for obstinate engines */
6065                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6066                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6067                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6068                 static int moveCount = 6;
6069                 ChessMove result;
6070                 char *reason = NULL;
6071
6072                 /* Count what is on board. */
6073                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6074                 {   ChessSquare p = boards[forwardMostMove][i][j];
6075                     int m=i;
6076
6077                     switch((int) p)
6078                     {   /* count B,N,R and other of each side */
6079                         case WhiteKing:
6080                         case BlackKing:
6081                              NrK++; break; // [HGM] atomic: count Kings
6082                         case WhiteKnight:
6083                              NrWN++; break;
6084                         case WhiteBishop:
6085                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6086                              bishopsColor |= 1 << ((i^j)&1);
6087                              NrWB++; break;
6088                         case BlackKnight:
6089                              NrBN++; break;
6090                         case BlackBishop:
6091                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6092                              bishopsColor |= 1 << ((i^j)&1);
6093                              NrBB++; break;
6094                         case WhiteRook:
6095                              NrWR++; break;
6096                         case BlackRook:
6097                              NrBR++; break;
6098                         case WhiteQueen:
6099                              NrWQ++; break;
6100                         case BlackQueen:
6101                              NrBQ++; break;
6102                         case EmptySquare: 
6103                              break;
6104                         case BlackPawn:
6105                              m = 7-i;
6106                         case WhitePawn:
6107                              PawnAdvance += m; NrPawns++;
6108                     }
6109                     NrPieces += (p != EmptySquare);
6110                     NrW += ((int)p < (int)BlackPawn);
6111                     if(gameInfo.variant == VariantXiangqi && 
6112                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6113                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6114                         NrW -= ((int)p < (int)BlackPawn);
6115                     }
6116                 }
6117
6118                 /* Some material-based adjudications that have to be made before stalemate test */
6119                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6120                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6121                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6122                      if(appData.checkMates) {
6123                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6124                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6125                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6126                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6127                          return;
6128                      }
6129                 }
6130
6131                 /* Bare King in Shatranj (loses) or Losers (wins) */
6132                 if( NrW == 1 || NrPieces - NrW == 1) {
6133                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6134                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6135                      if(appData.checkMates) {
6136                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6137                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6138                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6139                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6140                          return;
6141                      }
6142                   } else
6143                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6144                   {    /* bare King */
6145                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6146                         if(appData.checkMates) {
6147                             /* but only adjudicate if adjudication enabled */
6148                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6149                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6150                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6151                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6152                             return;
6153                         }
6154                   }
6155                 } else bare = 1;
6156
6157
6158             // don't wait for engine to announce game end if we can judge ourselves
6159             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6160                                        castlingRights[forwardMostMove]) ) {
6161               case MT_CHECK:
6162                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6163                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6164                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6165                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6166                             checkCnt++;
6167                         if(checkCnt >= 2) {
6168                             reason = "Xboard adjudication: 3rd check";
6169                             epStatus[forwardMostMove] = EP_CHECKMATE;
6170                             break;
6171                         }
6172                     }
6173                 }
6174               case MT_NONE:
6175               default:
6176                 break;
6177               case MT_STALEMATE:
6178               case MT_STAINMATE:
6179                 reason = "Xboard adjudication: Stalemate";
6180                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6181                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6182                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6183                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6184                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6185                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6186                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6187                                                                         EP_CHECKMATE : EP_WINS);
6188                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6189                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6190                 }
6191                 break;
6192               case MT_CHECKMATE:
6193                 reason = "Xboard adjudication: Checkmate";
6194                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6195                 break;
6196             }
6197
6198                 switch(i = epStatus[forwardMostMove]) {
6199                     case EP_STALEMATE:
6200                         result = GameIsDrawn; break;
6201                     case EP_CHECKMATE:
6202                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6203                     case EP_WINS:
6204                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6205                     default:
6206                         result = (ChessMove) 0;
6207                 }
6208                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6209                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6210                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6211                     GameEnds( result, reason, GE_XBOARD );
6212                     return;
6213                 }
6214
6215                 /* Next absolutely insufficient mating material. */
6216                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6217                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6218                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6219                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6220                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6221
6222                      /* always flag draws, for judging claims */
6223                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6224
6225                      if(appData.materialDraws) {
6226                          /* but only adjudicate them if adjudication enabled */
6227                          SendToProgram("force\n", cps->other); // suppress reply
6228                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6229                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6230                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6231                          return;
6232                      }
6233                 }
6234
6235                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6236                 if(NrPieces == 4 && 
6237                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6238                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6239                    || NrWN==2 || NrBN==2     /* KNNK */
6240                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6241                   ) ) {
6242                      if(--moveCount < 0 && appData.trivialDraws)
6243                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6244                           SendToProgram("force\n", cps->other); // suppress reply
6245                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6246                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6247                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6248                           return;
6249                      }
6250                 } else moveCount = 6;
6251             }
6252           }
6253
6254                 /* Check for rep-draws */
6255                 count = 0;
6256                 for(k = forwardMostMove-2;
6257                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6258                         epStatus[k] < EP_UNKNOWN &&
6259                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6260                     k-=2)
6261                 {   int rights=0;
6262                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6263                         /* compare castling rights */
6264                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6265                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6266                                 rights++; /* King lost rights, while rook still had them */
6267                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6268                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6269                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6270                                    rights++; /* but at least one rook lost them */
6271                         }
6272                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6273                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6274                                 rights++; 
6275                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6276                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6277                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6278                                    rights++;
6279                         }
6280                         if( rights == 0 && ++count > appData.drawRepeats-2
6281                             && appData.drawRepeats > 1) {
6282                              /* adjudicate after user-specified nr of repeats */
6283                              SendToProgram("force\n", cps->other); // suppress reply
6284                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6285                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6286                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6287                                 // [HGM] xiangqi: check for forbidden perpetuals
6288                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6289                                 for(m=forwardMostMove; m>k; m-=2) {
6290                                     if(MateTest(boards[m], PosFlags(m), 
6291                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6292                                         ourPerpetual = 0; // the current mover did not always check
6293                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6294                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6295                                         hisPerpetual = 0; // the opponent did not always check
6296                                 }
6297                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6298                                                                         ourPerpetual, hisPerpetual);
6299                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6300                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6301                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6302                                     return;
6303                                 }
6304                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6305                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6306                                 // Now check for perpetual chases
6307                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6308                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6309                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6310                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6311                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6312                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6313                                         return;
6314                                     }
6315                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6316                                         break; // Abort repetition-checking loop.
6317                                 }
6318                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6319                              }
6320                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6321                              return;
6322                         }
6323                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6324                              epStatus[forwardMostMove] = EP_REP_DRAW;
6325                     }
6326                 }
6327
6328                 /* Now we test for 50-move draws. Determine ply count */
6329                 count = forwardMostMove;
6330                 /* look for last irreversble move */
6331                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6332                     count--;
6333                 /* if we hit starting position, add initial plies */
6334                 if( count == backwardMostMove )
6335                     count -= initialRulePlies;
6336                 count = forwardMostMove - count; 
6337                 if( count >= 100)
6338                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6339                          /* this is used to judge if draw claims are legal */
6340                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6341                          SendToProgram("force\n", cps->other); // suppress reply
6342                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6343                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6344                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6345                          return;
6346                 }
6347
6348                 /* if draw offer is pending, treat it as a draw claim
6349                  * when draw condition present, to allow engines a way to
6350                  * claim draws before making their move to avoid a race
6351                  * condition occurring after their move
6352                  */
6353                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6354                          char *p = NULL;
6355                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6356                              p = "Draw claim: 50-move rule";
6357                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6358                              p = "Draw claim: 3-fold repetition";
6359                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6360                              p = "Draw claim: insufficient mating material";
6361                          if( p != NULL ) {
6362                              SendToProgram("force\n", cps->other); // suppress reply
6363                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6364                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6365                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6366                              return;
6367                          }
6368                 }
6369
6370
6371                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6372                     SendToProgram("force\n", cps->other); // suppress reply
6373                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6374                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6375
6376                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6377
6378                     return;
6379                 }
6380         }
6381
6382         bookHit = NULL;
6383         if (gameMode == TwoMachinesPlay) {
6384             /* [HGM] relaying draw offers moved to after reception of move */
6385             /* and interpreting offer as claim if it brings draw condition */
6386             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6387                 SendToProgram("draw\n", cps->other);
6388             }
6389             if (cps->other->sendTime) {
6390                 SendTimeRemaining(cps->other,
6391                                   cps->other->twoMachinesColor[0] == 'w');
6392             }
6393             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6394             if (firstMove && !bookHit) {
6395                 firstMove = FALSE;
6396                 if (cps->other->useColors) {
6397                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6398                 }
6399                 SendToProgram("go\n", cps->other);
6400             }
6401             cps->other->maybeThinking = TRUE;
6402         }
6403
6404         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6405         
6406         if (!pausing && appData.ringBellAfterMoves) {
6407             RingBell();
6408         }
6409
6410         /* 
6411          * Reenable menu items that were disabled while
6412          * machine was thinking
6413          */
6414         if (gameMode != TwoMachinesPlay)
6415             SetUserThinkingEnables();
6416
6417         // [HGM] book: after book hit opponent has received move and is now in force mode
6418         // force the book reply into it, and then fake that it outputted this move by jumping
6419         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6420         if(bookHit) {
6421                 static char bookMove[MSG_SIZ]; // a bit generous?
6422
6423                 strcpy(bookMove, "move ");
6424                 strcat(bookMove, bookHit);
6425                 message = bookMove;
6426                 cps = cps->other;
6427                 programStats.nodes = programStats.depth = programStats.time = 
6428                 programStats.score = programStats.got_only_move = 0;
6429                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6430
6431                 if(cps->lastPing != cps->lastPong) {
6432                     savedMessage = message; // args for deferred call
6433                     savedState = cps;
6434                     ScheduleDelayedEvent(DeferredBookMove, 10);
6435                     return;
6436                 }
6437                 goto FakeBookMove;
6438         }
6439
6440         return;
6441     }
6442
6443     /* Set special modes for chess engines.  Later something general
6444      *  could be added here; for now there is just one kludge feature,
6445      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6446      *  when "xboard" is given as an interactive command.
6447      */
6448     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6449         cps->useSigint = FALSE;
6450         cps->useSigterm = FALSE;
6451     }
6452     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6453       ParseFeatures(message+8, cps);
6454       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6455     }
6456
6457     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6458      * want this, I was asked to put it in, and obliged.
6459      */
6460     if (!strncmp(message, "setboard ", 9)) {
6461         Board initial_position; int i;
6462
6463         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6464
6465         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6466             DisplayError(_("Bad FEN received from engine"), 0);
6467             return ;
6468         } else {
6469            Reset(TRUE, FALSE);
6470            CopyBoard(boards[0], initial_position);
6471            initialRulePlies = FENrulePlies;
6472            epStatus[0] = FENepStatus;
6473            for( i=0; i<nrCastlingRights; i++ )
6474                 castlingRights[0][i] = FENcastlingRights[i];
6475            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6476            else gameMode = MachinePlaysBlack;                 
6477            DrawPosition(FALSE, boards[currentMove]);
6478         }
6479         return;
6480     }
6481
6482     /*
6483      * Look for communication commands
6484      */
6485     if (!strncmp(message, "telluser ", 9)) {
6486         DisplayNote(message + 9);
6487         return;
6488     }
6489     if (!strncmp(message, "tellusererror ", 14)) {
6490         DisplayError(message + 14, 0);
6491         return;
6492     }
6493     if (!strncmp(message, "tellopponent ", 13)) {
6494       if (appData.icsActive) {
6495         if (loggedOn) {
6496           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6497           SendToICS(buf1);
6498         }
6499       } else {
6500         DisplayNote(message + 13);
6501       }
6502       return;
6503     }
6504     if (!strncmp(message, "tellothers ", 11)) {
6505       if (appData.icsActive) {
6506         if (loggedOn) {
6507           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6508           SendToICS(buf1);
6509         }
6510       }
6511       return;
6512     }
6513     if (!strncmp(message, "tellall ", 8)) {
6514       if (appData.icsActive) {
6515         if (loggedOn) {
6516           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6517           SendToICS(buf1);
6518         }
6519       } else {
6520         DisplayNote(message + 8);
6521       }
6522       return;
6523     }
6524     if (strncmp(message, "warning", 7) == 0) {
6525         /* Undocumented feature, use tellusererror in new code */
6526         DisplayError(message, 0);
6527         return;
6528     }
6529     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6530         strcpy(realname, cps->tidy);
6531         strcat(realname, " query");
6532         AskQuestion(realname, buf2, buf1, cps->pr);
6533         return;
6534     }
6535     /* Commands from the engine directly to ICS.  We don't allow these to be 
6536      *  sent until we are logged on. Crafty kibitzes have been known to 
6537      *  interfere with the login process.
6538      */
6539     if (loggedOn) {
6540         if (!strncmp(message, "tellics ", 8)) {
6541             SendToICS(message + 8);
6542             SendToICS("\n");
6543             return;
6544         }
6545         if (!strncmp(message, "tellicsnoalias ", 15)) {
6546             SendToICS(ics_prefix);
6547             SendToICS(message + 15);
6548             SendToICS("\n");
6549             return;
6550         }
6551         /* The following are for backward compatibility only */
6552         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6553             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6554             SendToICS(ics_prefix);
6555             SendToICS(message);
6556             SendToICS("\n");
6557             return;
6558         }
6559     }
6560     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6561         return;
6562     }
6563     /*
6564      * If the move is illegal, cancel it and redraw the board.
6565      * Also deal with other error cases.  Matching is rather loose
6566      * here to accommodate engines written before the spec.
6567      */
6568     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6569         strncmp(message, "Error", 5) == 0) {
6570         if (StrStr(message, "name") || 
6571             StrStr(message, "rating") || StrStr(message, "?") ||
6572             StrStr(message, "result") || StrStr(message, "board") ||
6573             StrStr(message, "bk") || StrStr(message, "computer") ||
6574             StrStr(message, "variant") || StrStr(message, "hint") ||
6575             StrStr(message, "random") || StrStr(message, "depth") ||
6576             StrStr(message, "accepted")) {
6577             return;
6578         }
6579         if (StrStr(message, "protover")) {
6580           /* Program is responding to input, so it's apparently done
6581              initializing, and this error message indicates it is
6582              protocol version 1.  So we don't need to wait any longer
6583              for it to initialize and send feature commands. */
6584           FeatureDone(cps, 1);
6585           cps->protocolVersion = 1;
6586           return;
6587         }
6588         cps->maybeThinking = FALSE;
6589
6590         if (StrStr(message, "draw")) {
6591             /* Program doesn't have "draw" command */
6592             cps->sendDrawOffers = 0;
6593             return;
6594         }
6595         if (cps->sendTime != 1 &&
6596             (StrStr(message, "time") || StrStr(message, "otim"))) {
6597           /* Program apparently doesn't have "time" or "otim" command */
6598           cps->sendTime = 0;
6599           return;
6600         }
6601         if (StrStr(message, "analyze")) {
6602             cps->analysisSupport = FALSE;
6603             cps->analyzing = FALSE;
6604             Reset(FALSE, TRUE);
6605             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6606             DisplayError(buf2, 0);
6607             return;
6608         }
6609         if (StrStr(message, "(no matching move)st")) {
6610           /* Special kludge for GNU Chess 4 only */
6611           cps->stKludge = TRUE;
6612           SendTimeControl(cps, movesPerSession, timeControl,
6613                           timeIncrement, appData.searchDepth,
6614                           searchTime);
6615           return;
6616         }
6617         if (StrStr(message, "(no matching move)sd")) {
6618           /* Special kludge for GNU Chess 4 only */
6619           cps->sdKludge = TRUE;
6620           SendTimeControl(cps, movesPerSession, timeControl,
6621                           timeIncrement, appData.searchDepth,
6622                           searchTime);
6623           return;
6624         }
6625         if (!StrStr(message, "llegal")) {
6626             return;
6627         }
6628         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6629             gameMode == IcsIdle) return;
6630         if (forwardMostMove <= backwardMostMove) return;
6631         if (pausing) PauseEvent();
6632       if(appData.forceIllegal) {
6633             // [HGM] illegal: machine refused move; force position after move into it
6634           SendToProgram("force\n", cps);
6635           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6636                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6637                 // when black is to move, while there might be nothing on a2 or black
6638                 // might already have the move. So send the board as if white has the move.
6639                 // But first we must change the stm of the engine, as it refused the last move
6640                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6641                 if(WhiteOnMove(forwardMostMove)) {
6642                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6643                     SendBoard(cps, forwardMostMove); // kludgeless board
6644                 } else {
6645                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6646                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6647                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6648                 }
6649           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6650             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6651                  gameMode == TwoMachinesPlay)
6652               SendToProgram("go\n", cps);
6653             return;
6654       } else
6655         if (gameMode == PlayFromGameFile) {
6656             /* Stop reading this game file */
6657             gameMode = EditGame;
6658             ModeHighlight();
6659         }
6660         currentMove = --forwardMostMove;
6661         DisplayMove(currentMove-1); /* before DisplayMoveError */
6662         SwitchClocks();
6663         DisplayBothClocks();
6664         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6665                 parseList[currentMove], cps->which);
6666         DisplayMoveError(buf1);
6667         DrawPosition(FALSE, boards[currentMove]);
6668
6669         /* [HGM] illegal-move claim should forfeit game when Xboard */
6670         /* only passes fully legal moves                            */
6671         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6672             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6673                                 "False illegal-move claim", GE_XBOARD );
6674         }
6675         return;
6676     }
6677     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6678         /* Program has a broken "time" command that
6679            outputs a string not ending in newline.
6680            Don't use it. */
6681         cps->sendTime = 0;
6682     }
6683     
6684     /*
6685      * If chess program startup fails, exit with an error message.
6686      * Attempts to recover here are futile.
6687      */
6688     if ((StrStr(message, "unknown host") != NULL)
6689         || (StrStr(message, "No remote directory") != NULL)
6690         || (StrStr(message, "not found") != NULL)
6691         || (StrStr(message, "No such file") != NULL)
6692         || (StrStr(message, "can't alloc") != NULL)
6693         || (StrStr(message, "Permission denied") != NULL)) {
6694
6695         cps->maybeThinking = FALSE;
6696         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6697                 cps->which, cps->program, cps->host, message);
6698         RemoveInputSource(cps->isr);
6699         DisplayFatalError(buf1, 0, 1);
6700         return;
6701     }
6702     
6703     /* 
6704      * Look for hint output
6705      */
6706     if (sscanf(message, "Hint: %s", buf1) == 1) {
6707         if (cps == &first && hintRequested) {
6708             hintRequested = FALSE;
6709             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6710                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6711                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6712                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6713                                     fromY, fromX, toY, toX, promoChar, buf1);
6714                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6715                 DisplayInformation(buf2);
6716             } else {
6717                 /* Hint move could not be parsed!? */
6718               snprintf(buf2, sizeof(buf2),
6719                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6720                         buf1, cps->which);
6721                 DisplayError(buf2, 0);
6722             }
6723         } else {
6724             strcpy(lastHint, buf1);
6725         }
6726         return;
6727     }
6728
6729     /*
6730      * Ignore other messages if game is not in progress
6731      */
6732     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6733         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6734
6735     /*
6736      * look for win, lose, draw, or draw offer
6737      */
6738     if (strncmp(message, "1-0", 3) == 0) {
6739         char *p, *q, *r = "";
6740         p = strchr(message, '{');
6741         if (p) {
6742             q = strchr(p, '}');
6743             if (q) {
6744                 *q = NULLCHAR;
6745                 r = p + 1;
6746             }
6747         }
6748         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6749         return;
6750     } else if (strncmp(message, "0-1", 3) == 0) {
6751         char *p, *q, *r = "";
6752         p = strchr(message, '{');
6753         if (p) {
6754             q = strchr(p, '}');
6755             if (q) {
6756                 *q = NULLCHAR;
6757                 r = p + 1;
6758             }
6759         }
6760         /* Kludge for Arasan 4.1 bug */
6761         if (strcmp(r, "Black resigns") == 0) {
6762             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6763             return;
6764         }
6765         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6766         return;
6767     } else if (strncmp(message, "1/2", 3) == 0) {
6768         char *p, *q, *r = "";
6769         p = strchr(message, '{');
6770         if (p) {
6771             q = strchr(p, '}');
6772             if (q) {
6773                 *q = NULLCHAR;
6774                 r = p + 1;
6775             }
6776         }
6777             
6778         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6779         return;
6780
6781     } else if (strncmp(message, "White resign", 12) == 0) {
6782         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6783         return;
6784     } else if (strncmp(message, "Black resign", 12) == 0) {
6785         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6786         return;
6787     } else if (strncmp(message, "White matches", 13) == 0 ||
6788                strncmp(message, "Black matches", 13) == 0   ) {
6789         /* [HGM] ignore GNUShogi noises */
6790         return;
6791     } else if (strncmp(message, "White", 5) == 0 &&
6792                message[5] != '(' &&
6793                StrStr(message, "Black") == NULL) {
6794         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6795         return;
6796     } else if (strncmp(message, "Black", 5) == 0 &&
6797                message[5] != '(') {
6798         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6799         return;
6800     } else if (strcmp(message, "resign") == 0 ||
6801                strcmp(message, "computer resigns") == 0) {
6802         switch (gameMode) {
6803           case MachinePlaysBlack:
6804           case IcsPlayingBlack:
6805             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6806             break;
6807           case MachinePlaysWhite:
6808           case IcsPlayingWhite:
6809             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6810             break;
6811           case TwoMachinesPlay:
6812             if (cps->twoMachinesColor[0] == 'w')
6813               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6814             else
6815               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6816             break;
6817           default:
6818             /* can't happen */
6819             break;
6820         }
6821         return;
6822     } else if (strncmp(message, "opponent mates", 14) == 0) {
6823         switch (gameMode) {
6824           case MachinePlaysBlack:
6825           case IcsPlayingBlack:
6826             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6827             break;
6828           case MachinePlaysWhite:
6829           case IcsPlayingWhite:
6830             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6831             break;
6832           case TwoMachinesPlay:
6833             if (cps->twoMachinesColor[0] == 'w')
6834               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6835             else
6836               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6837             break;
6838           default:
6839             /* can't happen */
6840             break;
6841         }
6842         return;
6843     } else if (strncmp(message, "computer mates", 14) == 0) {
6844         switch (gameMode) {
6845           case MachinePlaysBlack:
6846           case IcsPlayingBlack:
6847             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6848             break;
6849           case MachinePlaysWhite:
6850           case IcsPlayingWhite:
6851             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6852             break;
6853           case TwoMachinesPlay:
6854             if (cps->twoMachinesColor[0] == 'w')
6855               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6856             else
6857               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6858             break;
6859           default:
6860             /* can't happen */
6861             break;
6862         }
6863         return;
6864     } else if (strncmp(message, "checkmate", 9) == 0) {
6865         if (WhiteOnMove(forwardMostMove)) {
6866             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6867         } else {
6868             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6869         }
6870         return;
6871     } else if (strstr(message, "Draw") != NULL ||
6872                strstr(message, "game is a draw") != NULL) {
6873         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6874         return;
6875     } else if (strstr(message, "offer") != NULL &&
6876                strstr(message, "draw") != NULL) {
6877 #if ZIPPY
6878         if (appData.zippyPlay && first.initDone) {
6879             /* Relay offer to ICS */
6880             SendToICS(ics_prefix);
6881             SendToICS("draw\n");
6882         }
6883 #endif
6884         cps->offeredDraw = 2; /* valid until this engine moves twice */
6885         if (gameMode == TwoMachinesPlay) {
6886             if (cps->other->offeredDraw) {
6887                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6888             /* [HGM] in two-machine mode we delay relaying draw offer      */
6889             /* until after we also have move, to see if it is really claim */
6890             }
6891         } else if (gameMode == MachinePlaysWhite ||
6892                    gameMode == MachinePlaysBlack) {
6893           if (userOfferedDraw) {
6894             DisplayInformation(_("Machine accepts your draw offer"));
6895             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6896           } else {
6897             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6898           }
6899         }
6900     }
6901
6902     
6903     /*
6904      * Look for thinking output
6905      */
6906     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6907           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6908                                 ) {
6909         int plylev, mvleft, mvtot, curscore, time;
6910         char mvname[MOVE_LEN];
6911         u64 nodes; // [DM]
6912         char plyext;
6913         int ignore = FALSE;
6914         int prefixHint = FALSE;
6915         mvname[0] = NULLCHAR;
6916
6917         switch (gameMode) {
6918           case MachinePlaysBlack:
6919           case IcsPlayingBlack:
6920             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6921             break;
6922           case MachinePlaysWhite:
6923           case IcsPlayingWhite:
6924             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6925             break;
6926           case AnalyzeMode:
6927           case AnalyzeFile:
6928             break;
6929           case IcsObserving: /* [DM] icsEngineAnalyze */
6930             if (!appData.icsEngineAnalyze) ignore = TRUE;
6931             break;
6932           case TwoMachinesPlay:
6933             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6934                 ignore = TRUE;
6935             }
6936             break;
6937           default:
6938             ignore = TRUE;
6939             break;
6940         }
6941
6942         if (!ignore) {
6943             buf1[0] = NULLCHAR;
6944             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6945                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6946
6947                 if (plyext != ' ' && plyext != '\t') {
6948                     time *= 100;
6949                 }
6950
6951                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6952                 if( cps->scoreIsAbsolute && 
6953                     ( gameMode == MachinePlaysBlack ||
6954                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6955                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
6956                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6957                      !WhiteOnMove(currentMove)
6958                     ) )
6959                 {
6960                     curscore = -curscore;
6961                 }
6962
6963
6964                 programStats.depth = plylev;
6965                 programStats.nodes = nodes;
6966                 programStats.time = time;
6967                 programStats.score = curscore;
6968                 programStats.got_only_move = 0;
6969
6970                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6971                         int ticklen;
6972
6973                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6974                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6975                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6976                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6977                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6978                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6979                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6980                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6981                 }
6982
6983                 /* Buffer overflow protection */
6984                 if (buf1[0] != NULLCHAR) {
6985                     if (strlen(buf1) >= sizeof(programStats.movelist)
6986                         && appData.debugMode) {
6987                         fprintf(debugFP,
6988                                 "PV is too long; using the first %u bytes.\n",
6989                                 (unsigned) sizeof(programStats.movelist) - 1);
6990                     }
6991
6992                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6993                 } else {
6994                     sprintf(programStats.movelist, " no PV\n");
6995                 }
6996
6997                 if (programStats.seen_stat) {
6998                     programStats.ok_to_send = 1;
6999                 }
7000
7001                 if (strchr(programStats.movelist, '(') != NULL) {
7002                     programStats.line_is_book = 1;
7003                     programStats.nr_moves = 0;
7004                     programStats.moves_left = 0;
7005                 } else {
7006                     programStats.line_is_book = 0;
7007                 }
7008
7009                 SendProgramStatsToFrontend( cps, &programStats );
7010
7011                 /* 
7012                     [AS] Protect the thinkOutput buffer from overflow... this
7013                     is only useful if buf1 hasn't overflowed first!
7014                 */
7015                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7016                         plylev, 
7017                         (gameMode == TwoMachinesPlay ?
7018                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7019                         ((double) curscore) / 100.0,
7020                         prefixHint ? lastHint : "",
7021                         prefixHint ? " " : "" );
7022
7023                 if( buf1[0] != NULLCHAR ) {
7024                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7025
7026                     if( strlen(buf1) > max_len ) {
7027                         if( appData.debugMode) {
7028                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7029                         }
7030                         buf1[max_len+1] = '\0';
7031                     }
7032
7033                     strcat( thinkOutput, buf1 );
7034                 }
7035
7036                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7037                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7038                     DisplayMove(currentMove - 1);
7039                 }
7040                 return;
7041
7042             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7043                 /* crafty (9.25+) says "(only move) <move>"
7044                  * if there is only 1 legal move
7045                  */
7046                 sscanf(p, "(only move) %s", buf1);
7047                 sprintf(thinkOutput, "%s (only move)", buf1);
7048                 sprintf(programStats.movelist, "%s (only move)", buf1);
7049                 programStats.depth = 1;
7050                 programStats.nr_moves = 1;
7051                 programStats.moves_left = 1;
7052                 programStats.nodes = 1;
7053                 programStats.time = 1;
7054                 programStats.got_only_move = 1;
7055
7056                 /* Not really, but we also use this member to
7057                    mean "line isn't going to change" (Crafty
7058                    isn't searching, so stats won't change) */
7059                 programStats.line_is_book = 1;
7060
7061                 SendProgramStatsToFrontend( cps, &programStats );
7062                 
7063                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7064                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7065                     DisplayMove(currentMove - 1);
7066                 }
7067                 return;
7068             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7069                               &time, &nodes, &plylev, &mvleft,
7070                               &mvtot, mvname) >= 5) {
7071                 /* The stat01: line is from Crafty (9.29+) in response
7072                    to the "." command */
7073                 programStats.seen_stat = 1;
7074                 cps->maybeThinking = TRUE;
7075
7076                 if (programStats.got_only_move || !appData.periodicUpdates)
7077                   return;
7078
7079                 programStats.depth = plylev;
7080                 programStats.time = time;
7081                 programStats.nodes = nodes;
7082                 programStats.moves_left = mvleft;
7083                 programStats.nr_moves = mvtot;
7084                 strcpy(programStats.move_name, mvname);
7085                 programStats.ok_to_send = 1;
7086                 programStats.movelist[0] = '\0';
7087
7088                 SendProgramStatsToFrontend( cps, &programStats );
7089
7090                 return;
7091
7092             } else if (strncmp(message,"++",2) == 0) {
7093                 /* Crafty 9.29+ outputs this */
7094                 programStats.got_fail = 2;
7095                 return;
7096
7097             } else if (strncmp(message,"--",2) == 0) {
7098                 /* Crafty 9.29+ outputs this */
7099                 programStats.got_fail = 1;
7100                 return;
7101
7102             } else if (thinkOutput[0] != NULLCHAR &&
7103                        strncmp(message, "    ", 4) == 0) {
7104                 unsigned message_len;
7105
7106                 p = message;
7107                 while (*p && *p == ' ') p++;
7108
7109                 message_len = strlen( p );
7110
7111                 /* [AS] Avoid buffer overflow */
7112                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7113                     strcat(thinkOutput, " ");
7114                     strcat(thinkOutput, p);
7115                 }
7116
7117                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7118                     strcat(programStats.movelist, " ");
7119                     strcat(programStats.movelist, p);
7120                 }
7121
7122                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7123                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7124                     DisplayMove(currentMove - 1);
7125                 }
7126                 return;
7127             }
7128         }
7129         else {
7130             buf1[0] = NULLCHAR;
7131
7132             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7133                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7134             {
7135                 ChessProgramStats cpstats;
7136
7137                 if (plyext != ' ' && plyext != '\t') {
7138                     time *= 100;
7139                 }
7140
7141                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7142                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7143                     curscore = -curscore;
7144                 }
7145
7146                 cpstats.depth = plylev;
7147                 cpstats.nodes = nodes;
7148                 cpstats.time = time;
7149                 cpstats.score = curscore;
7150                 cpstats.got_only_move = 0;
7151                 cpstats.movelist[0] = '\0';
7152
7153                 if (buf1[0] != NULLCHAR) {
7154                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7155                 }
7156
7157                 cpstats.ok_to_send = 0;
7158                 cpstats.line_is_book = 0;
7159                 cpstats.nr_moves = 0;
7160                 cpstats.moves_left = 0;
7161
7162                 SendProgramStatsToFrontend( cps, &cpstats );
7163             }
7164         }
7165     }
7166 }
7167
7168
7169 /* Parse a game score from the character string "game", and
7170    record it as the history of the current game.  The game
7171    score is NOT assumed to start from the standard position. 
7172    The display is not updated in any way.
7173    */
7174 void
7175 ParseGameHistory(game)
7176      char *game;
7177 {
7178     ChessMove moveType;
7179     int fromX, fromY, toX, toY, boardIndex;
7180     char promoChar;
7181     char *p, *q;
7182     char buf[MSG_SIZ];
7183
7184     if (appData.debugMode)
7185       fprintf(debugFP, "Parsing game history: %s\n", game);
7186
7187     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7188     gameInfo.site = StrSave(appData.icsHost);
7189     gameInfo.date = PGNDate();
7190     gameInfo.round = StrSave("-");
7191
7192     /* Parse out names of players */
7193     while (*game == ' ') game++;
7194     p = buf;
7195     while (*game != ' ') *p++ = *game++;
7196     *p = NULLCHAR;
7197     gameInfo.white = StrSave(buf);
7198     while (*game == ' ') game++;
7199     p = buf;
7200     while (*game != ' ' && *game != '\n') *p++ = *game++;
7201     *p = NULLCHAR;
7202     gameInfo.black = StrSave(buf);
7203
7204     /* Parse moves */
7205     boardIndex = blackPlaysFirst ? 1 : 0;
7206     yynewstr(game);
7207     for (;;) {
7208         yyboardindex = boardIndex;
7209         moveType = (ChessMove) yylex();
7210         switch (moveType) {
7211           case IllegalMove:             /* maybe suicide chess, etc. */
7212   if (appData.debugMode) {
7213     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7214     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7215     setbuf(debugFP, NULL);
7216   }
7217           case WhitePromotionChancellor:
7218           case BlackPromotionChancellor:
7219           case WhitePromotionArchbishop:
7220           case BlackPromotionArchbishop:
7221           case WhitePromotionQueen:
7222           case BlackPromotionQueen:
7223           case WhitePromotionRook:
7224           case BlackPromotionRook:
7225           case WhitePromotionBishop:
7226           case BlackPromotionBishop:
7227           case WhitePromotionKnight:
7228           case BlackPromotionKnight:
7229           case WhitePromotionKing:
7230           case BlackPromotionKing:
7231           case NormalMove:
7232           case WhiteCapturesEnPassant:
7233           case BlackCapturesEnPassant:
7234           case WhiteKingSideCastle:
7235           case WhiteQueenSideCastle:
7236           case BlackKingSideCastle:
7237           case BlackQueenSideCastle:
7238           case WhiteKingSideCastleWild:
7239           case WhiteQueenSideCastleWild:
7240           case BlackKingSideCastleWild:
7241           case BlackQueenSideCastleWild:
7242           /* PUSH Fabien */
7243           case WhiteHSideCastleFR:
7244           case WhiteASideCastleFR:
7245           case BlackHSideCastleFR:
7246           case BlackASideCastleFR:
7247           /* POP Fabien */
7248             fromX = currentMoveString[0] - AAA;
7249             fromY = currentMoveString[1] - ONE;
7250             toX = currentMoveString[2] - AAA;
7251             toY = currentMoveString[3] - ONE;
7252             promoChar = currentMoveString[4];
7253             break;
7254           case WhiteDrop:
7255           case BlackDrop:
7256             fromX = moveType == WhiteDrop ?
7257               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7258             (int) CharToPiece(ToLower(currentMoveString[0]));
7259             fromY = DROP_RANK;
7260             toX = currentMoveString[2] - AAA;
7261             toY = currentMoveString[3] - ONE;
7262             promoChar = NULLCHAR;
7263             break;
7264           case AmbiguousMove:
7265             /* bug? */
7266             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7267   if (appData.debugMode) {
7268     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7269     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7270     setbuf(debugFP, NULL);
7271   }
7272             DisplayError(buf, 0);
7273             return;
7274           case ImpossibleMove:
7275             /* bug? */
7276             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7277   if (appData.debugMode) {
7278     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7279     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7280     setbuf(debugFP, NULL);
7281   }
7282             DisplayError(buf, 0);
7283             return;
7284           case (ChessMove) 0:   /* end of file */
7285             if (boardIndex < backwardMostMove) {
7286                 /* Oops, gap.  How did that happen? */
7287                 DisplayError(_("Gap in move list"), 0);
7288                 return;
7289             }
7290             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7291             if (boardIndex > forwardMostMove) {
7292                 forwardMostMove = boardIndex;
7293             }
7294             return;
7295           case ElapsedTime:
7296             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7297                 strcat(parseList[boardIndex-1], " ");
7298                 strcat(parseList[boardIndex-1], yy_text);
7299             }
7300             continue;
7301           case Comment:
7302           case PGNTag:
7303           case NAG:
7304           default:
7305             /* ignore */
7306             continue;
7307           case WhiteWins:
7308           case BlackWins:
7309           case GameIsDrawn:
7310           case GameUnfinished:
7311             if (gameMode == IcsExamining) {
7312                 if (boardIndex < backwardMostMove) {
7313                     /* Oops, gap.  How did that happen? */
7314                     return;
7315                 }
7316                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7317                 return;
7318             }
7319             gameInfo.result = moveType;
7320             p = strchr(yy_text, '{');
7321             if (p == NULL) p = strchr(yy_text, '(');
7322             if (p == NULL) {
7323                 p = yy_text;
7324                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7325             } else {
7326                 q = strchr(p, *p == '{' ? '}' : ')');
7327                 if (q != NULL) *q = NULLCHAR;
7328                 p++;
7329             }
7330             gameInfo.resultDetails = StrSave(p);
7331             continue;
7332         }
7333         if (boardIndex >= forwardMostMove &&
7334             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7335             backwardMostMove = blackPlaysFirst ? 1 : 0;
7336             return;
7337         }
7338         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7339                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7340                                  parseList[boardIndex]);
7341         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7342         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7343         /* currentMoveString is set as a side-effect of yylex */
7344         strcpy(moveList[boardIndex], currentMoveString);
7345         strcat(moveList[boardIndex], "\n");
7346         boardIndex++;
7347         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7348                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7349         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7350                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7351           case MT_NONE:
7352           case MT_STALEMATE:
7353           default:
7354             break;
7355           case MT_CHECK:
7356             if(gameInfo.variant != VariantShogi)
7357                 strcat(parseList[boardIndex - 1], "+");
7358             break;
7359           case MT_CHECKMATE:
7360           case MT_STAINMATE:
7361             strcat(parseList[boardIndex - 1], "#");
7362             break;
7363         }
7364     }
7365 }
7366
7367
7368 /* Apply a move to the given board  */
7369 void
7370 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7371      int fromX, fromY, toX, toY;
7372      int promoChar;
7373      Board board;
7374      char *castling;
7375      char *ep;
7376 {
7377   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7378
7379     /* [HGM] compute & store e.p. status and castling rights for new position */
7380     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7381     { int i;
7382
7383       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7384       oldEP = *ep;
7385       *ep = EP_NONE;
7386
7387       if( board[toY][toX] != EmptySquare ) 
7388            *ep = EP_CAPTURE;  
7389
7390       if( board[fromY][fromX] == WhitePawn ) {
7391            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7392                *ep = EP_PAWN_MOVE;
7393            if( toY-fromY==2) {
7394                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7395                         gameInfo.variant != VariantBerolina || toX < fromX)
7396                       *ep = toX | berolina;
7397                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7398                         gameInfo.variant != VariantBerolina || toX > fromX) 
7399                       *ep = toX;
7400            }
7401       } else 
7402       if( board[fromY][fromX] == BlackPawn ) {
7403            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7404                *ep = EP_PAWN_MOVE; 
7405            if( toY-fromY== -2) {
7406                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7407                         gameInfo.variant != VariantBerolina || toX < fromX)
7408                       *ep = toX | berolina;
7409                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7410                         gameInfo.variant != VariantBerolina || toX > fromX) 
7411                       *ep = toX;
7412            }
7413        }
7414
7415        for(i=0; i<nrCastlingRights; i++) {
7416            if(castling[i] == fromX && castlingRank[i] == fromY ||
7417               castling[i] == toX   && castlingRank[i] == toY   
7418              ) castling[i] = -1; // revoke for moved or captured piece
7419        }
7420
7421     }
7422
7423   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7424   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7425        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7426          
7427   if (fromX == toX && fromY == toY) return;
7428
7429   if (fromY == DROP_RANK) {
7430         /* must be first */
7431         piece = board[toY][toX] = (ChessSquare) fromX;
7432   } else {
7433      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7434      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7435      if(gameInfo.variant == VariantKnightmate)
7436          king += (int) WhiteUnicorn - (int) WhiteKing;
7437
7438     /* Code added by Tord: */
7439     /* FRC castling assumed when king captures friendly rook. */
7440     if (board[fromY][fromX] == WhiteKing &&
7441              board[toY][toX] == WhiteRook) {
7442       board[fromY][fromX] = EmptySquare;
7443       board[toY][toX] = EmptySquare;
7444       if(toX > fromX) {
7445         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7446       } else {
7447         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7448       }
7449     } else if (board[fromY][fromX] == BlackKing &&
7450                board[toY][toX] == BlackRook) {
7451       board[fromY][fromX] = EmptySquare;
7452       board[toY][toX] = EmptySquare;
7453       if(toX > fromX) {
7454         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7455       } else {
7456         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7457       }
7458     /* End of code added by Tord */
7459
7460     } else if (board[fromY][fromX] == king
7461         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7462         && toY == fromY && toX > fromX+1) {
7463         board[fromY][fromX] = EmptySquare;
7464         board[toY][toX] = king;
7465         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7466         board[fromY][BOARD_RGHT-1] = EmptySquare;
7467     } else if (board[fromY][fromX] == king
7468         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7469                && toY == fromY && toX < fromX-1) {
7470         board[fromY][fromX] = EmptySquare;
7471         board[toY][toX] = king;
7472         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7473         board[fromY][BOARD_LEFT] = EmptySquare;
7474     } else if (board[fromY][fromX] == WhitePawn
7475                && toY == BOARD_HEIGHT-1
7476                && gameInfo.variant != VariantXiangqi
7477                ) {
7478         /* white pawn promotion */
7479         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7480         if (board[toY][toX] == EmptySquare) {
7481             board[toY][toX] = WhiteQueen;
7482         }
7483         if(gameInfo.variant==VariantBughouse ||
7484            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7485             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7486         board[fromY][fromX] = EmptySquare;
7487     } else if ((fromY == BOARD_HEIGHT-4)
7488                && (toX != fromX)
7489                && gameInfo.variant != VariantXiangqi
7490                && gameInfo.variant != VariantBerolina
7491                && (board[fromY][fromX] == WhitePawn)
7492                && (board[toY][toX] == EmptySquare)) {
7493         board[fromY][fromX] = EmptySquare;
7494         board[toY][toX] = WhitePawn;
7495         captured = board[toY - 1][toX];
7496         board[toY - 1][toX] = EmptySquare;
7497     } else if ((fromY == BOARD_HEIGHT-4)
7498                && (toX == fromX)
7499                && gameInfo.variant == VariantBerolina
7500                && (board[fromY][fromX] == WhitePawn)
7501                && (board[toY][toX] == EmptySquare)) {
7502         board[fromY][fromX] = EmptySquare;
7503         board[toY][toX] = WhitePawn;
7504         if(oldEP & EP_BEROLIN_A) {
7505                 captured = board[fromY][fromX-1];
7506                 board[fromY][fromX-1] = EmptySquare;
7507         }else{  captured = board[fromY][fromX+1];
7508                 board[fromY][fromX+1] = EmptySquare;
7509         }
7510     } else if (board[fromY][fromX] == king
7511         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7512                && toY == fromY && toX > fromX+1) {
7513         board[fromY][fromX] = EmptySquare;
7514         board[toY][toX] = king;
7515         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7516         board[fromY][BOARD_RGHT-1] = EmptySquare;
7517     } else if (board[fromY][fromX] == king
7518         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7519                && toY == fromY && toX < fromX-1) {
7520         board[fromY][fromX] = EmptySquare;
7521         board[toY][toX] = king;
7522         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7523         board[fromY][BOARD_LEFT] = EmptySquare;
7524     } else if (fromY == 7 && fromX == 3
7525                && board[fromY][fromX] == BlackKing
7526                && toY == 7 && toX == 5) {
7527         board[fromY][fromX] = EmptySquare;
7528         board[toY][toX] = BlackKing;
7529         board[fromY][7] = EmptySquare;
7530         board[toY][4] = BlackRook;
7531     } else if (fromY == 7 && fromX == 3
7532                && board[fromY][fromX] == BlackKing
7533                && toY == 7 && toX == 1) {
7534         board[fromY][fromX] = EmptySquare;
7535         board[toY][toX] = BlackKing;
7536         board[fromY][0] = EmptySquare;
7537         board[toY][2] = BlackRook;
7538     } else if (board[fromY][fromX] == BlackPawn
7539                && toY == 0
7540                && gameInfo.variant != VariantXiangqi
7541                ) {
7542         /* black pawn promotion */
7543         board[0][toX] = CharToPiece(ToLower(promoChar));
7544         if (board[0][toX] == EmptySquare) {
7545             board[0][toX] = BlackQueen;
7546         }
7547         if(gameInfo.variant==VariantBughouse ||
7548            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7549             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7550         board[fromY][fromX] = EmptySquare;
7551     } else if ((fromY == 3)
7552                && (toX != fromX)
7553                && gameInfo.variant != VariantXiangqi
7554                && gameInfo.variant != VariantBerolina
7555                && (board[fromY][fromX] == BlackPawn)
7556                && (board[toY][toX] == EmptySquare)) {
7557         board[fromY][fromX] = EmptySquare;
7558         board[toY][toX] = BlackPawn;
7559         captured = board[toY + 1][toX];
7560         board[toY + 1][toX] = EmptySquare;
7561     } else if ((fromY == 3)
7562                && (toX == fromX)
7563                && gameInfo.variant == VariantBerolina
7564                && (board[fromY][fromX] == BlackPawn)
7565                && (board[toY][toX] == EmptySquare)) {
7566         board[fromY][fromX] = EmptySquare;
7567         board[toY][toX] = BlackPawn;
7568         if(oldEP & EP_BEROLIN_A) {
7569                 captured = board[fromY][fromX-1];
7570                 board[fromY][fromX-1] = EmptySquare;
7571         }else{  captured = board[fromY][fromX+1];
7572                 board[fromY][fromX+1] = EmptySquare;
7573         }
7574     } else {
7575         board[toY][toX] = board[fromY][fromX];
7576         board[fromY][fromX] = EmptySquare;
7577     }
7578
7579     /* [HGM] now we promote for Shogi, if needed */
7580     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7581         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7582   }
7583
7584     if (gameInfo.holdingsWidth != 0) {
7585
7586       /* !!A lot more code needs to be written to support holdings  */
7587       /* [HGM] OK, so I have written it. Holdings are stored in the */
7588       /* penultimate board files, so they are automaticlly stored   */
7589       /* in the game history.                                       */
7590       if (fromY == DROP_RANK) {
7591         /* Delete from holdings, by decreasing count */
7592         /* and erasing image if necessary            */
7593         p = (int) fromX;
7594         if(p < (int) BlackPawn) { /* white drop */
7595              p -= (int)WhitePawn;
7596                  p = PieceToNumber((ChessSquare)p);
7597              if(p >= gameInfo.holdingsSize) p = 0;
7598              if(--board[p][BOARD_WIDTH-2] <= 0)
7599                   board[p][BOARD_WIDTH-1] = EmptySquare;
7600              if((int)board[p][BOARD_WIDTH-2] < 0)
7601                         board[p][BOARD_WIDTH-2] = 0;
7602         } else {                  /* black drop */
7603              p -= (int)BlackPawn;
7604                  p = PieceToNumber((ChessSquare)p);
7605              if(p >= gameInfo.holdingsSize) p = 0;
7606              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7607                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7608              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7609                         board[BOARD_HEIGHT-1-p][1] = 0;
7610         }
7611       }
7612       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7613           && gameInfo.variant != VariantBughouse        ) {
7614         /* [HGM] holdings: Add to holdings, if holdings exist */
7615         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7616                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7617                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7618         }
7619         p = (int) captured;
7620         if (p >= (int) BlackPawn) {
7621           p -= (int)BlackPawn;
7622           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7623                   /* in Shogi restore piece to its original  first */
7624                   captured = (ChessSquare) (DEMOTED captured);
7625                   p = DEMOTED p;
7626           }
7627           p = PieceToNumber((ChessSquare)p);
7628           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7629           board[p][BOARD_WIDTH-2]++;
7630           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7631         } else {
7632           p -= (int)WhitePawn;
7633           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7634                   captured = (ChessSquare) (DEMOTED captured);
7635                   p = DEMOTED p;
7636           }
7637           p = PieceToNumber((ChessSquare)p);
7638           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7639           board[BOARD_HEIGHT-1-p][1]++;
7640           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7641         }
7642       }
7643     } else if (gameInfo.variant == VariantAtomic) {
7644       if (captured != EmptySquare) {
7645         int y, x;
7646         for (y = toY-1; y <= toY+1; y++) {
7647           for (x = toX-1; x <= toX+1; x++) {
7648             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7649                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7650               board[y][x] = EmptySquare;
7651             }
7652           }
7653         }
7654         board[toY][toX] = EmptySquare;
7655       }
7656     }
7657     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7658         /* [HGM] Shogi promotions */
7659         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7660     }
7661
7662     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7663                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7664         // [HGM] superchess: take promotion piece out of holdings
7665         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7666         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7667             if(!--board[k][BOARD_WIDTH-2])
7668                 board[k][BOARD_WIDTH-1] = EmptySquare;
7669         } else {
7670             if(!--board[BOARD_HEIGHT-1-k][1])
7671                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7672         }
7673     }
7674
7675 }
7676
7677 /* Updates forwardMostMove */
7678 void
7679 MakeMove(fromX, fromY, toX, toY, promoChar)
7680      int fromX, fromY, toX, toY;
7681      int promoChar;
7682 {
7683 //    forwardMostMove++; // [HGM] bare: moved downstream
7684
7685     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7686         int timeLeft; static int lastLoadFlag=0; int king, piece;
7687         piece = boards[forwardMostMove][fromY][fromX];
7688         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7689         if(gameInfo.variant == VariantKnightmate)
7690             king += (int) WhiteUnicorn - (int) WhiteKing;
7691         if(forwardMostMove == 0) {
7692             if(blackPlaysFirst) 
7693                 fprintf(serverMoves, "%s;", second.tidy);
7694             fprintf(serverMoves, "%s;", first.tidy);
7695             if(!blackPlaysFirst) 
7696                 fprintf(serverMoves, "%s;", second.tidy);
7697         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7698         lastLoadFlag = loadFlag;
7699         // print base move
7700         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7701         // print castling suffix
7702         if( toY == fromY && piece == king ) {
7703             if(toX-fromX > 1)
7704                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7705             if(fromX-toX >1)
7706                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7707         }
7708         // e.p. suffix
7709         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7710              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7711              boards[forwardMostMove][toY][toX] == EmptySquare
7712              && fromX != toX )
7713                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7714         // promotion suffix
7715         if(promoChar != NULLCHAR)
7716                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7717         if(!loadFlag) {
7718             fprintf(serverMoves, "/%d/%d",
7719                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7720             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7721             else                      timeLeft = blackTimeRemaining/1000;
7722             fprintf(serverMoves, "/%d", timeLeft);
7723         }
7724         fflush(serverMoves);
7725     }
7726
7727     if (forwardMostMove+1 >= MAX_MOVES) {
7728       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7729                         0, 1);
7730       return;
7731     }
7732     if (commentList[forwardMostMove+1] != NULL) {
7733         free(commentList[forwardMostMove+1]);
7734         commentList[forwardMostMove+1] = NULL;
7735     }
7736     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7737     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7738     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7739                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7740     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7741     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7742     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7743     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7744     gameInfo.result = GameUnfinished;
7745     if (gameInfo.resultDetails != NULL) {
7746         free(gameInfo.resultDetails);
7747         gameInfo.resultDetails = NULL;
7748     }
7749     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7750                               moveList[forwardMostMove - 1]);
7751     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7752                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7753                              fromY, fromX, toY, toX, promoChar,
7754                              parseList[forwardMostMove - 1]);
7755     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7756                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7757                             castlingRights[forwardMostMove]) ) {
7758       case MT_NONE:
7759       case MT_STALEMATE:
7760       default:
7761         break;
7762       case MT_CHECK:
7763         if(gameInfo.variant != VariantShogi)
7764             strcat(parseList[forwardMostMove - 1], "+");
7765         break;
7766       case MT_CHECKMATE:
7767       case MT_STAINMATE:
7768         strcat(parseList[forwardMostMove - 1], "#");
7769         break;
7770     }
7771     if (appData.debugMode) {
7772         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7773     }
7774
7775 }
7776
7777 /* Updates currentMove if not pausing */
7778 void
7779 ShowMove(fromX, fromY, toX, toY)
7780 {
7781     int instant = (gameMode == PlayFromGameFile) ?
7782         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7783     if(appData.noGUI) return;
7784     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7785         if (!instant) {
7786             if (forwardMostMove == currentMove + 1) {
7787                 AnimateMove(boards[forwardMostMove - 1],
7788                             fromX, fromY, toX, toY);
7789             }
7790             if (appData.highlightLastMove) {
7791                 SetHighlights(fromX, fromY, toX, toY);
7792             }
7793         }
7794         currentMove = forwardMostMove;
7795     }
7796
7797     if (instant) return;
7798
7799     DisplayMove(currentMove - 1);
7800     DrawPosition(FALSE, boards[currentMove]);
7801     DisplayBothClocks();
7802     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7803 }
7804
7805 void SendEgtPath(ChessProgramState *cps)
7806 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7807         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7808
7809         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7810
7811         while(*p) {
7812             char c, *q = name+1, *r, *s;
7813
7814             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7815             while(*p && *p != ',') *q++ = *p++;
7816             *q++ = ':'; *q = 0;
7817             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7818                 strcmp(name, ",nalimov:") == 0 ) {
7819                 // take nalimov path from the menu-changeable option first, if it is defined
7820                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7821                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7822             } else
7823             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7824                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7825                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7826                 s = r = StrStr(s, ":") + 1; // beginning of path info
7827                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7828                 c = *r; *r = 0;             // temporarily null-terminate path info
7829                     *--q = 0;               // strip of trailig ':' from name
7830                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7831                 *r = c;
7832                 SendToProgram(buf,cps);     // send egtbpath command for this format
7833             }
7834             if(*p == ',') p++; // read away comma to position for next format name
7835         }
7836 }
7837
7838 void
7839 InitChessProgram(cps, setup)
7840      ChessProgramState *cps;
7841      int setup; /* [HGM] needed to setup FRC opening position */
7842 {
7843     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7844     if (appData.noChessProgram) return;
7845     hintRequested = FALSE;
7846     bookRequested = FALSE;
7847
7848     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7849     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7850     if(cps->memSize) { /* [HGM] memory */
7851         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7852         SendToProgram(buf, cps);
7853     }
7854     SendEgtPath(cps); /* [HGM] EGT */
7855     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7856         sprintf(buf, "cores %d\n", appData.smpCores);
7857         SendToProgram(buf, cps);
7858     }
7859
7860     SendToProgram(cps->initString, cps);
7861     if (gameInfo.variant != VariantNormal &&
7862         gameInfo.variant != VariantLoadable
7863         /* [HGM] also send variant if board size non-standard */
7864         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7865                                             ) {
7866       char *v = VariantName(gameInfo.variant);
7867       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7868         /* [HGM] in protocol 1 we have to assume all variants valid */
7869         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7870         DisplayFatalError(buf, 0, 1);
7871         return;
7872       }
7873
7874       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7875       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7876       if( gameInfo.variant == VariantXiangqi )
7877            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7878       if( gameInfo.variant == VariantShogi )
7879            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7880       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7881            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7882       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7883                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7884            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7885       if( gameInfo.variant == VariantCourier )
7886            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7887       if( gameInfo.variant == VariantSuper )
7888            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7889       if( gameInfo.variant == VariantGreat )
7890            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7891
7892       if(overruled) {
7893            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7894                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7895            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7896            if(StrStr(cps->variants, b) == NULL) { 
7897                // specific sized variant not known, check if general sizing allowed
7898                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7899                    if(StrStr(cps->variants, "boardsize") == NULL) {
7900                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7901                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7902                        DisplayFatalError(buf, 0, 1);
7903                        return;
7904                    }
7905                    /* [HGM] here we really should compare with the maximum supported board size */
7906                }
7907            }
7908       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7909       sprintf(buf, "variant %s\n", b);
7910       SendToProgram(buf, cps);
7911     }
7912     currentlyInitializedVariant = gameInfo.variant;
7913
7914     /* [HGM] send opening position in FRC to first engine */
7915     if(setup) {
7916           SendToProgram("force\n", cps);
7917           SendBoard(cps, 0);
7918           /* engine is now in force mode! Set flag to wake it up after first move. */
7919           setboardSpoiledMachineBlack = 1;
7920     }
7921
7922     if (cps->sendICS) {
7923       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7924       SendToProgram(buf, cps);
7925     }
7926     cps->maybeThinking = FALSE;
7927     cps->offeredDraw = 0;
7928     if (!appData.icsActive) {
7929         SendTimeControl(cps, movesPerSession, timeControl,
7930                         timeIncrement, appData.searchDepth,
7931                         searchTime);
7932     }
7933     if (appData.showThinking 
7934         // [HGM] thinking: four options require thinking output to be sent
7935         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7936                                 ) {
7937         SendToProgram("post\n", cps);
7938     }
7939     SendToProgram("hard\n", cps);
7940     if (!appData.ponderNextMove) {
7941         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7942            it without being sure what state we are in first.  "hard"
7943            is not a toggle, so that one is OK.
7944          */
7945         SendToProgram("easy\n", cps);
7946     }
7947     if (cps->usePing) {
7948       sprintf(buf, "ping %d\n", ++cps->lastPing);
7949       SendToProgram(buf, cps);
7950     }
7951     cps->initDone = TRUE;
7952 }   
7953
7954
7955 void
7956 StartChessProgram(cps)
7957      ChessProgramState *cps;
7958 {
7959     char buf[MSG_SIZ];
7960     int err;
7961
7962     if (appData.noChessProgram) return;
7963     cps->initDone = FALSE;
7964
7965     if (strcmp(cps->host, "localhost") == 0) {
7966         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7967     } else if (*appData.remoteShell == NULLCHAR) {
7968         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7969     } else {
7970         if (*appData.remoteUser == NULLCHAR) {
7971           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7972                     cps->program);
7973         } else {
7974           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7975                     cps->host, appData.remoteUser, cps->program);
7976         }
7977         err = StartChildProcess(buf, "", &cps->pr);
7978     }
7979     
7980     if (err != 0) {
7981         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7982         DisplayFatalError(buf, err, 1);
7983         cps->pr = NoProc;
7984         cps->isr = NULL;
7985         return;
7986     }
7987     
7988     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7989     if (cps->protocolVersion > 1) {
7990       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7991       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7992       cps->comboCnt = 0;  //                and values of combo boxes
7993       SendToProgram(buf, cps);
7994     } else {
7995       SendToProgram("xboard\n", cps);
7996     }
7997 }
7998
7999
8000 void
8001 TwoMachinesEventIfReady P((void))
8002 {
8003   if (first.lastPing != first.lastPong) {
8004     DisplayMessage("", _("Waiting for first chess program"));
8005     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8006     return;
8007   }
8008   if (second.lastPing != second.lastPong) {
8009     DisplayMessage("", _("Waiting for second chess program"));
8010     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8011     return;
8012   }
8013   ThawUI();
8014   TwoMachinesEvent();
8015 }
8016
8017 void
8018 NextMatchGame P((void))
8019 {
8020     int index; /* [HGM] autoinc: step load index during match */
8021     Reset(FALSE, TRUE);
8022     if (*appData.loadGameFile != NULLCHAR) {
8023         index = appData.loadGameIndex;
8024         if(index < 0) { // [HGM] autoinc
8025             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8026             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8027         } 
8028         LoadGameFromFile(appData.loadGameFile,
8029                          index,
8030                          appData.loadGameFile, FALSE);
8031     } else if (*appData.loadPositionFile != NULLCHAR) {
8032         index = appData.loadPositionIndex;
8033         if(index < 0) { // [HGM] autoinc
8034             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8035             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8036         } 
8037         LoadPositionFromFile(appData.loadPositionFile,
8038                              index,
8039                              appData.loadPositionFile);
8040     }
8041     TwoMachinesEventIfReady();
8042 }
8043
8044 void UserAdjudicationEvent( int result )
8045 {
8046     ChessMove gameResult = GameIsDrawn;
8047
8048     if( result > 0 ) {
8049         gameResult = WhiteWins;
8050     }
8051     else if( result < 0 ) {
8052         gameResult = BlackWins;
8053     }
8054
8055     if( gameMode == TwoMachinesPlay ) {
8056         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8057     }
8058 }
8059
8060
8061 // [HGM] save: calculate checksum of game to make games easily identifiable
8062 int StringCheckSum(char *s)
8063 {
8064         int i = 0;
8065         if(s==NULL) return 0;
8066         while(*s) i = i*259 + *s++;
8067         return i;
8068 }
8069
8070 int GameCheckSum()
8071 {
8072         int i, sum=0;
8073         for(i=backwardMostMove; i<forwardMostMove; i++) {
8074                 sum += pvInfoList[i].depth;
8075                 sum += StringCheckSum(parseList[i]);
8076                 sum += StringCheckSum(commentList[i]);
8077                 sum *= 261;
8078         }
8079         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8080         return sum + StringCheckSum(commentList[i]);
8081 } // end of save patch
8082
8083 void
8084 GameEnds(result, resultDetails, whosays)
8085      ChessMove result;
8086      char *resultDetails;
8087      int whosays;
8088 {
8089     GameMode nextGameMode;
8090     int isIcsGame;
8091     char buf[MSG_SIZ];
8092
8093     if(endingGame) return; /* [HGM] crash: forbid recursion */
8094     endingGame = 1;
8095
8096     if (appData.debugMode) {
8097       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8098               result, resultDetails ? resultDetails : "(null)", whosays);
8099     }
8100
8101     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8102         /* If we are playing on ICS, the server decides when the
8103            game is over, but the engine can offer to draw, claim 
8104            a draw, or resign. 
8105          */
8106 #if ZIPPY
8107         if (appData.zippyPlay && first.initDone) {
8108             if (result == GameIsDrawn) {
8109                 /* In case draw still needs to be claimed */
8110                 SendToICS(ics_prefix);
8111                 SendToICS("draw\n");
8112             } else if (StrCaseStr(resultDetails, "resign")) {
8113                 SendToICS(ics_prefix);
8114                 SendToICS("resign\n");
8115             }
8116         }
8117 #endif
8118         endingGame = 0; /* [HGM] crash */
8119         return;
8120     }
8121
8122     /* If we're loading the game from a file, stop */
8123     if (whosays == GE_FILE) {
8124       (void) StopLoadGameTimer();
8125       gameFileFP = NULL;
8126     }
8127
8128     /* Cancel draw offers */
8129     first.offeredDraw = second.offeredDraw = 0;
8130
8131     /* If this is an ICS game, only ICS can really say it's done;
8132        if not, anyone can. */
8133     isIcsGame = (gameMode == IcsPlayingWhite || 
8134                  gameMode == IcsPlayingBlack || 
8135                  gameMode == IcsObserving    || 
8136                  gameMode == IcsExamining);
8137
8138     if (!isIcsGame || whosays == GE_ICS) {
8139         /* OK -- not an ICS game, or ICS said it was done */
8140         StopClocks();
8141         if (!isIcsGame && !appData.noChessProgram) 
8142           SetUserThinkingEnables();
8143     
8144         /* [HGM] if a machine claims the game end we verify this claim */
8145         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8146             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8147                 char claimer;
8148                 ChessMove trueResult = (ChessMove) -1;
8149
8150                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8151                                             first.twoMachinesColor[0] :
8152                                             second.twoMachinesColor[0] ;
8153
8154                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8155                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8156                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8157                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8158                 } else
8159                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8160                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8161                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8162                 } else
8163                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8164                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8165                 }
8166
8167                 // now verify win claims, but not in drop games, as we don't understand those yet
8168                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8169                                                  || gameInfo.variant == VariantGreat) &&
8170                     (result == WhiteWins && claimer == 'w' ||
8171                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8172                       if (appData.debugMode) {
8173                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8174                                 result, epStatus[forwardMostMove], forwardMostMove);
8175                       }
8176                       if(result != trueResult) {
8177                               sprintf(buf, "False win claim: '%s'", resultDetails);
8178                               result = claimer == 'w' ? BlackWins : WhiteWins;
8179                               resultDetails = buf;
8180                       }
8181                 } else
8182                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8183                     && (forwardMostMove <= backwardMostMove ||
8184                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8185                         (claimer=='b')==(forwardMostMove&1))
8186                                                                                   ) {
8187                       /* [HGM] verify: draws that were not flagged are false claims */
8188                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8189                       result = claimer == 'w' ? BlackWins : WhiteWins;
8190                       resultDetails = buf;
8191                 }
8192                 /* (Claiming a loss is accepted no questions asked!) */
8193             }
8194             /* [HGM] bare: don't allow bare King to win */
8195             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8196                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8197                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8198                && result != GameIsDrawn)
8199             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8200                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8201                         int p = (int)boards[forwardMostMove][i][j] - color;
8202                         if(p >= 0 && p <= (int)WhiteKing) k++;
8203                 }
8204                 if (appData.debugMode) {
8205                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8206                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8207                 }
8208                 if(k <= 1) {
8209                         result = GameIsDrawn;
8210                         sprintf(buf, "%s but bare king", resultDetails);
8211                         resultDetails = buf;
8212                 }
8213             }
8214         }
8215
8216
8217         if(serverMoves != NULL && !loadFlag) { char c = '=';
8218             if(result==WhiteWins) c = '+';
8219             if(result==BlackWins) c = '-';
8220             if(resultDetails != NULL)
8221                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8222         }
8223         if (resultDetails != NULL) {
8224             gameInfo.result = result;
8225             gameInfo.resultDetails = StrSave(resultDetails);
8226
8227             /* display last move only if game was not loaded from file */
8228             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8229                 DisplayMove(currentMove - 1);
8230     
8231             if (forwardMostMove != 0) {
8232                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8233                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8234                                                                 ) {
8235                     if (*appData.saveGameFile != NULLCHAR) {
8236                         SaveGameToFile(appData.saveGameFile, TRUE);
8237                     } else if (appData.autoSaveGames) {
8238                         AutoSaveGame();
8239                     }
8240                     if (*appData.savePositionFile != NULLCHAR) {
8241                         SavePositionToFile(appData.savePositionFile);
8242                     }
8243                 }
8244             }
8245
8246             /* Tell program how game ended in case it is learning */
8247             /* [HGM] Moved this to after saving the PGN, just in case */
8248             /* engine died and we got here through time loss. In that */
8249             /* case we will get a fatal error writing the pipe, which */
8250             /* would otherwise lose us the PGN.                       */
8251             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8252             /* output during GameEnds should never be fatal anymore   */
8253             if (gameMode == MachinePlaysWhite ||
8254                 gameMode == MachinePlaysBlack ||
8255                 gameMode == TwoMachinesPlay ||
8256                 gameMode == IcsPlayingWhite ||
8257                 gameMode == IcsPlayingBlack ||
8258                 gameMode == BeginningOfGame) {
8259                 char buf[MSG_SIZ];
8260                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8261                         resultDetails);
8262                 if (first.pr != NoProc) {
8263                     SendToProgram(buf, &first);
8264                 }
8265                 if (second.pr != NoProc &&
8266                     gameMode == TwoMachinesPlay) {
8267                     SendToProgram(buf, &second);
8268                 }
8269             }
8270         }
8271
8272         if (appData.icsActive) {
8273             if (appData.quietPlay &&
8274                 (gameMode == IcsPlayingWhite ||
8275                  gameMode == IcsPlayingBlack)) {
8276                 SendToICS(ics_prefix);
8277                 SendToICS("set shout 1\n");
8278             }
8279             nextGameMode = IcsIdle;
8280             ics_user_moved = FALSE;
8281             /* clean up premove.  It's ugly when the game has ended and the
8282              * premove highlights are still on the board.
8283              */
8284             if (gotPremove) {
8285               gotPremove = FALSE;
8286               ClearPremoveHighlights();
8287               DrawPosition(FALSE, boards[currentMove]);
8288             }
8289             if (whosays == GE_ICS) {
8290                 switch (result) {
8291                 case WhiteWins:
8292                     if (gameMode == IcsPlayingWhite)
8293                         PlayIcsWinSound();
8294                     else if(gameMode == IcsPlayingBlack)
8295                         PlayIcsLossSound();
8296                     break;
8297                 case BlackWins:
8298                     if (gameMode == IcsPlayingBlack)
8299                         PlayIcsWinSound();
8300                     else if(gameMode == IcsPlayingWhite)
8301                         PlayIcsLossSound();
8302                     break;
8303                 case GameIsDrawn:
8304                     PlayIcsDrawSound();
8305                     break;
8306                 default:
8307                     PlayIcsUnfinishedSound();
8308                 }
8309             }
8310         } else if (gameMode == EditGame ||
8311                    gameMode == PlayFromGameFile || 
8312                    gameMode == AnalyzeMode || 
8313                    gameMode == AnalyzeFile) {
8314             nextGameMode = gameMode;
8315         } else {
8316             nextGameMode = EndOfGame;
8317         }
8318         pausing = FALSE;
8319         ModeHighlight();
8320     } else {
8321         nextGameMode = gameMode;
8322     }
8323
8324     if (appData.noChessProgram) {
8325         gameMode = nextGameMode;
8326         ModeHighlight();
8327         endingGame = 0; /* [HGM] crash */
8328         return;
8329     }
8330
8331     if (first.reuse) {
8332         /* Put first chess program into idle state */
8333         if (first.pr != NoProc &&
8334             (gameMode == MachinePlaysWhite ||
8335              gameMode == MachinePlaysBlack ||
8336              gameMode == TwoMachinesPlay ||
8337              gameMode == IcsPlayingWhite ||
8338              gameMode == IcsPlayingBlack ||
8339              gameMode == BeginningOfGame)) {
8340             SendToProgram("force\n", &first);
8341             if (first.usePing) {
8342               char buf[MSG_SIZ];
8343               sprintf(buf, "ping %d\n", ++first.lastPing);
8344               SendToProgram(buf, &first);
8345             }
8346         }
8347     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8348         /* Kill off first chess program */
8349         if (first.isr != NULL)
8350           RemoveInputSource(first.isr);
8351         first.isr = NULL;
8352     
8353         if (first.pr != NoProc) {
8354             ExitAnalyzeMode();
8355             DoSleep( appData.delayBeforeQuit );
8356             SendToProgram("quit\n", &first);
8357             DoSleep( appData.delayAfterQuit );
8358             DestroyChildProcess(first.pr, first.useSigterm);
8359         }
8360         first.pr = NoProc;
8361     }
8362     if (second.reuse) {
8363         /* Put second chess program into idle state */
8364         if (second.pr != NoProc &&
8365             gameMode == TwoMachinesPlay) {
8366             SendToProgram("force\n", &second);
8367             if (second.usePing) {
8368               char buf[MSG_SIZ];
8369               sprintf(buf, "ping %d\n", ++second.lastPing);
8370               SendToProgram(buf, &second);
8371             }
8372         }
8373     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8374         /* Kill off second chess program */
8375         if (second.isr != NULL)
8376           RemoveInputSource(second.isr);
8377         second.isr = NULL;
8378     
8379         if (second.pr != NoProc) {
8380             DoSleep( appData.delayBeforeQuit );
8381             SendToProgram("quit\n", &second);
8382             DoSleep( appData.delayAfterQuit );
8383             DestroyChildProcess(second.pr, second.useSigterm);
8384         }
8385         second.pr = NoProc;
8386     }
8387
8388     if (matchMode && gameMode == TwoMachinesPlay) {
8389         switch (result) {
8390         case WhiteWins:
8391           if (first.twoMachinesColor[0] == 'w') {
8392             first.matchWins++;
8393           } else {
8394             second.matchWins++;
8395           }
8396           break;
8397         case BlackWins:
8398           if (first.twoMachinesColor[0] == 'b') {
8399             first.matchWins++;
8400           } else {
8401             second.matchWins++;
8402           }
8403           break;
8404         default:
8405           break;
8406         }
8407         if (matchGame < appData.matchGames) {
8408             char *tmp;
8409             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8410                 tmp = first.twoMachinesColor;
8411                 first.twoMachinesColor = second.twoMachinesColor;
8412                 second.twoMachinesColor = tmp;
8413             }
8414             gameMode = nextGameMode;
8415             matchGame++;
8416             if(appData.matchPause>10000 || appData.matchPause<10)
8417                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8418             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8419             endingGame = 0; /* [HGM] crash */
8420             return;
8421         } else {
8422             char buf[MSG_SIZ];
8423             gameMode = nextGameMode;
8424             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8425                     first.tidy, second.tidy,
8426                     first.matchWins, second.matchWins,
8427                     appData.matchGames - (first.matchWins + second.matchWins));
8428             DisplayFatalError(buf, 0, 0);
8429         }
8430     }
8431     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8432         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8433       ExitAnalyzeMode();
8434     gameMode = nextGameMode;
8435     ModeHighlight();
8436     endingGame = 0;  /* [HGM] crash */
8437 }
8438
8439 /* Assumes program was just initialized (initString sent).
8440    Leaves program in force mode. */
8441 void
8442 FeedMovesToProgram(cps, upto) 
8443      ChessProgramState *cps;
8444      int upto;
8445 {
8446     int i;
8447     
8448     if (appData.debugMode)
8449       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8450               startedFromSetupPosition ? "position and " : "",
8451               backwardMostMove, upto, cps->which);
8452     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8453         // [HGM] variantswitch: make engine aware of new variant
8454         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8455                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8456         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8457         SendToProgram(buf, cps);
8458         currentlyInitializedVariant = gameInfo.variant;
8459     }
8460     SendToProgram("force\n", cps);
8461     if (startedFromSetupPosition) {
8462         SendBoard(cps, backwardMostMove);
8463     if (appData.debugMode) {
8464         fprintf(debugFP, "feedMoves\n");
8465     }
8466     }
8467     for (i = backwardMostMove; i < upto; i++) {
8468         SendMoveToProgram(i, cps);
8469     }
8470 }
8471
8472
8473 void
8474 ResurrectChessProgram()
8475 {
8476      /* The chess program may have exited.
8477         If so, restart it and feed it all the moves made so far. */
8478
8479     if (appData.noChessProgram || first.pr != NoProc) return;
8480     
8481     StartChessProgram(&first);
8482     InitChessProgram(&first, FALSE);
8483     FeedMovesToProgram(&first, currentMove);
8484
8485     if (!first.sendTime) {
8486         /* can't tell gnuchess what its clock should read,
8487            so we bow to its notion. */
8488         ResetClocks();
8489         timeRemaining[0][currentMove] = whiteTimeRemaining;
8490         timeRemaining[1][currentMove] = blackTimeRemaining;
8491     }
8492
8493     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8494                 appData.icsEngineAnalyze) && first.analysisSupport) {
8495       SendToProgram("analyze\n", &first);
8496       first.analyzing = TRUE;
8497     }
8498 }
8499
8500 /*
8501  * Button procedures
8502  */
8503 void
8504 Reset(redraw, init)
8505      int redraw, init;
8506 {
8507     int i;
8508
8509     if (appData.debugMode) {
8510         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8511                 redraw, init, gameMode);
8512     }
8513     pausing = pauseExamInvalid = FALSE;
8514     startedFromSetupPosition = blackPlaysFirst = FALSE;
8515     firstMove = TRUE;
8516     whiteFlag = blackFlag = FALSE;
8517     userOfferedDraw = FALSE;
8518     hintRequested = bookRequested = FALSE;
8519     first.maybeThinking = FALSE;
8520     second.maybeThinking = FALSE;
8521     first.bookSuspend = FALSE; // [HGM] book
8522     second.bookSuspend = FALSE;
8523     thinkOutput[0] = NULLCHAR;
8524     lastHint[0] = NULLCHAR;
8525     ClearGameInfo(&gameInfo);
8526     gameInfo.variant = StringToVariant(appData.variant);
8527     ics_user_moved = ics_clock_paused = FALSE;
8528     ics_getting_history = H_FALSE;
8529     ics_gamenum = -1;
8530     white_holding[0] = black_holding[0] = NULLCHAR;
8531     ClearProgramStats();
8532     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8533     
8534     ResetFrontEnd();
8535     ClearHighlights();
8536     flipView = appData.flipView;
8537     ClearPremoveHighlights();
8538     gotPremove = FALSE;
8539     alarmSounded = FALSE;
8540
8541     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8542     if(appData.serverMovesName != NULL) {
8543         /* [HGM] prepare to make moves file for broadcasting */
8544         clock_t t = clock();
8545         if(serverMoves != NULL) fclose(serverMoves);
8546         serverMoves = fopen(appData.serverMovesName, "r");
8547         if(serverMoves != NULL) {
8548             fclose(serverMoves);
8549             /* delay 15 sec before overwriting, so all clients can see end */
8550             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8551         }
8552         serverMoves = fopen(appData.serverMovesName, "w");
8553     }
8554
8555     ExitAnalyzeMode();
8556     gameMode = BeginningOfGame;
8557     ModeHighlight();
8558     if(appData.icsActive) gameInfo.variant = VariantNormal;
8559     currentMove = forwardMostMove = backwardMostMove = 0;
8560     InitPosition(redraw);
8561     for (i = 0; i < MAX_MOVES; i++) {
8562         if (commentList[i] != NULL) {
8563             free(commentList[i]);
8564             commentList[i] = NULL;
8565         }
8566     }
8567     ResetClocks();
8568     timeRemaining[0][0] = whiteTimeRemaining;
8569     timeRemaining[1][0] = blackTimeRemaining;
8570     if (first.pr == NULL) {
8571         StartChessProgram(&first);
8572     }
8573     if (init) {
8574             InitChessProgram(&first, startedFromSetupPosition);
8575     }
8576     DisplayTitle("");
8577     DisplayMessage("", "");
8578     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8579     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8580 }
8581
8582 void
8583 AutoPlayGameLoop()
8584 {
8585     for (;;) {
8586         if (!AutoPlayOneMove())
8587           return;
8588         if (matchMode || appData.timeDelay == 0)
8589           continue;
8590         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8591           return;
8592         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8593         break;
8594     }
8595 }
8596
8597
8598 int
8599 AutoPlayOneMove()
8600 {
8601     int fromX, fromY, toX, toY;
8602
8603     if (appData.debugMode) {
8604       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8605     }
8606
8607     if (gameMode != PlayFromGameFile)
8608       return FALSE;
8609
8610     if (currentMove >= forwardMostMove) {
8611       gameMode = EditGame;
8612       ModeHighlight();
8613
8614       /* [AS] Clear current move marker at the end of a game */
8615       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8616
8617       return FALSE;
8618     }
8619     
8620     toX = moveList[currentMove][2] - AAA;
8621     toY = moveList[currentMove][3] - ONE;
8622
8623     if (moveList[currentMove][1] == '@') {
8624         if (appData.highlightLastMove) {
8625             SetHighlights(-1, -1, toX, toY);
8626         }
8627     } else {
8628         fromX = moveList[currentMove][0] - AAA;
8629         fromY = moveList[currentMove][1] - ONE;
8630
8631         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8632
8633         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8634
8635         if (appData.highlightLastMove) {
8636             SetHighlights(fromX, fromY, toX, toY);
8637         }
8638     }
8639     DisplayMove(currentMove);
8640     SendMoveToProgram(currentMove++, &first);
8641     DisplayBothClocks();
8642     DrawPosition(FALSE, boards[currentMove]);
8643     // [HGM] PV info: always display, routine tests if empty
8644     DisplayComment(currentMove - 1, commentList[currentMove]);
8645     return TRUE;
8646 }
8647
8648
8649 int
8650 LoadGameOneMove(readAhead)
8651      ChessMove readAhead;
8652 {
8653     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8654     char promoChar = NULLCHAR;
8655     ChessMove moveType;
8656     char move[MSG_SIZ];
8657     char *p, *q;
8658     
8659     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8660         gameMode != AnalyzeMode && gameMode != Training) {
8661         gameFileFP = NULL;
8662         return FALSE;
8663     }
8664     
8665     yyboardindex = forwardMostMove;
8666     if (readAhead != (ChessMove)0) {
8667       moveType = readAhead;
8668     } else {
8669       if (gameFileFP == NULL)
8670           return FALSE;
8671       moveType = (ChessMove) yylex();
8672     }
8673     
8674     done = FALSE;
8675     switch (moveType) {
8676       case Comment:
8677         if (appData.debugMode) 
8678           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8679         p = yy_text;
8680         if (*p == '{' || *p == '[' || *p == '(') {
8681             p[strlen(p) - 1] = NULLCHAR;
8682             p++;
8683         }
8684
8685         /* append the comment but don't display it */
8686         while (*p == '\n') p++;
8687         AppendComment(currentMove, p);
8688         return TRUE;
8689
8690       case WhiteCapturesEnPassant:
8691       case BlackCapturesEnPassant:
8692       case WhitePromotionChancellor:
8693       case BlackPromotionChancellor:
8694       case WhitePromotionArchbishop:
8695       case BlackPromotionArchbishop:
8696       case WhitePromotionCentaur:
8697       case BlackPromotionCentaur:
8698       case WhitePromotionQueen:
8699       case BlackPromotionQueen:
8700       case WhitePromotionRook:
8701       case BlackPromotionRook:
8702       case WhitePromotionBishop:
8703       case BlackPromotionBishop:
8704       case WhitePromotionKnight:
8705       case BlackPromotionKnight:
8706       case WhitePromotionKing:
8707       case BlackPromotionKing:
8708       case NormalMove:
8709       case WhiteKingSideCastle:
8710       case WhiteQueenSideCastle:
8711       case BlackKingSideCastle:
8712       case BlackQueenSideCastle:
8713       case WhiteKingSideCastleWild:
8714       case WhiteQueenSideCastleWild:
8715       case BlackKingSideCastleWild:
8716       case BlackQueenSideCastleWild:
8717       /* PUSH Fabien */
8718       case WhiteHSideCastleFR:
8719       case WhiteASideCastleFR:
8720       case BlackHSideCastleFR:
8721       case BlackASideCastleFR:
8722       /* POP Fabien */
8723         if (appData.debugMode)
8724           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8725         fromX = currentMoveString[0] - AAA;
8726         fromY = currentMoveString[1] - ONE;
8727         toX = currentMoveString[2] - AAA;
8728         toY = currentMoveString[3] - ONE;
8729         promoChar = currentMoveString[4];
8730         break;
8731
8732       case WhiteDrop:
8733       case BlackDrop:
8734         if (appData.debugMode)
8735           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8736         fromX = moveType == WhiteDrop ?
8737           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8738         (int) CharToPiece(ToLower(currentMoveString[0]));
8739         fromY = DROP_RANK;
8740         toX = currentMoveString[2] - AAA;
8741         toY = currentMoveString[3] - ONE;
8742         break;
8743
8744       case WhiteWins:
8745       case BlackWins:
8746       case GameIsDrawn:
8747       case GameUnfinished:
8748         if (appData.debugMode)
8749           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8750         p = strchr(yy_text, '{');
8751         if (p == NULL) p = strchr(yy_text, '(');
8752         if (p == NULL) {
8753             p = yy_text;
8754             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8755         } else {
8756             q = strchr(p, *p == '{' ? '}' : ')');
8757             if (q != NULL) *q = NULLCHAR;
8758             p++;
8759         }
8760         GameEnds(moveType, p, GE_FILE);
8761         done = TRUE;
8762         if (cmailMsgLoaded) {
8763             ClearHighlights();
8764             flipView = WhiteOnMove(currentMove);
8765             if (moveType == GameUnfinished) flipView = !flipView;
8766             if (appData.debugMode)
8767               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8768         }
8769         break;
8770
8771       case (ChessMove) 0:       /* end of file */
8772         if (appData.debugMode)
8773           fprintf(debugFP, "Parser hit end of file\n");
8774         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8775                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8776           case MT_NONE:
8777           case MT_CHECK:
8778             break;
8779           case MT_CHECKMATE:
8780           case MT_STAINMATE:
8781             if (WhiteOnMove(currentMove)) {
8782                 GameEnds(BlackWins, "Black mates", GE_FILE);
8783             } else {
8784                 GameEnds(WhiteWins, "White mates", GE_FILE);
8785             }
8786             break;
8787           case MT_STALEMATE:
8788             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8789             break;
8790         }
8791         done = TRUE;
8792         break;
8793
8794       case MoveNumberOne:
8795         if (lastLoadGameStart == GNUChessGame) {
8796             /* GNUChessGames have numbers, but they aren't move numbers */
8797             if (appData.debugMode)
8798               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8799                       yy_text, (int) moveType);
8800             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8801         }
8802         /* else fall thru */
8803
8804       case XBoardGame:
8805       case GNUChessGame:
8806       case PGNTag:
8807         /* Reached start of next game in file */
8808         if (appData.debugMode)
8809           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8810         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8811                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8812           case MT_NONE:
8813           case MT_CHECK:
8814             break;
8815           case MT_CHECKMATE:
8816           case MT_STAINMATE:
8817             if (WhiteOnMove(currentMove)) {
8818                 GameEnds(BlackWins, "Black mates", GE_FILE);
8819             } else {
8820                 GameEnds(WhiteWins, "White mates", GE_FILE);
8821             }
8822             break;
8823           case MT_STALEMATE:
8824             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8825             break;
8826         }
8827         done = TRUE;
8828         break;
8829
8830       case PositionDiagram:     /* should not happen; ignore */
8831       case ElapsedTime:         /* ignore */
8832       case NAG:                 /* ignore */
8833         if (appData.debugMode)
8834           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8835                   yy_text, (int) moveType);
8836         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8837
8838       case IllegalMove:
8839         if (appData.testLegality) {
8840             if (appData.debugMode)
8841               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8842             sprintf(move, _("Illegal move: %d.%s%s"),
8843                     (forwardMostMove / 2) + 1,
8844                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8845             DisplayError(move, 0);
8846             done = TRUE;
8847         } else {
8848             if (appData.debugMode)
8849               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8850                       yy_text, currentMoveString);
8851             fromX = currentMoveString[0] - AAA;
8852             fromY = currentMoveString[1] - ONE;
8853             toX = currentMoveString[2] - AAA;
8854             toY = currentMoveString[3] - ONE;
8855             promoChar = currentMoveString[4];
8856         }
8857         break;
8858
8859       case AmbiguousMove:
8860         if (appData.debugMode)
8861           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8862         sprintf(move, _("Ambiguous move: %d.%s%s"),
8863                 (forwardMostMove / 2) + 1,
8864                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8865         DisplayError(move, 0);
8866         done = TRUE;
8867         break;
8868
8869       default:
8870       case ImpossibleMove:
8871         if (appData.debugMode)
8872           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8873         sprintf(move, _("Illegal move: %d.%s%s"),
8874                 (forwardMostMove / 2) + 1,
8875                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8876         DisplayError(move, 0);
8877         done = TRUE;
8878         break;
8879     }
8880
8881     if (done) {
8882         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8883             DrawPosition(FALSE, boards[currentMove]);
8884             DisplayBothClocks();
8885             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8886               DisplayComment(currentMove - 1, commentList[currentMove]);
8887         }
8888         (void) StopLoadGameTimer();
8889         gameFileFP = NULL;
8890         cmailOldMove = forwardMostMove;
8891         return FALSE;
8892     } else {
8893         /* currentMoveString is set as a side-effect of yylex */
8894         strcat(currentMoveString, "\n");
8895         strcpy(moveList[forwardMostMove], currentMoveString);
8896         
8897         thinkOutput[0] = NULLCHAR;
8898         MakeMove(fromX, fromY, toX, toY, promoChar);
8899         currentMove = forwardMostMove;
8900         return TRUE;
8901     }
8902 }
8903
8904 /* Load the nth game from the given file */
8905 int
8906 LoadGameFromFile(filename, n, title, useList)
8907      char *filename;
8908      int n;
8909      char *title;
8910      /*Boolean*/ int useList;
8911 {
8912     FILE *f;
8913     char buf[MSG_SIZ];
8914
8915     if (strcmp(filename, "-") == 0) {
8916         f = stdin;
8917         title = "stdin";
8918     } else {
8919         f = fopen(filename, "rb");
8920         if (f == NULL) {
8921           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8922             DisplayError(buf, errno);
8923             return FALSE;
8924         }
8925     }
8926     if (fseek(f, 0, 0) == -1) {
8927         /* f is not seekable; probably a pipe */
8928         useList = FALSE;
8929     }
8930     if (useList && n == 0) {
8931         int error = GameListBuild(f);
8932         if (error) {
8933             DisplayError(_("Cannot build game list"), error);
8934         } else if (!ListEmpty(&gameList) &&
8935                    ((ListGame *) gameList.tailPred)->number > 1) {
8936             GameListPopUp(f, title);
8937             return TRUE;
8938         }
8939         GameListDestroy();
8940         n = 1;
8941     }
8942     if (n == 0) n = 1;
8943     return LoadGame(f, n, title, FALSE);
8944 }
8945
8946
8947 void
8948 MakeRegisteredMove()
8949 {
8950     int fromX, fromY, toX, toY;
8951     char promoChar;
8952     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8953         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8954           case CMAIL_MOVE:
8955           case CMAIL_DRAW:
8956             if (appData.debugMode)
8957               fprintf(debugFP, "Restoring %s for game %d\n",
8958                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8959     
8960             thinkOutput[0] = NULLCHAR;
8961             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8962             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8963             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8964             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8965             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8966             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8967             MakeMove(fromX, fromY, toX, toY, promoChar);
8968             ShowMove(fromX, fromY, toX, toY);
8969               
8970             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8971                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8972               case MT_NONE:
8973               case MT_CHECK:
8974                 break;
8975                 
8976               case MT_CHECKMATE:
8977               case MT_STAINMATE:
8978                 if (WhiteOnMove(currentMove)) {
8979                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8980                 } else {
8981                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8982                 }
8983                 break;
8984                 
8985               case MT_STALEMATE:
8986                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8987                 break;
8988             }
8989
8990             break;
8991             
8992           case CMAIL_RESIGN:
8993             if (WhiteOnMove(currentMove)) {
8994                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8995             } else {
8996                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8997             }
8998             break;
8999             
9000           case CMAIL_ACCEPT:
9001             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9002             break;
9003               
9004           default:
9005             break;
9006         }
9007     }
9008
9009     return;
9010 }
9011
9012 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9013 int
9014 CmailLoadGame(f, gameNumber, title, useList)
9015      FILE *f;
9016      int gameNumber;
9017      char *title;
9018      int useList;
9019 {
9020     int retVal;
9021
9022     if (gameNumber > nCmailGames) {
9023         DisplayError(_("No more games in this message"), 0);
9024         return FALSE;
9025     }
9026     if (f == lastLoadGameFP) {
9027         int offset = gameNumber - lastLoadGameNumber;
9028         if (offset == 0) {
9029             cmailMsg[0] = NULLCHAR;
9030             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9031                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9032                 nCmailMovesRegistered--;
9033             }
9034             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9035             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9036                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9037             }
9038         } else {
9039             if (! RegisterMove()) return FALSE;
9040         }
9041     }
9042
9043     retVal = LoadGame(f, gameNumber, title, useList);
9044
9045     /* Make move registered during previous look at this game, if any */
9046     MakeRegisteredMove();
9047
9048     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9049         commentList[currentMove]
9050           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9051         DisplayComment(currentMove - 1, commentList[currentMove]);
9052     }
9053
9054     return retVal;
9055 }
9056
9057 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9058 int
9059 ReloadGame(offset)
9060      int offset;
9061 {
9062     int gameNumber = lastLoadGameNumber + offset;
9063     if (lastLoadGameFP == NULL) {
9064         DisplayError(_("No game has been loaded yet"), 0);
9065         return FALSE;
9066     }
9067     if (gameNumber <= 0) {
9068         DisplayError(_("Can't back up any further"), 0);
9069         return FALSE;
9070     }
9071     if (cmailMsgLoaded) {
9072         return CmailLoadGame(lastLoadGameFP, gameNumber,
9073                              lastLoadGameTitle, lastLoadGameUseList);
9074     } else {
9075         return LoadGame(lastLoadGameFP, gameNumber,
9076                         lastLoadGameTitle, lastLoadGameUseList);
9077     }
9078 }
9079
9080
9081
9082 /* Load the nth game from open file f */
9083 int
9084 LoadGame(f, gameNumber, title, useList)
9085      FILE *f;
9086      int gameNumber;
9087      char *title;
9088      int useList;
9089 {
9090     ChessMove cm;
9091     char buf[MSG_SIZ];
9092     int gn = gameNumber;
9093     ListGame *lg = NULL;
9094     int numPGNTags = 0;
9095     int err;
9096     GameMode oldGameMode;
9097     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9098
9099     if (appData.debugMode) 
9100         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9101
9102     if (gameMode == Training )
9103         SetTrainingModeOff();
9104
9105     oldGameMode = gameMode;
9106     if (gameMode != BeginningOfGame) {
9107       Reset(FALSE, TRUE);
9108     }
9109
9110     gameFileFP = f;
9111     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9112         fclose(lastLoadGameFP);
9113     }
9114
9115     if (useList) {
9116         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9117         
9118         if (lg) {
9119             fseek(f, lg->offset, 0);
9120             GameListHighlight(gameNumber);
9121             gn = 1;
9122         }
9123         else {
9124             DisplayError(_("Game number out of range"), 0);
9125             return FALSE;
9126         }
9127     } else {
9128         GameListDestroy();
9129         if (fseek(f, 0, 0) == -1) {
9130             if (f == lastLoadGameFP ?
9131                 gameNumber == lastLoadGameNumber + 1 :
9132                 gameNumber == 1) {
9133                 gn = 1;
9134             } else {
9135                 DisplayError(_("Can't seek on game file"), 0);
9136                 return FALSE;
9137             }
9138         }
9139     }
9140     lastLoadGameFP = f;
9141     lastLoadGameNumber = gameNumber;
9142     strcpy(lastLoadGameTitle, title);
9143     lastLoadGameUseList = useList;
9144
9145     yynewfile(f);
9146
9147     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9148       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9149                 lg->gameInfo.black);
9150             DisplayTitle(buf);
9151     } else if (*title != NULLCHAR) {
9152         if (gameNumber > 1) {
9153             sprintf(buf, "%s %d", title, gameNumber);
9154             DisplayTitle(buf);
9155         } else {
9156             DisplayTitle(title);
9157         }
9158     }
9159
9160     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9161         gameMode = PlayFromGameFile;
9162         ModeHighlight();
9163     }
9164
9165     currentMove = forwardMostMove = backwardMostMove = 0;
9166     CopyBoard(boards[0], initialPosition);
9167     StopClocks();
9168
9169     /*
9170      * Skip the first gn-1 games in the file.
9171      * Also skip over anything that precedes an identifiable 
9172      * start of game marker, to avoid being confused by 
9173      * garbage at the start of the file.  Currently 
9174      * recognized start of game markers are the move number "1",
9175      * the pattern "gnuchess .* game", the pattern
9176      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9177      * A game that starts with one of the latter two patterns
9178      * will also have a move number 1, possibly
9179      * following a position diagram.
9180      * 5-4-02: Let's try being more lenient and allowing a game to
9181      * start with an unnumbered move.  Does that break anything?
9182      */
9183     cm = lastLoadGameStart = (ChessMove) 0;
9184     while (gn > 0) {
9185         yyboardindex = forwardMostMove;
9186         cm = (ChessMove) yylex();
9187         switch (cm) {
9188           case (ChessMove) 0:
9189             if (cmailMsgLoaded) {
9190                 nCmailGames = CMAIL_MAX_GAMES - gn;
9191             } else {
9192                 Reset(TRUE, TRUE);
9193                 DisplayError(_("Game not found in file"), 0);
9194             }
9195             return FALSE;
9196
9197           case GNUChessGame:
9198           case XBoardGame:
9199             gn--;
9200             lastLoadGameStart = cm;
9201             break;
9202             
9203           case MoveNumberOne:
9204             switch (lastLoadGameStart) {
9205               case GNUChessGame:
9206               case XBoardGame:
9207               case PGNTag:
9208                 break;
9209               case MoveNumberOne:
9210               case (ChessMove) 0:
9211                 gn--;           /* count this game */
9212                 lastLoadGameStart = cm;
9213                 break;
9214               default:
9215                 /* impossible */
9216                 break;
9217             }
9218             break;
9219
9220           case PGNTag:
9221             switch (lastLoadGameStart) {
9222               case GNUChessGame:
9223               case PGNTag:
9224               case MoveNumberOne:
9225               case (ChessMove) 0:
9226                 gn--;           /* count this game */
9227                 lastLoadGameStart = cm;
9228                 break;
9229               case XBoardGame:
9230                 lastLoadGameStart = cm; /* game counted already */
9231                 break;
9232               default:
9233                 /* impossible */
9234                 break;
9235             }
9236             if (gn > 0) {
9237                 do {
9238                     yyboardindex = forwardMostMove;
9239                     cm = (ChessMove) yylex();
9240                 } while (cm == PGNTag || cm == Comment);
9241             }
9242             break;
9243
9244           case WhiteWins:
9245           case BlackWins:
9246           case GameIsDrawn:
9247             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9248                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9249                     != CMAIL_OLD_RESULT) {
9250                     nCmailResults ++ ;
9251                     cmailResult[  CMAIL_MAX_GAMES
9252                                 - gn - 1] = CMAIL_OLD_RESULT;
9253                 }
9254             }
9255             break;
9256
9257           case NormalMove:
9258             /* Only a NormalMove can be at the start of a game
9259              * without a position diagram. */
9260             if (lastLoadGameStart == (ChessMove) 0) {
9261               gn--;
9262               lastLoadGameStart = MoveNumberOne;
9263             }
9264             break;
9265
9266           default:
9267             break;
9268         }
9269     }
9270     
9271     if (appData.debugMode)
9272       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9273
9274     if (cm == XBoardGame) {
9275         /* Skip any header junk before position diagram and/or move 1 */
9276         for (;;) {
9277             yyboardindex = forwardMostMove;
9278             cm = (ChessMove) yylex();
9279
9280             if (cm == (ChessMove) 0 ||
9281                 cm == GNUChessGame || cm == XBoardGame) {
9282                 /* Empty game; pretend end-of-file and handle later */
9283                 cm = (ChessMove) 0;
9284                 break;
9285             }
9286
9287             if (cm == MoveNumberOne || cm == PositionDiagram ||
9288                 cm == PGNTag || cm == Comment)
9289               break;
9290         }
9291     } else if (cm == GNUChessGame) {
9292         if (gameInfo.event != NULL) {
9293             free(gameInfo.event);
9294         }
9295         gameInfo.event = StrSave(yy_text);
9296     }   
9297
9298     startedFromSetupPosition = FALSE;
9299     while (cm == PGNTag) {
9300         if (appData.debugMode) 
9301           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9302         err = ParsePGNTag(yy_text, &gameInfo);
9303         if (!err) numPGNTags++;
9304
9305         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9306         if(gameInfo.variant != oldVariant) {
9307             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9308             InitPosition(TRUE);
9309             oldVariant = gameInfo.variant;
9310             if (appData.debugMode) 
9311               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9312         }
9313
9314
9315         if (gameInfo.fen != NULL) {
9316           Board initial_position;
9317           startedFromSetupPosition = TRUE;
9318           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9319             Reset(TRUE, TRUE);
9320             DisplayError(_("Bad FEN position in file"), 0);
9321             return FALSE;
9322           }
9323           CopyBoard(boards[0], initial_position);
9324           if (blackPlaysFirst) {
9325             currentMove = forwardMostMove = backwardMostMove = 1;
9326             CopyBoard(boards[1], initial_position);
9327             strcpy(moveList[0], "");
9328             strcpy(parseList[0], "");
9329             timeRemaining[0][1] = whiteTimeRemaining;
9330             timeRemaining[1][1] = blackTimeRemaining;
9331             if (commentList[0] != NULL) {
9332               commentList[1] = commentList[0];
9333               commentList[0] = NULL;
9334             }
9335           } else {
9336             currentMove = forwardMostMove = backwardMostMove = 0;
9337           }
9338           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9339           {   int i;
9340               initialRulePlies = FENrulePlies;
9341               epStatus[forwardMostMove] = FENepStatus;
9342               for( i=0; i< nrCastlingRights; i++ )
9343                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9344           }
9345           yyboardindex = forwardMostMove;
9346           free(gameInfo.fen);
9347           gameInfo.fen = NULL;
9348         }
9349
9350         yyboardindex = forwardMostMove;
9351         cm = (ChessMove) yylex();
9352
9353         /* Handle comments interspersed among the tags */
9354         while (cm == Comment) {
9355             char *p;
9356             if (appData.debugMode) 
9357               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9358             p = yy_text;
9359             if (*p == '{' || *p == '[' || *p == '(') {
9360                 p[strlen(p) - 1] = NULLCHAR;
9361                 p++;
9362             }
9363             while (*p == '\n') p++;
9364             AppendComment(currentMove, p);
9365             yyboardindex = forwardMostMove;
9366             cm = (ChessMove) yylex();
9367         }
9368     }
9369
9370     /* don't rely on existence of Event tag since if game was
9371      * pasted from clipboard the Event tag may not exist
9372      */
9373     if (numPGNTags > 0){
9374         char *tags;
9375         if (gameInfo.variant == VariantNormal) {
9376           gameInfo.variant = StringToVariant(gameInfo.event);
9377         }
9378         if (!matchMode) {
9379           if( appData.autoDisplayTags ) {
9380             tags = PGNTags(&gameInfo);
9381             TagsPopUp(tags, CmailMsg());
9382             free(tags);
9383           }
9384         }
9385     } else {
9386         /* Make something up, but don't display it now */
9387         SetGameInfo();
9388         TagsPopDown();
9389     }
9390
9391     if (cm == PositionDiagram) {
9392         int i, j;
9393         char *p;
9394         Board initial_position;
9395
9396         if (appData.debugMode)
9397           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9398
9399         if (!startedFromSetupPosition) {
9400             p = yy_text;
9401             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9402               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9403                 switch (*p) {
9404                   case '[':
9405                   case '-':
9406                   case ' ':
9407                   case '\t':
9408                   case '\n':
9409                   case '\r':
9410                     break;
9411                   default:
9412                     initial_position[i][j++] = CharToPiece(*p);
9413                     break;
9414                 }
9415             while (*p == ' ' || *p == '\t' ||
9416                    *p == '\n' || *p == '\r') p++;
9417         
9418             if (strncmp(p, "black", strlen("black"))==0)
9419               blackPlaysFirst = TRUE;
9420             else
9421               blackPlaysFirst = FALSE;
9422             startedFromSetupPosition = TRUE;
9423         
9424             CopyBoard(boards[0], initial_position);
9425             if (blackPlaysFirst) {
9426                 currentMove = forwardMostMove = backwardMostMove = 1;
9427                 CopyBoard(boards[1], initial_position);
9428                 strcpy(moveList[0], "");
9429                 strcpy(parseList[0], "");
9430                 timeRemaining[0][1] = whiteTimeRemaining;
9431                 timeRemaining[1][1] = blackTimeRemaining;
9432                 if (commentList[0] != NULL) {
9433                     commentList[1] = commentList[0];
9434                     commentList[0] = NULL;
9435                 }
9436             } else {
9437                 currentMove = forwardMostMove = backwardMostMove = 0;
9438             }
9439         }
9440         yyboardindex = forwardMostMove;
9441         cm = (ChessMove) yylex();
9442     }
9443
9444     if (first.pr == NoProc) {
9445         StartChessProgram(&first);
9446     }
9447     InitChessProgram(&first, FALSE);
9448     SendToProgram("force\n", &first);
9449     if (startedFromSetupPosition) {
9450         SendBoard(&first, forwardMostMove);
9451     if (appData.debugMode) {
9452         fprintf(debugFP, "Load Game\n");
9453     }
9454         DisplayBothClocks();
9455     }      
9456
9457     /* [HGM] server: flag to write setup moves in broadcast file as one */
9458     loadFlag = appData.suppressLoadMoves;
9459
9460     while (cm == Comment) {
9461         char *p;
9462         if (appData.debugMode) 
9463           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9464         p = yy_text;
9465         if (*p == '{' || *p == '[' || *p == '(') {
9466             p[strlen(p) - 1] = NULLCHAR;
9467             p++;
9468         }
9469         while (*p == '\n') p++;
9470         AppendComment(currentMove, p);
9471         yyboardindex = forwardMostMove;
9472         cm = (ChessMove) yylex();
9473     }
9474
9475     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9476         cm == WhiteWins || cm == BlackWins ||
9477         cm == GameIsDrawn || cm == GameUnfinished) {
9478         DisplayMessage("", _("No moves in game"));
9479         if (cmailMsgLoaded) {
9480             if (appData.debugMode)
9481               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9482             ClearHighlights();
9483             flipView = FALSE;
9484         }
9485         DrawPosition(FALSE, boards[currentMove]);
9486         DisplayBothClocks();
9487         gameMode = EditGame;
9488         ModeHighlight();
9489         gameFileFP = NULL;
9490         cmailOldMove = 0;
9491         return TRUE;
9492     }
9493
9494     // [HGM] PV info: routine tests if comment empty
9495     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9496         DisplayComment(currentMove - 1, commentList[currentMove]);
9497     }
9498     if (!matchMode && appData.timeDelay != 0) 
9499       DrawPosition(FALSE, boards[currentMove]);
9500
9501     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9502       programStats.ok_to_send = 1;
9503     }
9504
9505     /* if the first token after the PGN tags is a move
9506      * and not move number 1, retrieve it from the parser 
9507      */
9508     if (cm != MoveNumberOne)
9509         LoadGameOneMove(cm);
9510
9511     /* load the remaining moves from the file */
9512     while (LoadGameOneMove((ChessMove)0)) {
9513       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9514       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9515     }
9516
9517     /* rewind to the start of the game */
9518     currentMove = backwardMostMove;
9519
9520     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9521
9522     if (oldGameMode == AnalyzeFile ||
9523         oldGameMode == AnalyzeMode) {
9524       AnalyzeFileEvent();
9525     }
9526
9527     if (matchMode || appData.timeDelay == 0) {
9528       ToEndEvent();
9529       gameMode = EditGame;
9530       ModeHighlight();
9531     } else if (appData.timeDelay > 0) {
9532       AutoPlayGameLoop();
9533     }
9534
9535     if (appData.debugMode) 
9536         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9537
9538     loadFlag = 0; /* [HGM] true game starts */
9539     return TRUE;
9540 }
9541
9542 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9543 int
9544 ReloadPosition(offset)
9545      int offset;
9546 {
9547     int positionNumber = lastLoadPositionNumber + offset;
9548     if (lastLoadPositionFP == NULL) {
9549         DisplayError(_("No position has been loaded yet"), 0);
9550         return FALSE;
9551     }
9552     if (positionNumber <= 0) {
9553         DisplayError(_("Can't back up any further"), 0);
9554         return FALSE;
9555     }
9556     return LoadPosition(lastLoadPositionFP, positionNumber,
9557                         lastLoadPositionTitle);
9558 }
9559
9560 /* Load the nth position from the given file */
9561 int
9562 LoadPositionFromFile(filename, n, title)
9563      char *filename;
9564      int n;
9565      char *title;
9566 {
9567     FILE *f;
9568     char buf[MSG_SIZ];
9569
9570     if (strcmp(filename, "-") == 0) {
9571         return LoadPosition(stdin, n, "stdin");
9572     } else {
9573         f = fopen(filename, "rb");
9574         if (f == NULL) {
9575             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9576             DisplayError(buf, errno);
9577             return FALSE;
9578         } else {
9579             return LoadPosition(f, n, title);
9580         }
9581     }
9582 }
9583
9584 /* Load the nth position from the given open file, and close it */
9585 int
9586 LoadPosition(f, positionNumber, title)
9587      FILE *f;
9588      int positionNumber;
9589      char *title;
9590 {
9591     char *p, line[MSG_SIZ];
9592     Board initial_position;
9593     int i, j, fenMode, pn;
9594     
9595     if (gameMode == Training )
9596         SetTrainingModeOff();
9597
9598     if (gameMode != BeginningOfGame) {
9599         Reset(FALSE, TRUE);
9600     }
9601     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9602         fclose(lastLoadPositionFP);
9603     }
9604     if (positionNumber == 0) positionNumber = 1;
9605     lastLoadPositionFP = f;
9606     lastLoadPositionNumber = positionNumber;
9607     strcpy(lastLoadPositionTitle, title);
9608     if (first.pr == NoProc) {
9609       StartChessProgram(&first);
9610       InitChessProgram(&first, FALSE);
9611     }    
9612     pn = positionNumber;
9613     if (positionNumber < 0) {
9614         /* Negative position number means to seek to that byte offset */
9615         if (fseek(f, -positionNumber, 0) == -1) {
9616             DisplayError(_("Can't seek on position file"), 0);
9617             return FALSE;
9618         };
9619         pn = 1;
9620     } else {
9621         if (fseek(f, 0, 0) == -1) {
9622             if (f == lastLoadPositionFP ?
9623                 positionNumber == lastLoadPositionNumber + 1 :
9624                 positionNumber == 1) {
9625                 pn = 1;
9626             } else {
9627                 DisplayError(_("Can't seek on position file"), 0);
9628                 return FALSE;
9629             }
9630         }
9631     }
9632     /* See if this file is FEN or old-style xboard */
9633     if (fgets(line, MSG_SIZ, f) == NULL) {
9634         DisplayError(_("Position not found in file"), 0);
9635         return FALSE;
9636     }
9637     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9638     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9639
9640     if (pn >= 2) {
9641         if (fenMode || line[0] == '#') pn--;
9642         while (pn > 0) {
9643             /* skip positions before number pn */
9644             if (fgets(line, MSG_SIZ, f) == NULL) {
9645                 Reset(TRUE, TRUE);
9646                 DisplayError(_("Position not found in file"), 0);
9647                 return FALSE;
9648             }
9649             if (fenMode || line[0] == '#') pn--;
9650         }
9651     }
9652
9653     if (fenMode) {
9654         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9655             DisplayError(_("Bad FEN position in file"), 0);
9656             return FALSE;
9657         }
9658     } else {
9659         (void) fgets(line, MSG_SIZ, f);
9660         (void) fgets(line, MSG_SIZ, f);
9661     
9662         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9663             (void) fgets(line, MSG_SIZ, f);
9664             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9665                 if (*p == ' ')
9666                   continue;
9667                 initial_position[i][j++] = CharToPiece(*p);
9668             }
9669         }
9670     
9671         blackPlaysFirst = FALSE;
9672         if (!feof(f)) {
9673             (void) fgets(line, MSG_SIZ, f);
9674             if (strncmp(line, "black", strlen("black"))==0)
9675               blackPlaysFirst = TRUE;
9676         }
9677     }
9678     startedFromSetupPosition = TRUE;
9679     
9680     SendToProgram("force\n", &first);
9681     CopyBoard(boards[0], initial_position);
9682     if (blackPlaysFirst) {
9683         currentMove = forwardMostMove = backwardMostMove = 1;
9684         strcpy(moveList[0], "");
9685         strcpy(parseList[0], "");
9686         CopyBoard(boards[1], initial_position);
9687         DisplayMessage("", _("Black to play"));
9688     } else {
9689         currentMove = forwardMostMove = backwardMostMove = 0;
9690         DisplayMessage("", _("White to play"));
9691     }
9692           /* [HGM] copy FEN attributes as well */
9693           {   int i;
9694               initialRulePlies = FENrulePlies;
9695               epStatus[forwardMostMove] = FENepStatus;
9696               for( i=0; i< nrCastlingRights; i++ )
9697                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9698           }
9699     SendBoard(&first, forwardMostMove);
9700     if (appData.debugMode) {
9701 int i, j;
9702   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9703   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9704         fprintf(debugFP, "Load Position\n");
9705     }
9706
9707     if (positionNumber > 1) {
9708         sprintf(line, "%s %d", title, positionNumber);
9709         DisplayTitle(line);
9710     } else {
9711         DisplayTitle(title);
9712     }
9713     gameMode = EditGame;
9714     ModeHighlight();
9715     ResetClocks();
9716     timeRemaining[0][1] = whiteTimeRemaining;
9717     timeRemaining[1][1] = blackTimeRemaining;
9718     DrawPosition(FALSE, boards[currentMove]);
9719    
9720     return TRUE;
9721 }
9722
9723
9724 void
9725 CopyPlayerNameIntoFileName(dest, src)
9726      char **dest, *src;
9727 {
9728     while (*src != NULLCHAR && *src != ',') {
9729         if (*src == ' ') {
9730             *(*dest)++ = '_';
9731             src++;
9732         } else {
9733             *(*dest)++ = *src++;
9734         }
9735     }
9736 }
9737
9738 char *DefaultFileName(ext)
9739      char *ext;
9740 {
9741     static char def[MSG_SIZ];
9742     char *p;
9743
9744     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9745         p = def;
9746         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9747         *p++ = '-';
9748         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9749         *p++ = '.';
9750         strcpy(p, ext);
9751     } else {
9752         def[0] = NULLCHAR;
9753     }
9754     return def;
9755 }
9756
9757 /* Save the current game to the given file */
9758 int
9759 SaveGameToFile(filename, append)
9760      char *filename;
9761      int append;
9762 {
9763     FILE *f;
9764     char buf[MSG_SIZ];
9765
9766     if (strcmp(filename, "-") == 0) {
9767         return SaveGame(stdout, 0, NULL);
9768     } else {
9769         f = fopen(filename, append ? "a" : "w");
9770         if (f == NULL) {
9771             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9772             DisplayError(buf, errno);
9773             return FALSE;
9774         } else {
9775             return SaveGame(f, 0, NULL);
9776         }
9777     }
9778 }
9779
9780 char *
9781 SavePart(str)
9782      char *str;
9783 {
9784     static char buf[MSG_SIZ];
9785     char *p;
9786     
9787     p = strchr(str, ' ');
9788     if (p == NULL) return str;
9789     strncpy(buf, str, p - str);
9790     buf[p - str] = NULLCHAR;
9791     return buf;
9792 }
9793
9794 #define PGN_MAX_LINE 75
9795
9796 #define PGN_SIDE_WHITE  0
9797 #define PGN_SIDE_BLACK  1
9798
9799 /* [AS] */
9800 static int FindFirstMoveOutOfBook( int side )
9801 {
9802     int result = -1;
9803
9804     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9805         int index = backwardMostMove;
9806         int has_book_hit = 0;
9807
9808         if( (index % 2) != side ) {
9809             index++;
9810         }
9811
9812         while( index < forwardMostMove ) {
9813             /* Check to see if engine is in book */
9814             int depth = pvInfoList[index].depth;
9815             int score = pvInfoList[index].score;
9816             int in_book = 0;
9817
9818             if( depth <= 2 ) {
9819                 in_book = 1;
9820             }
9821             else if( score == 0 && depth == 63 ) {
9822                 in_book = 1; /* Zappa */
9823             }
9824             else if( score == 2 && depth == 99 ) {
9825                 in_book = 1; /* Abrok */
9826             }
9827
9828             has_book_hit += in_book;
9829
9830             if( ! in_book ) {
9831                 result = index;
9832
9833                 break;
9834             }
9835
9836             index += 2;
9837         }
9838     }
9839
9840     return result;
9841 }
9842
9843 /* [AS] */
9844 void GetOutOfBookInfo( char * buf )
9845 {
9846     int oob[2];
9847     int i;
9848     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9849
9850     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9851     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9852
9853     *buf = '\0';
9854
9855     if( oob[0] >= 0 || oob[1] >= 0 ) {
9856         for( i=0; i<2; i++ ) {
9857             int idx = oob[i];
9858
9859             if( idx >= 0 ) {
9860                 if( i > 0 && oob[0] >= 0 ) {
9861                     strcat( buf, "   " );
9862                 }
9863
9864                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9865                 sprintf( buf+strlen(buf), "%s%.2f", 
9866                     pvInfoList[idx].score >= 0 ? "+" : "",
9867                     pvInfoList[idx].score / 100.0 );
9868             }
9869         }
9870     }
9871 }
9872
9873 /* Save game in PGN style and close the file */
9874 int
9875 SaveGamePGN(f)
9876      FILE *f;
9877 {
9878     int i, offset, linelen, newblock;
9879     time_t tm;
9880 //    char *movetext;
9881     char numtext[32];
9882     int movelen, numlen, blank;
9883     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9884
9885     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9886     
9887     tm = time((time_t *) NULL);
9888     
9889     PrintPGNTags(f, &gameInfo);
9890     
9891     if (backwardMostMove > 0 || startedFromSetupPosition) {
9892         char *fen = PositionToFEN(backwardMostMove, NULL);
9893         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9894         fprintf(f, "\n{--------------\n");
9895         PrintPosition(f, backwardMostMove);
9896         fprintf(f, "--------------}\n");
9897         free(fen);
9898     }
9899     else {
9900         /* [AS] Out of book annotation */
9901         if( appData.saveOutOfBookInfo ) {
9902             char buf[64];
9903
9904             GetOutOfBookInfo( buf );
9905
9906             if( buf[0] != '\0' ) {
9907                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9908             }
9909         }
9910
9911         fprintf(f, "\n");
9912     }
9913
9914     i = backwardMostMove;
9915     linelen = 0;
9916     newblock = TRUE;
9917
9918     while (i < forwardMostMove) {
9919         /* Print comments preceding this move */
9920         if (commentList[i] != NULL) {
9921             if (linelen > 0) fprintf(f, "\n");
9922             fprintf(f, "{\n%s}\n", commentList[i]);
9923             linelen = 0;
9924             newblock = TRUE;
9925         }
9926
9927         /* Format move number */
9928         if ((i % 2) == 0) {
9929             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9930         } else {
9931             if (newblock) {
9932                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9933             } else {
9934                 numtext[0] = NULLCHAR;
9935             }
9936         }
9937         numlen = strlen(numtext);
9938         newblock = FALSE;
9939
9940         /* Print move number */
9941         blank = linelen > 0 && numlen > 0;
9942         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9943             fprintf(f, "\n");
9944             linelen = 0;
9945             blank = 0;
9946         }
9947         if (blank) {
9948             fprintf(f, " ");
9949             linelen++;
9950         }
9951         fprintf(f, "%s", numtext);
9952         linelen += numlen;
9953
9954         /* Get move */
9955         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9956         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9957
9958         /* Print move */
9959         blank = linelen > 0 && movelen > 0;
9960         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9961             fprintf(f, "\n");
9962             linelen = 0;
9963             blank = 0;
9964         }
9965         if (blank) {
9966             fprintf(f, " ");
9967             linelen++;
9968         }
9969         fprintf(f, "%s", move_buffer);
9970         linelen += movelen;
9971
9972         /* [AS] Add PV info if present */
9973         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9974             /* [HGM] add time */
9975             char buf[MSG_SIZ]; int seconds;
9976
9977             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9978
9979             if( seconds <= 0) buf[0] = 0; else
9980             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9981                 seconds = (seconds + 4)/10; // round to full seconds
9982                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9983                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9984             }
9985
9986             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9987                 pvInfoList[i].score >= 0 ? "+" : "",
9988                 pvInfoList[i].score / 100.0,
9989                 pvInfoList[i].depth,
9990                 buf );
9991
9992             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9993
9994             /* Print score/depth */
9995             blank = linelen > 0 && movelen > 0;
9996             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9997                 fprintf(f, "\n");
9998                 linelen = 0;
9999                 blank = 0;
10000             }
10001             if (blank) {
10002                 fprintf(f, " ");
10003                 linelen++;
10004             }
10005             fprintf(f, "%s", move_buffer);
10006             linelen += movelen;
10007         }
10008
10009         i++;
10010     }
10011     
10012     /* Start a new line */
10013     if (linelen > 0) fprintf(f, "\n");
10014
10015     /* Print comments after last move */
10016     if (commentList[i] != NULL) {
10017         fprintf(f, "{\n%s}\n", commentList[i]);
10018     }
10019
10020     /* Print result */
10021     if (gameInfo.resultDetails != NULL &&
10022         gameInfo.resultDetails[0] != NULLCHAR) {
10023         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10024                 PGNResult(gameInfo.result));
10025     } else {
10026         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10027     }
10028
10029     fclose(f);
10030     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10031     return TRUE;
10032 }
10033
10034 /* Save game in old style and close the file */
10035 int
10036 SaveGameOldStyle(f)
10037      FILE *f;
10038 {
10039     int i, offset;
10040     time_t tm;
10041     
10042     tm = time((time_t *) NULL);
10043     
10044     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10045     PrintOpponents(f);
10046     
10047     if (backwardMostMove > 0 || startedFromSetupPosition) {
10048         fprintf(f, "\n[--------------\n");
10049         PrintPosition(f, backwardMostMove);
10050         fprintf(f, "--------------]\n");
10051     } else {
10052         fprintf(f, "\n");
10053     }
10054
10055     i = backwardMostMove;
10056     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10057
10058     while (i < forwardMostMove) {
10059         if (commentList[i] != NULL) {
10060             fprintf(f, "[%s]\n", commentList[i]);
10061         }
10062
10063         if ((i % 2) == 1) {
10064             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10065             i++;
10066         } else {
10067             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10068             i++;
10069             if (commentList[i] != NULL) {
10070                 fprintf(f, "\n");
10071                 continue;
10072             }
10073             if (i >= forwardMostMove) {
10074                 fprintf(f, "\n");
10075                 break;
10076             }
10077             fprintf(f, "%s\n", parseList[i]);
10078             i++;
10079         }
10080     }
10081     
10082     if (commentList[i] != NULL) {
10083         fprintf(f, "[%s]\n", commentList[i]);
10084     }
10085
10086     /* This isn't really the old style, but it's close enough */
10087     if (gameInfo.resultDetails != NULL &&
10088         gameInfo.resultDetails[0] != NULLCHAR) {
10089         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10090                 gameInfo.resultDetails);
10091     } else {
10092         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10093     }
10094
10095     fclose(f);
10096     return TRUE;
10097 }
10098
10099 /* Save the current game to open file f and close the file */
10100 int
10101 SaveGame(f, dummy, dummy2)
10102      FILE *f;
10103      int dummy;
10104      char *dummy2;
10105 {
10106     if (gameMode == EditPosition) EditPositionDone(TRUE);
10107     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10108     if (appData.oldSaveStyle)
10109       return SaveGameOldStyle(f);
10110     else
10111       return SaveGamePGN(f);
10112 }
10113
10114 /* Save the current position to the given file */
10115 int
10116 SavePositionToFile(filename)
10117      char *filename;
10118 {
10119     FILE *f;
10120     char buf[MSG_SIZ];
10121
10122     if (strcmp(filename, "-") == 0) {
10123         return SavePosition(stdout, 0, NULL);
10124     } else {
10125         f = fopen(filename, "a");
10126         if (f == NULL) {
10127             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10128             DisplayError(buf, errno);
10129             return FALSE;
10130         } else {
10131             SavePosition(f, 0, NULL);
10132             return TRUE;
10133         }
10134     }
10135 }
10136
10137 /* Save the current position to the given open file and close the file */
10138 int
10139 SavePosition(f, dummy, dummy2)
10140      FILE *f;
10141      int dummy;
10142      char *dummy2;
10143 {
10144     time_t tm;
10145     char *fen;
10146
10147     if (gameMode == EditPosition) EditPositionDone(TRUE);
10148     if (appData.oldSaveStyle) {
10149         tm = time((time_t *) NULL);
10150     
10151         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10152         PrintOpponents(f);
10153         fprintf(f, "[--------------\n");
10154         PrintPosition(f, currentMove);
10155         fprintf(f, "--------------]\n");
10156     } else {
10157         fen = PositionToFEN(currentMove, NULL);
10158         fprintf(f, "%s\n", fen);
10159         free(fen);
10160     }
10161     fclose(f);
10162     return TRUE;
10163 }
10164
10165 void
10166 ReloadCmailMsgEvent(unregister)
10167      int unregister;
10168 {
10169 #if !WIN32
10170     static char *inFilename = NULL;
10171     static char *outFilename;
10172     int i;
10173     struct stat inbuf, outbuf;
10174     int status;
10175     
10176     /* Any registered moves are unregistered if unregister is set, */
10177     /* i.e. invoked by the signal handler */
10178     if (unregister) {
10179         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10180             cmailMoveRegistered[i] = FALSE;
10181             if (cmailCommentList[i] != NULL) {
10182                 free(cmailCommentList[i]);
10183                 cmailCommentList[i] = NULL;
10184             }
10185         }
10186         nCmailMovesRegistered = 0;
10187     }
10188
10189     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10190         cmailResult[i] = CMAIL_NOT_RESULT;
10191     }
10192     nCmailResults = 0;
10193
10194     if (inFilename == NULL) {
10195         /* Because the filenames are static they only get malloced once  */
10196         /* and they never get freed                                      */
10197         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10198         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10199
10200         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10201         sprintf(outFilename, "%s.out", appData.cmailGameName);
10202     }
10203     
10204     status = stat(outFilename, &outbuf);
10205     if (status < 0) {
10206         cmailMailedMove = FALSE;
10207     } else {
10208         status = stat(inFilename, &inbuf);
10209         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10210     }
10211     
10212     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10213        counts the games, notes how each one terminated, etc.
10214        
10215        It would be nice to remove this kludge and instead gather all
10216        the information while building the game list.  (And to keep it
10217        in the game list nodes instead of having a bunch of fixed-size
10218        parallel arrays.)  Note this will require getting each game's
10219        termination from the PGN tags, as the game list builder does
10220        not process the game moves.  --mann
10221        */
10222     cmailMsgLoaded = TRUE;
10223     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10224     
10225     /* Load first game in the file or popup game menu */
10226     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10227
10228 #endif /* !WIN32 */
10229     return;
10230 }
10231
10232 int
10233 RegisterMove()
10234 {
10235     FILE *f;
10236     char string[MSG_SIZ];
10237
10238     if (   cmailMailedMove
10239         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10240         return TRUE;            /* Allow free viewing  */
10241     }
10242
10243     /* Unregister move to ensure that we don't leave RegisterMove        */
10244     /* with the move registered when the conditions for registering no   */
10245     /* longer hold                                                       */
10246     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10247         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10248         nCmailMovesRegistered --;
10249
10250         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10251           {
10252               free(cmailCommentList[lastLoadGameNumber - 1]);
10253               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10254           }
10255     }
10256
10257     if (cmailOldMove == -1) {
10258         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10259         return FALSE;
10260     }
10261
10262     if (currentMove > cmailOldMove + 1) {
10263         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10264         return FALSE;
10265     }
10266
10267     if (currentMove < cmailOldMove) {
10268         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10269         return FALSE;
10270     }
10271
10272     if (forwardMostMove > currentMove) {
10273         /* Silently truncate extra moves */
10274         TruncateGame();
10275     }
10276
10277     if (   (currentMove == cmailOldMove + 1)
10278         || (   (currentMove == cmailOldMove)
10279             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10280                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10281         if (gameInfo.result != GameUnfinished) {
10282             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10283         }
10284
10285         if (commentList[currentMove] != NULL) {
10286             cmailCommentList[lastLoadGameNumber - 1]
10287               = StrSave(commentList[currentMove]);
10288         }
10289         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10290
10291         if (appData.debugMode)
10292           fprintf(debugFP, "Saving %s for game %d\n",
10293                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10294
10295         sprintf(string,
10296                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10297         
10298         f = fopen(string, "w");
10299         if (appData.oldSaveStyle) {
10300             SaveGameOldStyle(f); /* also closes the file */
10301             
10302             sprintf(string, "%s.pos.out", appData.cmailGameName);
10303             f = fopen(string, "w");
10304             SavePosition(f, 0, NULL); /* also closes the file */
10305         } else {
10306             fprintf(f, "{--------------\n");
10307             PrintPosition(f, currentMove);
10308             fprintf(f, "--------------}\n\n");
10309             
10310             SaveGame(f, 0, NULL); /* also closes the file*/
10311         }
10312         
10313         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10314         nCmailMovesRegistered ++;
10315     } else if (nCmailGames == 1) {
10316         DisplayError(_("You have not made a move yet"), 0);
10317         return FALSE;
10318     }
10319
10320     return TRUE;
10321 }
10322
10323 void
10324 MailMoveEvent()
10325 {
10326 #if !WIN32
10327     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10328     FILE *commandOutput;
10329     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10330     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10331     int nBuffers;
10332     int i;
10333     int archived;
10334     char *arcDir;
10335
10336     if (! cmailMsgLoaded) {
10337         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10338         return;
10339     }
10340
10341     if (nCmailGames == nCmailResults) {
10342         DisplayError(_("No unfinished games"), 0);
10343         return;
10344     }
10345
10346 #if CMAIL_PROHIBIT_REMAIL
10347     if (cmailMailedMove) {
10348         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);
10349         DisplayError(msg, 0);
10350         return;
10351     }
10352 #endif
10353
10354     if (! (cmailMailedMove || RegisterMove())) return;
10355     
10356     if (   cmailMailedMove
10357         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10358         sprintf(string, partCommandString,
10359                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10360         commandOutput = popen(string, "r");
10361
10362         if (commandOutput == NULL) {
10363             DisplayError(_("Failed to invoke cmail"), 0);
10364         } else {
10365             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10366                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10367             }
10368             if (nBuffers > 1) {
10369                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10370                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10371                 nBytes = MSG_SIZ - 1;
10372             } else {
10373                 (void) memcpy(msg, buffer, nBytes);
10374             }
10375             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10376
10377             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10378                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10379
10380                 archived = TRUE;
10381                 for (i = 0; i < nCmailGames; i ++) {
10382                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10383                         archived = FALSE;
10384                     }
10385                 }
10386                 if (   archived
10387                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10388                         != NULL)) {
10389                     sprintf(buffer, "%s/%s.%s.archive",
10390                             arcDir,
10391                             appData.cmailGameName,
10392                             gameInfo.date);
10393                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10394                     cmailMsgLoaded = FALSE;
10395                 }
10396             }
10397
10398             DisplayInformation(msg);
10399             pclose(commandOutput);
10400         }
10401     } else {
10402         if ((*cmailMsg) != '\0') {
10403             DisplayInformation(cmailMsg);
10404         }
10405     }
10406
10407     return;
10408 #endif /* !WIN32 */
10409 }
10410
10411 char *
10412 CmailMsg()
10413 {
10414 #if WIN32
10415     return NULL;
10416 #else
10417     int  prependComma = 0;
10418     char number[5];
10419     char string[MSG_SIZ];       /* Space for game-list */
10420     int  i;
10421     
10422     if (!cmailMsgLoaded) return "";
10423
10424     if (cmailMailedMove) {
10425         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10426     } else {
10427         /* Create a list of games left */
10428         sprintf(string, "[");
10429         for (i = 0; i < nCmailGames; i ++) {
10430             if (! (   cmailMoveRegistered[i]
10431                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10432                 if (prependComma) {
10433                     sprintf(number, ",%d", i + 1);
10434                 } else {
10435                     sprintf(number, "%d", i + 1);
10436                     prependComma = 1;
10437                 }
10438                 
10439                 strcat(string, number);
10440             }
10441         }
10442         strcat(string, "]");
10443
10444         if (nCmailMovesRegistered + nCmailResults == 0) {
10445             switch (nCmailGames) {
10446               case 1:
10447                 sprintf(cmailMsg,
10448                         _("Still need to make move for game\n"));
10449                 break;
10450                 
10451               case 2:
10452                 sprintf(cmailMsg,
10453                         _("Still need to make moves for both games\n"));
10454                 break;
10455                 
10456               default:
10457                 sprintf(cmailMsg,
10458                         _("Still need to make moves for all %d games\n"),
10459                         nCmailGames);
10460                 break;
10461             }
10462         } else {
10463             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10464               case 1:
10465                 sprintf(cmailMsg,
10466                         _("Still need to make a move for game %s\n"),
10467                         string);
10468                 break;
10469                 
10470               case 0:
10471                 if (nCmailResults == nCmailGames) {
10472                     sprintf(cmailMsg, _("No unfinished games\n"));
10473                 } else {
10474                     sprintf(cmailMsg, _("Ready to send mail\n"));
10475                 }
10476                 break;
10477                 
10478               default:
10479                 sprintf(cmailMsg,
10480                         _("Still need to make moves for games %s\n"),
10481                         string);
10482             }
10483         }
10484     }
10485     return cmailMsg;
10486 #endif /* WIN32 */
10487 }
10488
10489 void
10490 ResetGameEvent()
10491 {
10492     if (gameMode == Training)
10493       SetTrainingModeOff();
10494
10495     Reset(TRUE, TRUE);
10496     cmailMsgLoaded = FALSE;
10497     if (appData.icsActive) {
10498       SendToICS(ics_prefix);
10499       SendToICS("refresh\n");
10500     }
10501 }
10502
10503 void
10504 ExitEvent(status)
10505      int status;
10506 {
10507     exiting++;
10508     if (exiting > 2) {
10509       /* Give up on clean exit */
10510       exit(status);
10511     }
10512     if (exiting > 1) {
10513       /* Keep trying for clean exit */
10514       return;
10515     }
10516
10517     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10518
10519     if (telnetISR != NULL) {
10520       RemoveInputSource(telnetISR);
10521     }
10522     if (icsPR != NoProc) {
10523       DestroyChildProcess(icsPR, TRUE);
10524     }
10525
10526     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10527     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10528
10529     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10530     /* make sure this other one finishes before killing it!                  */
10531     if(endingGame) { int count = 0;
10532         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10533         while(endingGame && count++ < 10) DoSleep(1);
10534         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10535     }
10536
10537     /* Kill off chess programs */
10538     if (first.pr != NoProc) {
10539         ExitAnalyzeMode();
10540         
10541         DoSleep( appData.delayBeforeQuit );
10542         SendToProgram("quit\n", &first);
10543         DoSleep( appData.delayAfterQuit );
10544         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10545     }
10546     if (second.pr != NoProc) {
10547         DoSleep( appData.delayBeforeQuit );
10548         SendToProgram("quit\n", &second);
10549         DoSleep( appData.delayAfterQuit );
10550         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10551     }
10552     if (first.isr != NULL) {
10553         RemoveInputSource(first.isr);
10554     }
10555     if (second.isr != NULL) {
10556         RemoveInputSource(second.isr);
10557     }
10558
10559     ShutDownFrontEnd();
10560     exit(status);
10561 }
10562
10563 void
10564 PauseEvent()
10565 {
10566     if (appData.debugMode)
10567         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10568     if (pausing) {
10569         pausing = FALSE;
10570         ModeHighlight();
10571         if (gameMode == MachinePlaysWhite ||
10572             gameMode == MachinePlaysBlack) {
10573             StartClocks();
10574         } else {
10575             DisplayBothClocks();
10576         }
10577         if (gameMode == PlayFromGameFile) {
10578             if (appData.timeDelay >= 0) 
10579                 AutoPlayGameLoop();
10580         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10581             Reset(FALSE, TRUE);
10582             SendToICS(ics_prefix);
10583             SendToICS("refresh\n");
10584         } else if (currentMove < forwardMostMove) {
10585             ForwardInner(forwardMostMove);
10586         }
10587         pauseExamInvalid = FALSE;
10588     } else {
10589         switch (gameMode) {
10590           default:
10591             return;
10592           case IcsExamining:
10593             pauseExamForwardMostMove = forwardMostMove;
10594             pauseExamInvalid = FALSE;
10595             /* fall through */
10596           case IcsObserving:
10597           case IcsPlayingWhite:
10598           case IcsPlayingBlack:
10599             pausing = TRUE;
10600             ModeHighlight();
10601             return;
10602           case PlayFromGameFile:
10603             (void) StopLoadGameTimer();
10604             pausing = TRUE;
10605             ModeHighlight();
10606             break;
10607           case BeginningOfGame:
10608             if (appData.icsActive) return;
10609             /* else fall through */
10610           case MachinePlaysWhite:
10611           case MachinePlaysBlack:
10612           case TwoMachinesPlay:
10613             if (forwardMostMove == 0)
10614               return;           /* don't pause if no one has moved */
10615             if ((gameMode == MachinePlaysWhite &&
10616                  !WhiteOnMove(forwardMostMove)) ||
10617                 (gameMode == MachinePlaysBlack &&
10618                  WhiteOnMove(forwardMostMove))) {
10619                 StopClocks();
10620             }
10621             pausing = TRUE;
10622             ModeHighlight();
10623             break;
10624         }
10625     }
10626 }
10627
10628 void
10629 EditCommentEvent()
10630 {
10631     char title[MSG_SIZ];
10632
10633     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10634         strcpy(title, _("Edit comment"));
10635     } else {
10636         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10637                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10638                 parseList[currentMove - 1]);
10639     }
10640
10641     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10642 }
10643
10644
10645 void
10646 EditTagsEvent()
10647 {
10648     char *tags = PGNTags(&gameInfo);
10649     EditTagsPopUp(tags);
10650     free(tags);
10651 }
10652
10653 void
10654 AnalyzeModeEvent()
10655 {
10656     if (appData.noChessProgram || gameMode == AnalyzeMode)
10657       return;
10658
10659     if (gameMode != AnalyzeFile) {
10660         if (!appData.icsEngineAnalyze) {
10661                EditGameEvent();
10662                if (gameMode != EditGame) return;
10663         }
10664         ResurrectChessProgram();
10665         SendToProgram("analyze\n", &first);
10666         first.analyzing = TRUE;
10667         /*first.maybeThinking = TRUE;*/
10668         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10669         EngineOutputPopUp();
10670     }
10671     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10672     pausing = FALSE;
10673     ModeHighlight();
10674     SetGameInfo();
10675
10676     StartAnalysisClock();
10677     GetTimeMark(&lastNodeCountTime);
10678     lastNodeCount = 0;
10679 }
10680
10681 void
10682 AnalyzeFileEvent()
10683 {
10684     if (appData.noChessProgram || gameMode == AnalyzeFile)
10685       return;
10686
10687     if (gameMode != AnalyzeMode) {
10688         EditGameEvent();
10689         if (gameMode != EditGame) return;
10690         ResurrectChessProgram();
10691         SendToProgram("analyze\n", &first);
10692         first.analyzing = TRUE;
10693         /*first.maybeThinking = TRUE;*/
10694         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10695         EngineOutputPopUp();
10696     }
10697     gameMode = AnalyzeFile;
10698     pausing = FALSE;
10699     ModeHighlight();
10700     SetGameInfo();
10701
10702     StartAnalysisClock();
10703     GetTimeMark(&lastNodeCountTime);
10704     lastNodeCount = 0;
10705 }
10706
10707 void
10708 MachineWhiteEvent()
10709 {
10710     char buf[MSG_SIZ];
10711     char *bookHit = NULL;
10712
10713     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10714       return;
10715
10716
10717     if (gameMode == PlayFromGameFile || 
10718         gameMode == TwoMachinesPlay  || 
10719         gameMode == Training         || 
10720         gameMode == AnalyzeMode      || 
10721         gameMode == EndOfGame)
10722         EditGameEvent();
10723
10724     if (gameMode == EditPosition) 
10725         EditPositionDone(TRUE);
10726
10727     if (!WhiteOnMove(currentMove)) {
10728         DisplayError(_("It is not White's turn"), 0);
10729         return;
10730     }
10731   
10732     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10733       ExitAnalyzeMode();
10734
10735     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10736         gameMode == AnalyzeFile)
10737         TruncateGame();
10738
10739     ResurrectChessProgram();    /* in case it isn't running */
10740     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10741         gameMode = MachinePlaysWhite;
10742         ResetClocks();
10743     } else
10744     gameMode = MachinePlaysWhite;
10745     pausing = FALSE;
10746     ModeHighlight();
10747     SetGameInfo();
10748     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10749     DisplayTitle(buf);
10750     if (first.sendName) {
10751       sprintf(buf, "name %s\n", gameInfo.black);
10752       SendToProgram(buf, &first);
10753     }
10754     if (first.sendTime) {
10755       if (first.useColors) {
10756         SendToProgram("black\n", &first); /*gnu kludge*/
10757       }
10758       SendTimeRemaining(&first, TRUE);
10759     }
10760     if (first.useColors) {
10761       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10762     }
10763     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10764     SetMachineThinkingEnables();
10765     first.maybeThinking = TRUE;
10766     StartClocks();
10767     firstMove = FALSE;
10768
10769     if (appData.autoFlipView && !flipView) {
10770       flipView = !flipView;
10771       DrawPosition(FALSE, NULL);
10772       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10773     }
10774
10775     if(bookHit) { // [HGM] book: simulate book reply
10776         static char bookMove[MSG_SIZ]; // a bit generous?
10777
10778         programStats.nodes = programStats.depth = programStats.time = 
10779         programStats.score = programStats.got_only_move = 0;
10780         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10781
10782         strcpy(bookMove, "move ");
10783         strcat(bookMove, bookHit);
10784         HandleMachineMove(bookMove, &first);
10785     }
10786 }
10787
10788 void
10789 MachineBlackEvent()
10790 {
10791     char buf[MSG_SIZ];
10792    char *bookHit = NULL;
10793
10794     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10795         return;
10796
10797
10798     if (gameMode == PlayFromGameFile || 
10799         gameMode == TwoMachinesPlay  || 
10800         gameMode == Training         || 
10801         gameMode == AnalyzeMode      || 
10802         gameMode == EndOfGame)
10803         EditGameEvent();
10804
10805     if (gameMode == EditPosition) 
10806         EditPositionDone(TRUE);
10807
10808     if (WhiteOnMove(currentMove)) {
10809         DisplayError(_("It is not Black's turn"), 0);
10810         return;
10811     }
10812     
10813     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10814       ExitAnalyzeMode();
10815
10816     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10817         gameMode == AnalyzeFile)
10818         TruncateGame();
10819
10820     ResurrectChessProgram();    /* in case it isn't running */
10821     gameMode = MachinePlaysBlack;
10822     pausing = FALSE;
10823     ModeHighlight();
10824     SetGameInfo();
10825     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10826     DisplayTitle(buf);
10827     if (first.sendName) {
10828       sprintf(buf, "name %s\n", gameInfo.white);
10829       SendToProgram(buf, &first);
10830     }
10831     if (first.sendTime) {
10832       if (first.useColors) {
10833         SendToProgram("white\n", &first); /*gnu kludge*/
10834       }
10835       SendTimeRemaining(&first, FALSE);
10836     }
10837     if (first.useColors) {
10838       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10839     }
10840     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10841     SetMachineThinkingEnables();
10842     first.maybeThinking = TRUE;
10843     StartClocks();
10844
10845     if (appData.autoFlipView && flipView) {
10846       flipView = !flipView;
10847       DrawPosition(FALSE, NULL);
10848       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10849     }
10850     if(bookHit) { // [HGM] book: simulate book reply
10851         static char bookMove[MSG_SIZ]; // a bit generous?
10852
10853         programStats.nodes = programStats.depth = programStats.time = 
10854         programStats.score = programStats.got_only_move = 0;
10855         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10856
10857         strcpy(bookMove, "move ");
10858         strcat(bookMove, bookHit);
10859         HandleMachineMove(bookMove, &first);
10860     }
10861 }
10862
10863
10864 void
10865 DisplayTwoMachinesTitle()
10866 {
10867     char buf[MSG_SIZ];
10868     if (appData.matchGames > 0) {
10869         if (first.twoMachinesColor[0] == 'w') {
10870             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10871                     gameInfo.white, gameInfo.black,
10872                     first.matchWins, second.matchWins,
10873                     matchGame - 1 - (first.matchWins + second.matchWins));
10874         } else {
10875             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10876                     gameInfo.white, gameInfo.black,
10877                     second.matchWins, first.matchWins,
10878                     matchGame - 1 - (first.matchWins + second.matchWins));
10879         }
10880     } else {
10881         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10882     }
10883     DisplayTitle(buf);
10884 }
10885
10886 void
10887 TwoMachinesEvent P((void))
10888 {
10889     int i;
10890     char buf[MSG_SIZ];
10891     ChessProgramState *onmove;
10892     char *bookHit = NULL;
10893     
10894     if (appData.noChessProgram) return;
10895
10896     switch (gameMode) {
10897       case TwoMachinesPlay:
10898         return;
10899       case MachinePlaysWhite:
10900       case MachinePlaysBlack:
10901         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10902             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10903             return;
10904         }
10905         /* fall through */
10906       case BeginningOfGame:
10907       case PlayFromGameFile:
10908       case EndOfGame:
10909         EditGameEvent();
10910         if (gameMode != EditGame) return;
10911         break;
10912       case EditPosition:
10913         EditPositionDone(TRUE);
10914         break;
10915       case AnalyzeMode:
10916       case AnalyzeFile:
10917         ExitAnalyzeMode();
10918         break;
10919       case EditGame:
10920       default:
10921         break;
10922     }
10923
10924     forwardMostMove = currentMove;
10925     ResurrectChessProgram();    /* in case first program isn't running */
10926
10927     if (second.pr == NULL) {
10928         StartChessProgram(&second);
10929         if (second.protocolVersion == 1) {
10930           TwoMachinesEventIfReady();
10931         } else {
10932           /* kludge: allow timeout for initial "feature" command */
10933           FreezeUI();
10934           DisplayMessage("", _("Starting second chess program"));
10935           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10936         }
10937         return;
10938     }
10939     DisplayMessage("", "");
10940     InitChessProgram(&second, FALSE);
10941     SendToProgram("force\n", &second);
10942     if (startedFromSetupPosition) {
10943         SendBoard(&second, backwardMostMove);
10944     if (appData.debugMode) {
10945         fprintf(debugFP, "Two Machines\n");
10946     }
10947     }
10948     for (i = backwardMostMove; i < forwardMostMove; i++) {
10949         SendMoveToProgram(i, &second);
10950     }
10951
10952     gameMode = TwoMachinesPlay;
10953     pausing = FALSE;
10954     ModeHighlight();
10955     SetGameInfo();
10956     DisplayTwoMachinesTitle();
10957     firstMove = TRUE;
10958     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10959         onmove = &first;
10960     } else {
10961         onmove = &second;
10962     }
10963
10964     SendToProgram(first.computerString, &first);
10965     if (first.sendName) {
10966       sprintf(buf, "name %s\n", second.tidy);
10967       SendToProgram(buf, &first);
10968     }
10969     SendToProgram(second.computerString, &second);
10970     if (second.sendName) {
10971       sprintf(buf, "name %s\n", first.tidy);
10972       SendToProgram(buf, &second);
10973     }
10974
10975     ResetClocks();
10976     if (!first.sendTime || !second.sendTime) {
10977         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10978         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10979     }
10980     if (onmove->sendTime) {
10981       if (onmove->useColors) {
10982         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10983       }
10984       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10985     }
10986     if (onmove->useColors) {
10987       SendToProgram(onmove->twoMachinesColor, onmove);
10988     }
10989     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10990 //    SendToProgram("go\n", onmove);
10991     onmove->maybeThinking = TRUE;
10992     SetMachineThinkingEnables();
10993
10994     StartClocks();
10995
10996     if(bookHit) { // [HGM] book: simulate book reply
10997         static char bookMove[MSG_SIZ]; // a bit generous?
10998
10999         programStats.nodes = programStats.depth = programStats.time = 
11000         programStats.score = programStats.got_only_move = 0;
11001         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11002
11003         strcpy(bookMove, "move ");
11004         strcat(bookMove, bookHit);
11005         savedMessage = bookMove; // args for deferred call
11006         savedState = onmove;
11007         ScheduleDelayedEvent(DeferredBookMove, 1);
11008     }
11009 }
11010
11011 void
11012 TrainingEvent()
11013 {
11014     if (gameMode == Training) {
11015       SetTrainingModeOff();
11016       gameMode = PlayFromGameFile;
11017       DisplayMessage("", _("Training mode off"));
11018     } else {
11019       gameMode = Training;
11020       animateTraining = appData.animate;
11021
11022       /* make sure we are not already at the end of the game */
11023       if (currentMove < forwardMostMove) {
11024         SetTrainingModeOn();
11025         DisplayMessage("", _("Training mode on"));
11026       } else {
11027         gameMode = PlayFromGameFile;
11028         DisplayError(_("Already at end of game"), 0);
11029       }
11030     }
11031     ModeHighlight();
11032 }
11033
11034 void
11035 IcsClientEvent()
11036 {
11037     if (!appData.icsActive) return;
11038     switch (gameMode) {
11039       case IcsPlayingWhite:
11040       case IcsPlayingBlack:
11041       case IcsObserving:
11042       case IcsIdle:
11043       case BeginningOfGame:
11044       case IcsExamining:
11045         return;
11046
11047       case EditGame:
11048         break;
11049
11050       case EditPosition:
11051         EditPositionDone(TRUE);
11052         break;
11053
11054       case AnalyzeMode:
11055       case AnalyzeFile:
11056         ExitAnalyzeMode();
11057         break;
11058         
11059       default:
11060         EditGameEvent();
11061         break;
11062     }
11063
11064     gameMode = IcsIdle;
11065     ModeHighlight();
11066     return;
11067 }
11068
11069
11070 void
11071 EditGameEvent()
11072 {
11073     int i;
11074
11075     switch (gameMode) {
11076       case Training:
11077         SetTrainingModeOff();
11078         break;
11079       case MachinePlaysWhite:
11080       case MachinePlaysBlack:
11081       case BeginningOfGame:
11082         SendToProgram("force\n", &first);
11083         SetUserThinkingEnables();
11084         break;
11085       case PlayFromGameFile:
11086         (void) StopLoadGameTimer();
11087         if (gameFileFP != NULL) {
11088             gameFileFP = NULL;
11089         }
11090         break;
11091       case EditPosition:
11092         EditPositionDone(TRUE);
11093         break;
11094       case AnalyzeMode:
11095       case AnalyzeFile:
11096         ExitAnalyzeMode();
11097         SendToProgram("force\n", &first);
11098         break;
11099       case TwoMachinesPlay:
11100         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11101         ResurrectChessProgram();
11102         SetUserThinkingEnables();
11103         break;
11104       case EndOfGame:
11105         ResurrectChessProgram();
11106         break;
11107       case IcsPlayingBlack:
11108       case IcsPlayingWhite:
11109         DisplayError(_("Warning: You are still playing a game"), 0);
11110         break;
11111       case IcsObserving:
11112         DisplayError(_("Warning: You are still observing a game"), 0);
11113         break;
11114       case IcsExamining:
11115         DisplayError(_("Warning: You are still examining a game"), 0);
11116         break;
11117       case IcsIdle:
11118         break;
11119       case EditGame:
11120       default:
11121         return;
11122     }
11123     
11124     pausing = FALSE;
11125     StopClocks();
11126     first.offeredDraw = second.offeredDraw = 0;
11127
11128     if (gameMode == PlayFromGameFile) {
11129         whiteTimeRemaining = timeRemaining[0][currentMove];
11130         blackTimeRemaining = timeRemaining[1][currentMove];
11131         DisplayTitle("");
11132     }
11133
11134     if (gameMode == MachinePlaysWhite ||
11135         gameMode == MachinePlaysBlack ||
11136         gameMode == TwoMachinesPlay ||
11137         gameMode == EndOfGame) {
11138         i = forwardMostMove;
11139         while (i > currentMove) {
11140             SendToProgram("undo\n", &first);
11141             i--;
11142         }
11143         whiteTimeRemaining = timeRemaining[0][currentMove];
11144         blackTimeRemaining = timeRemaining[1][currentMove];
11145         DisplayBothClocks();
11146         if (whiteFlag || blackFlag) {
11147             whiteFlag = blackFlag = 0;
11148         }
11149         DisplayTitle("");
11150     }           
11151     
11152     gameMode = EditGame;
11153     ModeHighlight();
11154     SetGameInfo();
11155 }
11156
11157
11158 void
11159 EditPositionEvent()
11160 {
11161     if (gameMode == EditPosition) {
11162         EditGameEvent();
11163         return;
11164     }
11165     
11166     EditGameEvent();
11167     if (gameMode != EditGame) return;
11168     
11169     gameMode = EditPosition;
11170     ModeHighlight();
11171     SetGameInfo();
11172     if (currentMove > 0)
11173       CopyBoard(boards[0], boards[currentMove]);
11174     
11175     blackPlaysFirst = !WhiteOnMove(currentMove);
11176     ResetClocks();
11177     currentMove = forwardMostMove = backwardMostMove = 0;
11178     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11179     DisplayMove(-1);
11180 }
11181
11182 void
11183 ExitAnalyzeMode()
11184 {
11185     /* [DM] icsEngineAnalyze - possible call from other functions */
11186     if (appData.icsEngineAnalyze) {
11187         appData.icsEngineAnalyze = FALSE;
11188
11189         DisplayMessage("",_("Close ICS engine analyze..."));
11190     }
11191     if (first.analysisSupport && first.analyzing) {
11192       SendToProgram("exit\n", &first);
11193       first.analyzing = FALSE;
11194     }
11195     thinkOutput[0] = NULLCHAR;
11196 }
11197
11198 void
11199 EditPositionDone(Boolean fakeRights)
11200 {
11201     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11202
11203     startedFromSetupPosition = TRUE;
11204     InitChessProgram(&first, FALSE);
11205     if(fakeRights)  
11206       { /* don't do this if we just pasted FEN */
11207         castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11208         if(boards[0][0][BOARD_WIDTH>>1] == king) 
11209           {
11210             castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11211             castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11212           } 
11213         else 
11214           castlingRights[0][2] = -1;
11215         if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) 
11216           {
11217             castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11218             castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11219           } 
11220         else 
11221           castlingRights[0][5] = -1;
11222       }
11223     SendToProgram("force\n", &first);
11224     if (blackPlaysFirst) {
11225         strcpy(moveList[0], "");
11226         strcpy(parseList[0], "");
11227         currentMove = forwardMostMove = backwardMostMove = 1;
11228         CopyBoard(boards[1], boards[0]);
11229         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11230         { int i;
11231           epStatus[1] = epStatus[0];
11232           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11233         }
11234     } else {
11235         currentMove = forwardMostMove = backwardMostMove = 0;
11236     }
11237     SendBoard(&first, forwardMostMove);
11238     if (appData.debugMode) {
11239         fprintf(debugFP, "EditPosDone\n");
11240     }
11241     DisplayTitle("");
11242     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11243     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11244     gameMode = EditGame;
11245     ModeHighlight();
11246     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11247     ClearHighlights(); /* [AS] */
11248 }
11249
11250 /* Pause for `ms' milliseconds */
11251 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11252 void
11253 TimeDelay(ms)
11254      long ms;
11255 {
11256     TimeMark m1, m2;
11257
11258     GetTimeMark(&m1);
11259     do {
11260         GetTimeMark(&m2);
11261     } while (SubtractTimeMarks(&m2, &m1) < ms);
11262 }
11263
11264 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11265 void
11266 SendMultiLineToICS(buf)
11267      char *buf;
11268 {
11269     char temp[MSG_SIZ+1], *p;
11270     int len;
11271
11272     len = strlen(buf);
11273     if (len > MSG_SIZ)
11274       len = MSG_SIZ;
11275   
11276     strncpy(temp, buf, len);
11277     temp[len] = 0;
11278
11279     p = temp;
11280     while (*p) {
11281         if (*p == '\n' || *p == '\r')
11282           *p = ' ';
11283         ++p;
11284     }
11285
11286     strcat(temp, "\n");
11287     SendToICS(temp);
11288     SendToPlayer(temp, strlen(temp));
11289 }
11290
11291 void
11292 SetWhiteToPlayEvent()
11293 {
11294     if (gameMode == EditPosition) {
11295         blackPlaysFirst = FALSE;
11296         DisplayBothClocks();    /* works because currentMove is 0 */
11297     } else if (gameMode == IcsExamining) {
11298         SendToICS(ics_prefix);
11299         SendToICS("tomove white\n");
11300     }
11301 }
11302
11303 void
11304 SetBlackToPlayEvent()
11305 {
11306     if (gameMode == EditPosition) {
11307         blackPlaysFirst = TRUE;
11308         currentMove = 1;        /* kludge */
11309         DisplayBothClocks();
11310         currentMove = 0;
11311     } else if (gameMode == IcsExamining) {
11312         SendToICS(ics_prefix);
11313         SendToICS("tomove black\n");
11314     }
11315 }
11316
11317 void
11318 EditPositionMenuEvent(selection, x, y)
11319      ChessSquare selection;
11320      int x, y;
11321 {
11322     char buf[MSG_SIZ];
11323     ChessSquare piece = boards[0][y][x];
11324
11325     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11326
11327     switch (selection) {
11328       case ClearBoard:
11329         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11330             SendToICS(ics_prefix);
11331             SendToICS("bsetup clear\n");
11332         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11333             SendToICS(ics_prefix);
11334             SendToICS("clearboard\n");
11335         } else {
11336             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11337                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11338                 for (y = 0; y < BOARD_HEIGHT; y++) {
11339                     if (gameMode == IcsExamining) {
11340                         if (boards[currentMove][y][x] != EmptySquare) {
11341                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11342                                     AAA + x, ONE + y);
11343                             SendToICS(buf);
11344                         }
11345                     } else {
11346                         boards[0][y][x] = p;
11347                     }
11348                 }
11349             }
11350         }
11351         if (gameMode == EditPosition) {
11352             DrawPosition(FALSE, boards[0]);
11353         }
11354         break;
11355
11356       case WhitePlay:
11357         SetWhiteToPlayEvent();
11358         break;
11359
11360       case BlackPlay:
11361         SetBlackToPlayEvent();
11362         break;
11363
11364       case EmptySquare:
11365         if (gameMode == IcsExamining) {
11366             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11367             SendToICS(buf);
11368         } else {
11369             boards[0][y][x] = EmptySquare;
11370             DrawPosition(FALSE, boards[0]);
11371         }
11372         break;
11373
11374       case PromotePiece:
11375         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11376            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11377             selection = (ChessSquare) (PROMOTED piece);
11378         } else if(piece == EmptySquare) selection = WhiteSilver;
11379         else selection = (ChessSquare)((int)piece - 1);
11380         goto defaultlabel;
11381
11382       case DemotePiece:
11383         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11384            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11385             selection = (ChessSquare) (DEMOTED piece);
11386         } else if(piece == EmptySquare) selection = BlackSilver;
11387         else selection = (ChessSquare)((int)piece + 1);       
11388         goto defaultlabel;
11389
11390       case WhiteQueen:
11391       case BlackQueen:
11392         if(gameInfo.variant == VariantShatranj ||
11393            gameInfo.variant == VariantXiangqi  ||
11394            gameInfo.variant == VariantCourier    )
11395             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11396         goto defaultlabel;
11397
11398       case WhiteKing:
11399       case BlackKing:
11400         if(gameInfo.variant == VariantXiangqi)
11401             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11402         if(gameInfo.variant == VariantKnightmate)
11403             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11404       default:
11405         defaultlabel:
11406         if (gameMode == IcsExamining) {
11407             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11408                     PieceToChar(selection), AAA + x, ONE + y);
11409             SendToICS(buf);
11410         } else {
11411             boards[0][y][x] = selection;
11412             DrawPosition(FALSE, boards[0]);
11413         }
11414         break;
11415     }
11416 }
11417
11418
11419 void
11420 DropMenuEvent(selection, x, y)
11421      ChessSquare selection;
11422      int x, y;
11423 {
11424     ChessMove moveType;
11425
11426     switch (gameMode) {
11427       case IcsPlayingWhite:
11428       case MachinePlaysBlack:
11429         if (!WhiteOnMove(currentMove)) {
11430             DisplayMoveError(_("It is Black's turn"));
11431             return;
11432         }
11433         moveType = WhiteDrop;
11434         break;
11435       case IcsPlayingBlack:
11436       case MachinePlaysWhite:
11437         if (WhiteOnMove(currentMove)) {
11438             DisplayMoveError(_("It is White's turn"));
11439             return;
11440         }
11441         moveType = BlackDrop;
11442         break;
11443       case EditGame:
11444         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11445         break;
11446       default:
11447         return;
11448     }
11449
11450     if (moveType == BlackDrop && selection < BlackPawn) {
11451       selection = (ChessSquare) ((int) selection
11452                                  + (int) BlackPawn - (int) WhitePawn);
11453     }
11454     if (boards[currentMove][y][x] != EmptySquare) {
11455         DisplayMoveError(_("That square is occupied"));
11456         return;
11457     }
11458
11459     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11460 }
11461
11462 void
11463 AcceptEvent()
11464 {
11465     /* Accept a pending offer of any kind from opponent */
11466     
11467     if (appData.icsActive) {
11468         SendToICS(ics_prefix);
11469         SendToICS("accept\n");
11470     } else if (cmailMsgLoaded) {
11471         if (currentMove == cmailOldMove &&
11472             commentList[cmailOldMove] != NULL &&
11473             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11474                    "Black offers a draw" : "White offers a draw")) {
11475             TruncateGame();
11476             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11477             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11478         } else {
11479             DisplayError(_("There is no pending offer on this move"), 0);
11480             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11481         }
11482     } else {
11483         /* Not used for offers from chess program */
11484     }
11485 }
11486
11487 void
11488 DeclineEvent()
11489 {
11490     /* Decline a pending offer of any kind from opponent */
11491     
11492     if (appData.icsActive) {
11493         SendToICS(ics_prefix);
11494         SendToICS("decline\n");
11495     } else if (cmailMsgLoaded) {
11496         if (currentMove == cmailOldMove &&
11497             commentList[cmailOldMove] != NULL &&
11498             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11499                    "Black offers a draw" : "White offers a draw")) {
11500 #ifdef NOTDEF
11501             AppendComment(cmailOldMove, "Draw declined");
11502             DisplayComment(cmailOldMove - 1, "Draw declined");
11503 #endif /*NOTDEF*/
11504         } else {
11505             DisplayError(_("There is no pending offer on this move"), 0);
11506         }
11507     } else {
11508         /* Not used for offers from chess program */
11509     }
11510 }
11511
11512 void
11513 RematchEvent()
11514 {
11515     /* Issue ICS rematch command */
11516     if (appData.icsActive) {
11517         SendToICS(ics_prefix);
11518         SendToICS("rematch\n");
11519     }
11520 }
11521
11522 void
11523 CallFlagEvent()
11524 {
11525     /* Call your opponent's flag (claim a win on time) */
11526     if (appData.icsActive) {
11527         SendToICS(ics_prefix);
11528         SendToICS("flag\n");
11529     } else {
11530         switch (gameMode) {
11531           default:
11532             return;
11533           case MachinePlaysWhite:
11534             if (whiteFlag) {
11535                 if (blackFlag)
11536                   GameEnds(GameIsDrawn, "Both players ran out of time",
11537                            GE_PLAYER);
11538                 else
11539                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11540             } else {
11541                 DisplayError(_("Your opponent is not out of time"), 0);
11542             }
11543             break;
11544           case MachinePlaysBlack:
11545             if (blackFlag) {
11546                 if (whiteFlag)
11547                   GameEnds(GameIsDrawn, "Both players ran out of time",
11548                            GE_PLAYER);
11549                 else
11550                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11551             } else {
11552                 DisplayError(_("Your opponent is not out of time"), 0);
11553             }
11554             break;
11555         }
11556     }
11557 }
11558
11559 void
11560 DrawEvent()
11561 {
11562     /* Offer draw or accept pending draw offer from opponent */
11563     
11564     if (appData.icsActive) {
11565         /* Note: tournament rules require draw offers to be
11566            made after you make your move but before you punch
11567            your clock.  Currently ICS doesn't let you do that;
11568            instead, you immediately punch your clock after making
11569            a move, but you can offer a draw at any time. */
11570         
11571         SendToICS(ics_prefix);
11572         SendToICS("draw\n");
11573     } else if (cmailMsgLoaded) {
11574         if (currentMove == cmailOldMove &&
11575             commentList[cmailOldMove] != NULL &&
11576             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11577                    "Black offers a draw" : "White offers a draw")) {
11578             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11579             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11580         } else if (currentMove == cmailOldMove + 1) {
11581             char *offer = WhiteOnMove(cmailOldMove) ?
11582               "White offers a draw" : "Black offers a draw";
11583             AppendComment(currentMove, offer);
11584             DisplayComment(currentMove - 1, offer);
11585             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11586         } else {
11587             DisplayError(_("You must make your move before offering a draw"), 0);
11588             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11589         }
11590     } else if (first.offeredDraw) {
11591         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11592     } else {
11593         if (first.sendDrawOffers) {
11594             SendToProgram("draw\n", &first);
11595             userOfferedDraw = TRUE;
11596         }
11597     }
11598 }
11599
11600 void
11601 AdjournEvent()
11602 {
11603     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11604     
11605     if (appData.icsActive) {
11606         SendToICS(ics_prefix);
11607         SendToICS("adjourn\n");
11608     } else {
11609         /* Currently GNU Chess doesn't offer or accept Adjourns */
11610     }
11611 }
11612
11613
11614 void
11615 AbortEvent()
11616 {
11617     /* Offer Abort or accept pending Abort offer from opponent */
11618     
11619     if (appData.icsActive) {
11620         SendToICS(ics_prefix);
11621         SendToICS("abort\n");
11622     } else {
11623         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11624     }
11625 }
11626
11627 void
11628 ResignEvent()
11629 {
11630     /* Resign.  You can do this even if it's not your turn. */
11631     
11632     if (appData.icsActive) {
11633         SendToICS(ics_prefix);
11634         SendToICS("resign\n");
11635     } else {
11636         switch (gameMode) {
11637           case MachinePlaysWhite:
11638             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11639             break;
11640           case MachinePlaysBlack:
11641             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11642             break;
11643           case EditGame:
11644             if (cmailMsgLoaded) {
11645                 TruncateGame();
11646                 if (WhiteOnMove(cmailOldMove)) {
11647                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11648                 } else {
11649                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11650                 }
11651                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11652             }
11653             break;
11654           default:
11655             break;
11656         }
11657     }
11658 }
11659
11660
11661 void
11662 StopObservingEvent()
11663 {
11664     /* Stop observing current games */
11665     SendToICS(ics_prefix);
11666     SendToICS("unobserve\n");
11667 }
11668
11669 void
11670 StopExaminingEvent()
11671 {
11672     /* Stop observing current game */
11673     SendToICS(ics_prefix);
11674     SendToICS("unexamine\n");
11675 }
11676
11677 void
11678 ForwardInner(target)
11679      int target;
11680 {
11681     int limit;
11682
11683     if (appData.debugMode)
11684         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11685                 target, currentMove, forwardMostMove);
11686
11687     if (gameMode == EditPosition)
11688       return;
11689
11690     if (gameMode == PlayFromGameFile && !pausing)
11691       PauseEvent();
11692     
11693     if (gameMode == IcsExamining && pausing)
11694       limit = pauseExamForwardMostMove;
11695     else
11696       limit = forwardMostMove;
11697     
11698     if (target > limit) target = limit;
11699
11700     if (target > 0 && moveList[target - 1][0]) {
11701         int fromX, fromY, toX, toY;
11702         toX = moveList[target - 1][2] - AAA;
11703         toY = moveList[target - 1][3] - ONE;
11704         if (moveList[target - 1][1] == '@') {
11705             if (appData.highlightLastMove) {
11706                 SetHighlights(-1, -1, toX, toY);
11707             }
11708         } else {
11709             fromX = moveList[target - 1][0] - AAA;
11710             fromY = moveList[target - 1][1] - ONE;
11711             if (target == currentMove + 1) {
11712                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11713             }
11714             if (appData.highlightLastMove) {
11715                 SetHighlights(fromX, fromY, toX, toY);
11716             }
11717         }
11718     }
11719     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11720         gameMode == Training || gameMode == PlayFromGameFile || 
11721         gameMode == AnalyzeFile) {
11722         while (currentMove < target) {
11723             SendMoveToProgram(currentMove++, &first);
11724         }
11725     } else {
11726         currentMove = target;
11727     }
11728     
11729     if (gameMode == EditGame || gameMode == EndOfGame) {
11730         whiteTimeRemaining = timeRemaining[0][currentMove];
11731         blackTimeRemaining = timeRemaining[1][currentMove];
11732     }
11733     DisplayBothClocks();
11734     DisplayMove(currentMove - 1);
11735     DrawPosition(FALSE, boards[currentMove]);
11736     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11737     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11738         DisplayComment(currentMove - 1, commentList[currentMove]);
11739     }
11740 }
11741
11742
11743 void
11744 ForwardEvent()
11745 {
11746     if (gameMode == IcsExamining && !pausing) {
11747         SendToICS(ics_prefix);
11748         SendToICS("forward\n");
11749     } else {
11750         ForwardInner(currentMove + 1);
11751     }
11752 }
11753
11754 void
11755 ToEndEvent()
11756 {
11757     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11758         /* to optimze, we temporarily turn off analysis mode while we feed
11759          * the remaining moves to the engine. Otherwise we get analysis output
11760          * after each move.
11761          */ 
11762         if (first.analysisSupport) {
11763           SendToProgram("exit\nforce\n", &first);
11764           first.analyzing = FALSE;
11765         }
11766     }
11767         
11768     if (gameMode == IcsExamining && !pausing) {
11769         SendToICS(ics_prefix);
11770         SendToICS("forward 999999\n");
11771     } else {
11772         ForwardInner(forwardMostMove);
11773     }
11774
11775     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11776         /* we have fed all the moves, so reactivate analysis mode */
11777         SendToProgram("analyze\n", &first);
11778         first.analyzing = TRUE;
11779         /*first.maybeThinking = TRUE;*/
11780         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11781     }
11782 }
11783
11784 void
11785 BackwardInner(target)
11786      int target;
11787 {
11788     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11789
11790     if (appData.debugMode)
11791         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11792                 target, currentMove, forwardMostMove);
11793
11794     if (gameMode == EditPosition) return;
11795     if (currentMove <= backwardMostMove) {
11796         ClearHighlights();
11797         DrawPosition(full_redraw, boards[currentMove]);
11798         return;
11799     }
11800     if (gameMode == PlayFromGameFile && !pausing)
11801       PauseEvent();
11802     
11803     if (moveList[target][0]) {
11804         int fromX, fromY, toX, toY;
11805         toX = moveList[target][2] - AAA;
11806         toY = moveList[target][3] - ONE;
11807         if (moveList[target][1] == '@') {
11808             if (appData.highlightLastMove) {
11809                 SetHighlights(-1, -1, toX, toY);
11810             }
11811         } else {
11812             fromX = moveList[target][0] - AAA;
11813             fromY = moveList[target][1] - ONE;
11814             if (target == currentMove - 1) {
11815                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11816             }
11817             if (appData.highlightLastMove) {
11818                 SetHighlights(fromX, fromY, toX, toY);
11819             }
11820         }
11821     }
11822     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11823         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11824         while (currentMove > target) {
11825             SendToProgram("undo\n", &first);
11826             currentMove--;
11827         }
11828     } else {
11829         currentMove = target;
11830     }
11831     
11832     if (gameMode == EditGame || gameMode == EndOfGame) {
11833         whiteTimeRemaining = timeRemaining[0][currentMove];
11834         blackTimeRemaining = timeRemaining[1][currentMove];
11835     }
11836     DisplayBothClocks();
11837     DisplayMove(currentMove - 1);
11838     DrawPosition(full_redraw, boards[currentMove]);
11839     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11840     // [HGM] PV info: routine tests if comment empty
11841     DisplayComment(currentMove - 1, commentList[currentMove]);
11842 }
11843
11844 void
11845 BackwardEvent()
11846 {
11847     if (gameMode == IcsExamining && !pausing) {
11848         SendToICS(ics_prefix);
11849         SendToICS("backward\n");
11850     } else {
11851         BackwardInner(currentMove - 1);
11852     }
11853 }
11854
11855 void
11856 ToStartEvent()
11857 {
11858     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11859         /* to optimize, we temporarily turn off analysis mode while we undo
11860          * all the moves. Otherwise we get analysis output after each undo.
11861          */ 
11862         if (first.analysisSupport) {
11863           SendToProgram("exit\nforce\n", &first);
11864           first.analyzing = FALSE;
11865         }
11866     }
11867
11868     if (gameMode == IcsExamining && !pausing) {
11869         SendToICS(ics_prefix);
11870         SendToICS("backward 999999\n");
11871     } else {
11872         BackwardInner(backwardMostMove);
11873     }
11874
11875     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11876         /* we have fed all the moves, so reactivate analysis mode */
11877         SendToProgram("analyze\n", &first);
11878         first.analyzing = TRUE;
11879         /*first.maybeThinking = TRUE;*/
11880         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11881     }
11882 }
11883
11884 void
11885 ToNrEvent(int to)
11886 {
11887   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11888   if (to >= forwardMostMove) to = forwardMostMove;
11889   if (to <= backwardMostMove) to = backwardMostMove;
11890   if (to < currentMove) {
11891     BackwardInner(to);
11892   } else {
11893     ForwardInner(to);
11894   }
11895 }
11896
11897 void
11898 RevertEvent()
11899 {
11900     if (gameMode != IcsExamining) {
11901         DisplayError(_("You are not examining a game"), 0);
11902         return;
11903     }
11904     if (pausing) {
11905         DisplayError(_("You can't revert while pausing"), 0);
11906         return;
11907     }
11908     SendToICS(ics_prefix);
11909     SendToICS("revert\n");
11910 }
11911
11912 void
11913 RetractMoveEvent()
11914 {
11915     switch (gameMode) {
11916       case MachinePlaysWhite:
11917       case MachinePlaysBlack:
11918         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11919             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11920             return;
11921         }
11922         if (forwardMostMove < 2) return;
11923         currentMove = forwardMostMove = forwardMostMove - 2;
11924         whiteTimeRemaining = timeRemaining[0][currentMove];
11925         blackTimeRemaining = timeRemaining[1][currentMove];
11926         DisplayBothClocks();
11927         DisplayMove(currentMove - 1);
11928         ClearHighlights();/*!! could figure this out*/
11929         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11930         SendToProgram("remove\n", &first);
11931         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11932         break;
11933
11934       case BeginningOfGame:
11935       default:
11936         break;
11937
11938       case IcsPlayingWhite:
11939       case IcsPlayingBlack:
11940         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11941             SendToICS(ics_prefix);
11942             SendToICS("takeback 2\n");
11943         } else {
11944             SendToICS(ics_prefix);
11945             SendToICS("takeback 1\n");
11946         }
11947         break;
11948     }
11949 }
11950
11951 void
11952 MoveNowEvent()
11953 {
11954     ChessProgramState *cps;
11955
11956     switch (gameMode) {
11957       case MachinePlaysWhite:
11958         if (!WhiteOnMove(forwardMostMove)) {
11959             DisplayError(_("It is your turn"), 0);
11960             return;
11961         }
11962         cps = &first;
11963         break;
11964       case MachinePlaysBlack:
11965         if (WhiteOnMove(forwardMostMove)) {
11966             DisplayError(_("It is your turn"), 0);
11967             return;
11968         }
11969         cps = &first;
11970         break;
11971       case TwoMachinesPlay:
11972         if (WhiteOnMove(forwardMostMove) ==
11973             (first.twoMachinesColor[0] == 'w')) {
11974             cps = &first;
11975         } else {
11976             cps = &second;
11977         }
11978         break;
11979       case BeginningOfGame:
11980       default:
11981         return;
11982     }
11983     SendToProgram("?\n", cps);
11984 }
11985
11986 void
11987 TruncateGameEvent()
11988 {
11989     EditGameEvent();
11990     if (gameMode != EditGame) return;
11991     TruncateGame();
11992 }
11993
11994 void
11995 TruncateGame()
11996 {
11997     if (forwardMostMove > currentMove) {
11998         if (gameInfo.resultDetails != NULL) {
11999             free(gameInfo.resultDetails);
12000             gameInfo.resultDetails = NULL;
12001             gameInfo.result = GameUnfinished;
12002         }
12003         forwardMostMove = currentMove;
12004         HistorySet(parseList, backwardMostMove, forwardMostMove,
12005                    currentMove-1);
12006     }
12007 }
12008
12009 void
12010 HintEvent()
12011 {
12012     if (appData.noChessProgram) return;
12013     switch (gameMode) {
12014       case MachinePlaysWhite:
12015         if (WhiteOnMove(forwardMostMove)) {
12016             DisplayError(_("Wait until your turn"), 0);
12017             return;
12018         }
12019         break;
12020       case BeginningOfGame:
12021       case MachinePlaysBlack:
12022         if (!WhiteOnMove(forwardMostMove)) {
12023             DisplayError(_("Wait until your turn"), 0);
12024             return;
12025         }
12026         break;
12027       default:
12028         DisplayError(_("No hint available"), 0);
12029         return;
12030     }
12031     SendToProgram("hint\n", &first);
12032     hintRequested = TRUE;
12033 }
12034
12035 void
12036 BookEvent()
12037 {
12038     if (appData.noChessProgram) return;
12039     switch (gameMode) {
12040       case MachinePlaysWhite:
12041         if (WhiteOnMove(forwardMostMove)) {
12042             DisplayError(_("Wait until your turn"), 0);
12043             return;
12044         }
12045         break;
12046       case BeginningOfGame:
12047       case MachinePlaysBlack:
12048         if (!WhiteOnMove(forwardMostMove)) {
12049             DisplayError(_("Wait until your turn"), 0);
12050             return;
12051         }
12052         break;
12053       case EditPosition:
12054         EditPositionDone(TRUE);
12055         break;
12056       case TwoMachinesPlay:
12057         return;
12058       default:
12059         break;
12060     }
12061     SendToProgram("bk\n", &first);
12062     bookOutput[0] = NULLCHAR;
12063     bookRequested = TRUE;
12064 }
12065
12066 void
12067 AboutGameEvent()
12068 {
12069     char *tags = PGNTags(&gameInfo);
12070     TagsPopUp(tags, CmailMsg());
12071     free(tags);
12072 }
12073
12074 /* end button procedures */
12075
12076 void
12077 PrintPosition(fp, move)
12078      FILE *fp;
12079      int move;
12080 {
12081     int i, j;
12082     
12083     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12084         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12085             char c = PieceToChar(boards[move][i][j]);
12086             fputc(c == 'x' ? '.' : c, fp);
12087             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12088         }
12089     }
12090     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12091       fprintf(fp, "white to play\n");
12092     else
12093       fprintf(fp, "black to play\n");
12094 }
12095
12096 void
12097 PrintOpponents(fp)
12098      FILE *fp;
12099 {
12100     if (gameInfo.white != NULL) {
12101         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12102     } else {
12103         fprintf(fp, "\n");
12104     }
12105 }
12106
12107 /* Find last component of program's own name, using some heuristics */
12108 void
12109 TidyProgramName(prog, host, buf)
12110      char *prog, *host, buf[MSG_SIZ];
12111 {
12112     char *p, *q;
12113     int local = (strcmp(host, "localhost") == 0);
12114     while (!local && (p = strchr(prog, ';')) != NULL) {
12115         p++;
12116         while (*p == ' ') p++;
12117         prog = p;
12118     }
12119     if (*prog == '"' || *prog == '\'') {
12120         q = strchr(prog + 1, *prog);
12121     } else {
12122         q = strchr(prog, ' ');
12123     }
12124     if (q == NULL) q = prog + strlen(prog);
12125     p = q;
12126     while (p >= prog && *p != '/' && *p != '\\') p--;
12127     p++;
12128     if(p == prog && *p == '"') p++;
12129     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12130     memcpy(buf, p, q - p);
12131     buf[q - p] = NULLCHAR;
12132     if (!local) {
12133         strcat(buf, "@");
12134         strcat(buf, host);
12135     }
12136 }
12137
12138 char *
12139 TimeControlTagValue()
12140 {
12141     char buf[MSG_SIZ];
12142     if (!appData.clockMode) {
12143         strcpy(buf, "-");
12144     } else if (movesPerSession > 0) {
12145         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12146     } else if (timeIncrement == 0) {
12147         sprintf(buf, "%ld", timeControl/1000);
12148     } else {
12149         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12150     }
12151     return StrSave(buf);
12152 }
12153
12154 void
12155 SetGameInfo()
12156 {
12157     /* This routine is used only for certain modes */
12158     VariantClass v = gameInfo.variant;
12159     ClearGameInfo(&gameInfo);
12160     gameInfo.variant = v;
12161
12162     switch (gameMode) {
12163       case MachinePlaysWhite:
12164         gameInfo.event = StrSave( appData.pgnEventHeader );
12165         gameInfo.site = StrSave(HostName());
12166         gameInfo.date = PGNDate();
12167         gameInfo.round = StrSave("-");
12168         gameInfo.white = StrSave(first.tidy);
12169         gameInfo.black = StrSave(UserName());
12170         gameInfo.timeControl = TimeControlTagValue();
12171         break;
12172
12173       case MachinePlaysBlack:
12174         gameInfo.event = StrSave( appData.pgnEventHeader );
12175         gameInfo.site = StrSave(HostName());
12176         gameInfo.date = PGNDate();
12177         gameInfo.round = StrSave("-");
12178         gameInfo.white = StrSave(UserName());
12179         gameInfo.black = StrSave(first.tidy);
12180         gameInfo.timeControl = TimeControlTagValue();
12181         break;
12182
12183       case TwoMachinesPlay:
12184         gameInfo.event = StrSave( appData.pgnEventHeader );
12185         gameInfo.site = StrSave(HostName());
12186         gameInfo.date = PGNDate();
12187         if (matchGame > 0) {
12188             char buf[MSG_SIZ];
12189             sprintf(buf, "%d", matchGame);
12190             gameInfo.round = StrSave(buf);
12191         } else {
12192             gameInfo.round = StrSave("-");
12193         }
12194         if (first.twoMachinesColor[0] == 'w') {
12195             gameInfo.white = StrSave(first.tidy);
12196             gameInfo.black = StrSave(second.tidy);
12197         } else {
12198             gameInfo.white = StrSave(second.tidy);
12199             gameInfo.black = StrSave(first.tidy);
12200         }
12201         gameInfo.timeControl = TimeControlTagValue();
12202         break;
12203
12204       case EditGame:
12205         gameInfo.event = StrSave("Edited game");
12206         gameInfo.site = StrSave(HostName());
12207         gameInfo.date = PGNDate();
12208         gameInfo.round = StrSave("-");
12209         gameInfo.white = StrSave("-");
12210         gameInfo.black = StrSave("-");
12211         break;
12212
12213       case EditPosition:
12214         gameInfo.event = StrSave("Edited position");
12215         gameInfo.site = StrSave(HostName());
12216         gameInfo.date = PGNDate();
12217         gameInfo.round = StrSave("-");
12218         gameInfo.white = StrSave("-");
12219         gameInfo.black = StrSave("-");
12220         break;
12221
12222       case IcsPlayingWhite:
12223       case IcsPlayingBlack:
12224       case IcsObserving:
12225       case IcsExamining:
12226         break;
12227
12228       case PlayFromGameFile:
12229         gameInfo.event = StrSave("Game from non-PGN file");
12230         gameInfo.site = StrSave(HostName());
12231         gameInfo.date = PGNDate();
12232         gameInfo.round = StrSave("-");
12233         gameInfo.white = StrSave("?");
12234         gameInfo.black = StrSave("?");
12235         break;
12236
12237       default:
12238         break;
12239     }
12240 }
12241
12242 void
12243 ReplaceComment(index, text)
12244      int index;
12245      char *text;
12246 {
12247     int len;
12248
12249     while (*text == '\n') text++;
12250     len = strlen(text);
12251     while (len > 0 && text[len - 1] == '\n') len--;
12252
12253     if (commentList[index] != NULL)
12254       free(commentList[index]);
12255
12256     if (len == 0) {
12257         commentList[index] = NULL;
12258         return;
12259     }
12260     commentList[index] = (char *) malloc(len + 2);
12261     strncpy(commentList[index], text, len);
12262     commentList[index][len] = '\n';
12263     commentList[index][len + 1] = NULLCHAR;
12264 }
12265
12266 void
12267 CrushCRs(text)
12268      char *text;
12269 {
12270   char *p = text;
12271   char *q = text;
12272   char ch;
12273
12274   do {
12275     ch = *p++;
12276     if (ch == '\r') continue;
12277     *q++ = ch;
12278   } while (ch != '\0');
12279 }
12280
12281 void
12282 AppendComment(index, text)
12283      int index;
12284      char *text;
12285 {
12286     int oldlen, len;
12287     char *old;
12288
12289     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12290
12291     CrushCRs(text);
12292     while (*text == '\n') text++;
12293     len = strlen(text);
12294     while (len > 0 && text[len - 1] == '\n') len--;
12295
12296     if (len == 0) return;
12297
12298     if (commentList[index] != NULL) {
12299         old = commentList[index];
12300         oldlen = strlen(old);
12301         commentList[index] = (char *) malloc(oldlen + len + 2);
12302         strcpy(commentList[index], old);
12303         free(old);
12304         strncpy(&commentList[index][oldlen], text, len);
12305         commentList[index][oldlen + len] = '\n';
12306         commentList[index][oldlen + len + 1] = NULLCHAR;
12307     } else {
12308         commentList[index] = (char *) malloc(len + 2);
12309         strncpy(commentList[index], text, len);
12310         commentList[index][len] = '\n';
12311         commentList[index][len + 1] = NULLCHAR;
12312     }
12313 }
12314
12315 static char * FindStr( char * text, char * sub_text )
12316 {
12317     char * result = strstr( text, sub_text );
12318
12319     if( result != NULL ) {
12320         result += strlen( sub_text );
12321     }
12322
12323     return result;
12324 }
12325
12326 /* [AS] Try to extract PV info from PGN comment */
12327 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12328 char *GetInfoFromComment( int index, char * text )
12329 {
12330     char * sep = text;
12331
12332     if( text != NULL && index > 0 ) {
12333         int score = 0;
12334         int depth = 0;
12335         int time = -1, sec = 0, deci;
12336         char * s_eval = FindStr( text, "[%eval " );
12337         char * s_emt = FindStr( text, "[%emt " );
12338
12339         if( s_eval != NULL || s_emt != NULL ) {
12340             /* New style */
12341             char delim;
12342
12343             if( s_eval != NULL ) {
12344                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12345                     return text;
12346                 }
12347
12348                 if( delim != ']' ) {
12349                     return text;
12350                 }
12351             }
12352
12353             if( s_emt != NULL ) {
12354             }
12355         }
12356         else {
12357             /* We expect something like: [+|-]nnn.nn/dd */
12358             int score_lo = 0;
12359
12360             sep = strchr( text, '/' );
12361             if( sep == NULL || sep < (text+4) ) {
12362                 return text;
12363             }
12364
12365             time = -1; sec = -1; deci = -1;
12366             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12367                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12368                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12369                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12370                 return text;
12371             }
12372
12373             if( score_lo < 0 || score_lo >= 100 ) {
12374                 return text;
12375             }
12376
12377             if(sec >= 0) time = 600*time + 10*sec; else
12378             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12379
12380             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12381
12382             /* [HGM] PV time: now locate end of PV info */
12383             while( *++sep >= '0' && *sep <= '9'); // strip depth
12384             if(time >= 0)
12385             while( *++sep >= '0' && *sep <= '9'); // strip time
12386             if(sec >= 0)
12387             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12388             if(deci >= 0)
12389             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12390             while(*sep == ' ') sep++;
12391         }
12392
12393         if( depth <= 0 ) {
12394             return text;
12395         }
12396
12397         if( time < 0 ) {
12398             time = -1;
12399         }
12400
12401         pvInfoList[index-1].depth = depth;
12402         pvInfoList[index-1].score = score;
12403         pvInfoList[index-1].time  = 10*time; // centi-sec
12404     }
12405     return sep;
12406 }
12407
12408 void
12409 SendToProgram(message, cps)
12410      char *message;
12411      ChessProgramState *cps;
12412 {
12413     int count, outCount, error;
12414     char buf[MSG_SIZ];
12415
12416     if (cps->pr == NULL) return;
12417     Attention(cps);
12418     
12419     if (appData.debugMode) {
12420         TimeMark now;
12421         GetTimeMark(&now);
12422         fprintf(debugFP, "%ld >%-6s: %s", 
12423                 SubtractTimeMarks(&now, &programStartTime),
12424                 cps->which, message);
12425     }
12426     
12427     count = strlen(message);
12428     outCount = OutputToProcess(cps->pr, message, count, &error);
12429     if (outCount < count && !exiting 
12430                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12431         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12432         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12433             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12434                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12435                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12436             } else {
12437                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12438             }
12439             gameInfo.resultDetails = StrSave(buf);
12440         }
12441         DisplayFatalError(buf, error, 1);
12442     }
12443 }
12444
12445 void
12446 ReceiveFromProgram(isr, closure, message, count, error)
12447      InputSourceRef isr;
12448      VOIDSTAR closure;
12449      char *message;
12450      int count;
12451      int error;
12452 {
12453     char *end_str;
12454     char buf[MSG_SIZ];
12455     ChessProgramState *cps = (ChessProgramState *)closure;
12456
12457     if (isr != cps->isr) return; /* Killed intentionally */
12458     if (count <= 0) {
12459         if (count == 0) {
12460             sprintf(buf,
12461                     _("Error: %s chess program (%s) exited unexpectedly"),
12462                     cps->which, cps->program);
12463         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12464                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12465                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12466                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12467                 } else {
12468                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12469                 }
12470                 gameInfo.resultDetails = StrSave(buf);
12471             }
12472             RemoveInputSource(cps->isr);
12473             DisplayFatalError(buf, 0, 1);
12474         } else {
12475             sprintf(buf,
12476                     _("Error reading from %s chess program (%s)"),
12477                     cps->which, cps->program);
12478             RemoveInputSource(cps->isr);
12479
12480             /* [AS] Program is misbehaving badly... kill it */
12481             if( count == -2 ) {
12482                 DestroyChildProcess( cps->pr, 9 );
12483                 cps->pr = NoProc;
12484             }
12485
12486             DisplayFatalError(buf, error, 1);
12487         }
12488         return;
12489     }
12490     
12491     if ((end_str = strchr(message, '\r')) != NULL)
12492       *end_str = NULLCHAR;
12493     if ((end_str = strchr(message, '\n')) != NULL)
12494       *end_str = NULLCHAR;
12495     
12496     if (appData.debugMode) {
12497         TimeMark now; int print = 1;
12498         char *quote = ""; char c; int i;
12499
12500         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12501                 char start = message[0];
12502                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12503                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12504                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12505                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12506                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12507                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12508                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12509                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12510                         { quote = "# "; print = (appData.engineComments == 2); }
12511                 message[0] = start; // restore original message
12512         }
12513         if(print) {
12514                 GetTimeMark(&now);
12515                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12516                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12517                         quote,
12518                         message);
12519         }
12520     }
12521
12522     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12523     if (appData.icsEngineAnalyze) {
12524         if (strstr(message, "whisper") != NULL ||
12525              strstr(message, "kibitz") != NULL || 
12526             strstr(message, "tellics") != NULL) return;
12527     }
12528
12529     HandleMachineMove(message, cps);
12530 }
12531
12532
12533 void
12534 SendTimeControl(cps, mps, tc, inc, sd, st)
12535      ChessProgramState *cps;
12536      int mps, inc, sd, st;
12537      long tc;
12538 {
12539     char buf[MSG_SIZ];
12540     int seconds;
12541
12542     if( timeControl_2 > 0 ) {
12543         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12544             tc = timeControl_2;
12545         }
12546     }
12547     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12548     inc /= cps->timeOdds;
12549     st  /= cps->timeOdds;
12550
12551     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12552
12553     if (st > 0) {
12554       /* Set exact time per move, normally using st command */
12555       if (cps->stKludge) {
12556         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12557         seconds = st % 60;
12558         if (seconds == 0) {
12559           sprintf(buf, "level 1 %d\n", st/60);
12560         } else {
12561           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12562         }
12563       } else {
12564         sprintf(buf, "st %d\n", st);
12565       }
12566     } else {
12567       /* Set conventional or incremental time control, using level command */
12568       if (seconds == 0) {
12569         /* Note old gnuchess bug -- minutes:seconds used to not work.
12570            Fixed in later versions, but still avoid :seconds
12571            when seconds is 0. */
12572         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12573       } else {
12574         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12575                 seconds, inc/1000);
12576       }
12577     }
12578     SendToProgram(buf, cps);
12579
12580     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12581     /* Orthogonally, limit search to given depth */
12582     if (sd > 0) {
12583       if (cps->sdKludge) {
12584         sprintf(buf, "depth\n%d\n", sd);
12585       } else {
12586         sprintf(buf, "sd %d\n", sd);
12587       }
12588       SendToProgram(buf, cps);
12589     }
12590
12591     if(cps->nps > 0) { /* [HGM] nps */
12592         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12593         else {
12594                 sprintf(buf, "nps %d\n", cps->nps);
12595               SendToProgram(buf, cps);
12596         }
12597     }
12598 }
12599
12600 ChessProgramState *WhitePlayer()
12601 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12602 {
12603     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12604        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12605         return &second;
12606     return &first;
12607 }
12608
12609 void
12610 SendTimeRemaining(cps, machineWhite)
12611      ChessProgramState *cps;
12612      int /*boolean*/ machineWhite;
12613 {
12614     char message[MSG_SIZ];
12615     long time, otime;
12616
12617     /* Note: this routine must be called when the clocks are stopped
12618        or when they have *just* been set or switched; otherwise
12619        it will be off by the time since the current tick started.
12620     */
12621     if (machineWhite) {
12622         time = whiteTimeRemaining / 10;
12623         otime = blackTimeRemaining / 10;
12624     } else {
12625         time = blackTimeRemaining / 10;
12626         otime = whiteTimeRemaining / 10;
12627     }
12628     /* [HGM] translate opponent's time by time-odds factor */
12629     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12630     if (appData.debugMode) {
12631         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12632     }
12633
12634     if (time <= 0) time = 1;
12635     if (otime <= 0) otime = 1;
12636     
12637     sprintf(message, "time %ld\n", time);
12638     SendToProgram(message, cps);
12639
12640     sprintf(message, "otim %ld\n", otime);
12641     SendToProgram(message, cps);
12642 }
12643
12644 int
12645 BoolFeature(p, name, loc, cps)
12646      char **p;
12647      char *name;
12648      int *loc;
12649      ChessProgramState *cps;
12650 {
12651   char buf[MSG_SIZ];
12652   int len = strlen(name);
12653   int val;
12654   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12655     (*p) += len + 1;
12656     sscanf(*p, "%d", &val);
12657     *loc = (val != 0);
12658     while (**p && **p != ' ') (*p)++;
12659     sprintf(buf, "accepted %s\n", name);
12660     SendToProgram(buf, cps);
12661     return TRUE;
12662   }
12663   return FALSE;
12664 }
12665
12666 int
12667 IntFeature(p, name, loc, cps)
12668      char **p;
12669      char *name;
12670      int *loc;
12671      ChessProgramState *cps;
12672 {
12673   char buf[MSG_SIZ];
12674   int len = strlen(name);
12675   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12676     (*p) += len + 1;
12677     sscanf(*p, "%d", loc);
12678     while (**p && **p != ' ') (*p)++;
12679     sprintf(buf, "accepted %s\n", name);
12680     SendToProgram(buf, cps);
12681     return TRUE;
12682   }
12683   return FALSE;
12684 }
12685
12686 int
12687 StringFeature(p, name, loc, cps)
12688      char **p;
12689      char *name;
12690      char loc[];
12691      ChessProgramState *cps;
12692 {
12693   char buf[MSG_SIZ];
12694   int len = strlen(name);
12695   if (strncmp((*p), name, len) == 0
12696       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12697     (*p) += len + 2;
12698     sscanf(*p, "%[^\"]", loc);
12699     while (**p && **p != '\"') (*p)++;
12700     if (**p == '\"') (*p)++;
12701     sprintf(buf, "accepted %s\n", name);
12702     SendToProgram(buf, cps);
12703     return TRUE;
12704   }
12705   return FALSE;
12706 }
12707
12708 int 
12709 ParseOption(Option *opt, ChessProgramState *cps)
12710 // [HGM] options: process the string that defines an engine option, and determine
12711 // name, type, default value, and allowed value range
12712 {
12713         char *p, *q, buf[MSG_SIZ];
12714         int n, min = (-1)<<31, max = 1<<31, def;
12715
12716         if(p = strstr(opt->name, " -spin ")) {
12717             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12718             if(max < min) max = min; // enforce consistency
12719             if(def < min) def = min;
12720             if(def > max) def = max;
12721             opt->value = def;
12722             opt->min = min;
12723             opt->max = max;
12724             opt->type = Spin;
12725         } else if((p = strstr(opt->name, " -slider "))) {
12726             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12727             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12728             if(max < min) max = min; // enforce consistency
12729             if(def < min) def = min;
12730             if(def > max) def = max;
12731             opt->value = def;
12732             opt->min = min;
12733             opt->max = max;
12734             opt->type = Spin; // Slider;
12735         } else if((p = strstr(opt->name, " -string "))) {
12736             opt->textValue = p+9;
12737             opt->type = TextBox;
12738         } else if((p = strstr(opt->name, " -file "))) {
12739             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12740             opt->textValue = p+7;
12741             opt->type = TextBox; // FileName;
12742         } else if((p = strstr(opt->name, " -path "))) {
12743             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12744             opt->textValue = p+7;
12745             opt->type = TextBox; // PathName;
12746         } else if(p = strstr(opt->name, " -check ")) {
12747             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12748             opt->value = (def != 0);
12749             opt->type = CheckBox;
12750         } else if(p = strstr(opt->name, " -combo ")) {
12751             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12752             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12753             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12754             opt->value = n = 0;
12755             while(q = StrStr(q, " /// ")) {
12756                 n++; *q = 0;    // count choices, and null-terminate each of them
12757                 q += 5;
12758                 if(*q == '*') { // remember default, which is marked with * prefix
12759                     q++;
12760                     opt->value = n;
12761                 }
12762                 cps->comboList[cps->comboCnt++] = q;
12763             }
12764             cps->comboList[cps->comboCnt++] = NULL;
12765             opt->max = n + 1;
12766             opt->type = ComboBox;
12767         } else if(p = strstr(opt->name, " -button")) {
12768             opt->type = Button;
12769         } else if(p = strstr(opt->name, " -save")) {
12770             opt->type = SaveButton;
12771         } else return FALSE;
12772         *p = 0; // terminate option name
12773         // now look if the command-line options define a setting for this engine option.
12774         if(cps->optionSettings && cps->optionSettings[0])
12775             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12776         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12777                 sprintf(buf, "option %s", p);
12778                 if(p = strstr(buf, ",")) *p = 0;
12779                 strcat(buf, "\n");
12780                 SendToProgram(buf, cps);
12781         }
12782         return TRUE;
12783 }
12784
12785 void
12786 FeatureDone(cps, val)
12787      ChessProgramState* cps;
12788      int val;
12789 {
12790   DelayedEventCallback cb = GetDelayedEvent();
12791   if ((cb == InitBackEnd3 && cps == &first) ||
12792       (cb == TwoMachinesEventIfReady && cps == &second)) {
12793     CancelDelayedEvent();
12794     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12795   }
12796   cps->initDone = val;
12797 }
12798
12799 /* Parse feature command from engine */
12800 void
12801 ParseFeatures(args, cps)
12802      char* args;
12803      ChessProgramState *cps;  
12804 {
12805   char *p = args;
12806   char *q;
12807   int val;
12808   char buf[MSG_SIZ];
12809
12810   for (;;) {
12811     while (*p == ' ') p++;
12812     if (*p == NULLCHAR) return;
12813
12814     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12815     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12816     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12817     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12818     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12819     if (BoolFeature(&p, "reuse", &val, cps)) {
12820       /* Engine can disable reuse, but can't enable it if user said no */
12821       if (!val) cps->reuse = FALSE;
12822       continue;
12823     }
12824     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12825     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12826       if (gameMode == TwoMachinesPlay) {
12827         DisplayTwoMachinesTitle();
12828       } else {
12829         DisplayTitle("");
12830       }
12831       continue;
12832     }
12833     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12834     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12835     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12836     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12837     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12838     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12839     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12840     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12841     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12842     if (IntFeature(&p, "done", &val, cps)) {
12843       FeatureDone(cps, val);
12844       continue;
12845     }
12846     /* Added by Tord: */
12847     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12848     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12849     /* End of additions by Tord */
12850
12851     /* [HGM] added features: */
12852     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12853     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12854     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12855     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12856     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12857     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12858     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12859         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12860             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12861             SendToProgram(buf, cps);
12862             continue;
12863         }
12864         if(cps->nrOptions >= MAX_OPTIONS) {
12865             cps->nrOptions--;
12866             sprintf(buf, "%s engine has too many options\n", cps->which);
12867             DisplayError(buf, 0);
12868         }
12869         continue;
12870     }
12871     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12872     /* End of additions by HGM */
12873
12874     /* unknown feature: complain and skip */
12875     q = p;
12876     while (*q && *q != '=') q++;
12877     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12878     SendToProgram(buf, cps);
12879     p = q;
12880     if (*p == '=') {
12881       p++;
12882       if (*p == '\"') {
12883         p++;
12884         while (*p && *p != '\"') p++;
12885         if (*p == '\"') p++;
12886       } else {
12887         while (*p && *p != ' ') p++;
12888       }
12889     }
12890   }
12891
12892 }
12893
12894 void
12895 PeriodicUpdatesEvent(newState)
12896      int newState;
12897 {
12898     if (newState == appData.periodicUpdates)
12899       return;
12900
12901     appData.periodicUpdates=newState;
12902
12903     /* Display type changes, so update it now */
12904 //    DisplayAnalysis();
12905
12906     /* Get the ball rolling again... */
12907     if (newState) {
12908         AnalysisPeriodicEvent(1);
12909         StartAnalysisClock();
12910     }
12911 }
12912
12913 void
12914 PonderNextMoveEvent(newState)
12915      int newState;
12916 {
12917     if (newState == appData.ponderNextMove) return;
12918     if (gameMode == EditPosition) EditPositionDone(TRUE);
12919     if (newState) {
12920         SendToProgram("hard\n", &first);
12921         if (gameMode == TwoMachinesPlay) {
12922             SendToProgram("hard\n", &second);
12923         }
12924     } else {
12925         SendToProgram("easy\n", &first);
12926         thinkOutput[0] = NULLCHAR;
12927         if (gameMode == TwoMachinesPlay) {
12928             SendToProgram("easy\n", &second);
12929         }
12930     }
12931     appData.ponderNextMove = newState;
12932 }
12933
12934 void
12935 NewSettingEvent(option, command, value)
12936      char *command;
12937      int option, value;
12938 {
12939     char buf[MSG_SIZ];
12940
12941     if (gameMode == EditPosition) EditPositionDone(TRUE);
12942     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12943     SendToProgram(buf, &first);
12944     if (gameMode == TwoMachinesPlay) {
12945         SendToProgram(buf, &second);
12946     }
12947 }
12948
12949 void
12950 ShowThinkingEvent()
12951 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12952 {
12953     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12954     int newState = appData.showThinking
12955         // [HGM] thinking: other features now need thinking output as well
12956         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12957     
12958     if (oldState == newState) return;
12959     oldState = newState;
12960     if (gameMode == EditPosition) EditPositionDone(TRUE);
12961     if (oldState) {
12962         SendToProgram("post\n", &first);
12963         if (gameMode == TwoMachinesPlay) {
12964             SendToProgram("post\n", &second);
12965         }
12966     } else {
12967         SendToProgram("nopost\n", &first);
12968         thinkOutput[0] = NULLCHAR;
12969         if (gameMode == TwoMachinesPlay) {
12970             SendToProgram("nopost\n", &second);
12971         }
12972     }
12973 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12974 }
12975
12976 void
12977 AskQuestionEvent(title, question, replyPrefix, which)
12978      char *title; char *question; char *replyPrefix; char *which;
12979 {
12980   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12981   if (pr == NoProc) return;
12982   AskQuestion(title, question, replyPrefix, pr);
12983 }
12984
12985 void
12986 DisplayMove(moveNumber)
12987      int moveNumber;
12988 {
12989     char message[MSG_SIZ];
12990     char res[MSG_SIZ];
12991     char cpThinkOutput[MSG_SIZ];
12992
12993     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12994     
12995     if (moveNumber == forwardMostMove - 1 || 
12996         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12997
12998         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12999
13000         if (strchr(cpThinkOutput, '\n')) {
13001             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13002         }
13003     } else {
13004         *cpThinkOutput = NULLCHAR;
13005     }
13006
13007     /* [AS] Hide thinking from human user */
13008     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13009         *cpThinkOutput = NULLCHAR;
13010         if( thinkOutput[0] != NULLCHAR ) {
13011             int i;
13012
13013             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13014                 cpThinkOutput[i] = '.';
13015             }
13016             cpThinkOutput[i] = NULLCHAR;
13017             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13018         }
13019     }
13020
13021     if (moveNumber == forwardMostMove - 1 &&
13022         gameInfo.resultDetails != NULL) {
13023         if (gameInfo.resultDetails[0] == NULLCHAR) {
13024             sprintf(res, " %s", PGNResult(gameInfo.result));
13025         } else {
13026             sprintf(res, " {%s} %s",
13027                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13028         }
13029     } else {
13030         res[0] = NULLCHAR;
13031     }
13032
13033     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13034         DisplayMessage(res, cpThinkOutput);
13035     } else {
13036         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13037                 WhiteOnMove(moveNumber) ? " " : ".. ",
13038                 parseList[moveNumber], res);
13039         DisplayMessage(message, cpThinkOutput);
13040     }
13041 }
13042
13043 void
13044 DisplayComment(moveNumber, text)
13045      int moveNumber;
13046      char *text;
13047 {
13048     char title[MSG_SIZ];
13049     char buf[8000]; // comment can be long!
13050     int score, depth;
13051     
13052     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13053       strcpy(title, "Comment");
13054     } else {
13055       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13056               WhiteOnMove(moveNumber) ? " " : ".. ",
13057               parseList[moveNumber]);
13058     }
13059     // [HGM] PV info: display PV info together with (or as) comment
13060     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13061       if(text == NULL) text = "";                                           
13062       score = pvInfoList[moveNumber].score;
13063       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13064               depth, (pvInfoList[moveNumber].time+50)/100, text);
13065       text = buf;
13066     }
13067     if (text != NULL && (appData.autoDisplayComment || commentUp))
13068         CommentPopUp(title, text);
13069 }
13070
13071 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13072  * might be busy thinking or pondering.  It can be omitted if your
13073  * gnuchess is configured to stop thinking immediately on any user
13074  * input.  However, that gnuchess feature depends on the FIONREAD
13075  * ioctl, which does not work properly on some flavors of Unix.
13076  */
13077 void
13078 Attention(cps)
13079      ChessProgramState *cps;
13080 {
13081 #if ATTENTION
13082     if (!cps->useSigint) return;
13083     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13084     switch (gameMode) {
13085       case MachinePlaysWhite:
13086       case MachinePlaysBlack:
13087       case TwoMachinesPlay:
13088       case IcsPlayingWhite:
13089       case IcsPlayingBlack:
13090       case AnalyzeMode:
13091       case AnalyzeFile:
13092         /* Skip if we know it isn't thinking */
13093         if (!cps->maybeThinking) return;
13094         if (appData.debugMode)
13095           fprintf(debugFP, "Interrupting %s\n", cps->which);
13096         InterruptChildProcess(cps->pr);
13097         cps->maybeThinking = FALSE;
13098         break;
13099       default:
13100         break;
13101     }
13102 #endif /*ATTENTION*/
13103 }
13104
13105 int
13106 CheckFlags()
13107 {
13108     if (whiteTimeRemaining <= 0) {
13109         if (!whiteFlag) {
13110             whiteFlag = TRUE;
13111             if (appData.icsActive) {
13112                 if (appData.autoCallFlag &&
13113                     gameMode == IcsPlayingBlack && !blackFlag) {
13114                   SendToICS(ics_prefix);
13115                   SendToICS("flag\n");
13116                 }
13117             } else {
13118                 if (blackFlag) {
13119                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13120                 } else {
13121                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13122                     if (appData.autoCallFlag) {
13123                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13124                         return TRUE;
13125                     }
13126                 }
13127             }
13128         }
13129     }
13130     if (blackTimeRemaining <= 0) {
13131         if (!blackFlag) {
13132             blackFlag = TRUE;
13133             if (appData.icsActive) {
13134                 if (appData.autoCallFlag &&
13135                     gameMode == IcsPlayingWhite && !whiteFlag) {
13136                   SendToICS(ics_prefix);
13137                   SendToICS("flag\n");
13138                 }
13139             } else {
13140                 if (whiteFlag) {
13141                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13142                 } else {
13143                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13144                     if (appData.autoCallFlag) {
13145                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13146                         return TRUE;
13147                     }
13148                 }
13149             }
13150         }
13151     }
13152     return FALSE;
13153 }
13154
13155 void
13156 CheckTimeControl()
13157 {
13158     if (!appData.clockMode || appData.icsActive ||
13159         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13160
13161     /*
13162      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13163      */
13164     if ( !WhiteOnMove(forwardMostMove) )
13165         /* White made time control */
13166         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13167         /* [HGM] time odds: correct new time quota for time odds! */
13168                                             / WhitePlayer()->timeOdds;
13169       else
13170         /* Black made time control */
13171         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13172                                             / WhitePlayer()->other->timeOdds;
13173 }
13174
13175 void
13176 DisplayBothClocks()
13177 {
13178     int wom = gameMode == EditPosition ?
13179       !blackPlaysFirst : WhiteOnMove(currentMove);
13180     DisplayWhiteClock(whiteTimeRemaining, wom);
13181     DisplayBlackClock(blackTimeRemaining, !wom);
13182 }
13183
13184
13185 /* Timekeeping seems to be a portability nightmare.  I think everyone
13186    has ftime(), but I'm really not sure, so I'm including some ifdefs
13187    to use other calls if you don't.  Clocks will be less accurate if
13188    you have neither ftime nor gettimeofday.
13189 */
13190
13191 /* VS 2008 requires the #include outside of the function */
13192 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13193 #include <sys/timeb.h>
13194 #endif
13195
13196 /* Get the current time as a TimeMark */
13197 void
13198 GetTimeMark(tm)
13199      TimeMark *tm;
13200 {
13201 #if HAVE_GETTIMEOFDAY
13202
13203     struct timeval timeVal;
13204     struct timezone timeZone;
13205
13206     gettimeofday(&timeVal, &timeZone);
13207     tm->sec = (long) timeVal.tv_sec; 
13208     tm->ms = (int) (timeVal.tv_usec / 1000L);
13209
13210 #else /*!HAVE_GETTIMEOFDAY*/
13211 #if HAVE_FTIME
13212
13213 // include <sys/timeb.h> / moved to just above start of function
13214     struct timeb timeB;
13215
13216     ftime(&timeB);
13217     tm->sec = (long) timeB.time;
13218     tm->ms = (int) timeB.millitm;
13219
13220 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13221     tm->sec = (long) time(NULL);
13222     tm->ms = 0;
13223 #endif
13224 #endif
13225 }
13226
13227 /* Return the difference in milliseconds between two
13228    time marks.  We assume the difference will fit in a long!
13229 */
13230 long
13231 SubtractTimeMarks(tm2, tm1)
13232      TimeMark *tm2, *tm1;
13233 {
13234     return 1000L*(tm2->sec - tm1->sec) +
13235            (long) (tm2->ms - tm1->ms);
13236 }
13237
13238
13239 /*
13240  * Code to manage the game clocks.
13241  *
13242  * In tournament play, black starts the clock and then white makes a move.
13243  * We give the human user a slight advantage if he is playing white---the
13244  * clocks don't run until he makes his first move, so it takes zero time.
13245  * Also, we don't account for network lag, so we could get out of sync
13246  * with GNU Chess's clock -- but then, referees are always right.  
13247  */
13248
13249 static TimeMark tickStartTM;
13250 static long intendedTickLength;
13251
13252 long
13253 NextTickLength(timeRemaining)
13254      long timeRemaining;
13255 {
13256     long nominalTickLength, nextTickLength;
13257
13258     if (timeRemaining > 0L && timeRemaining <= 10000L)
13259       nominalTickLength = 100L;
13260     else
13261       nominalTickLength = 1000L;
13262     nextTickLength = timeRemaining % nominalTickLength;
13263     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13264
13265     return nextTickLength;
13266 }
13267
13268 /* Adjust clock one minute up or down */
13269 void
13270 AdjustClock(Boolean which, int dir)
13271 {
13272     if(which) blackTimeRemaining += 60000*dir;
13273     else      whiteTimeRemaining += 60000*dir;
13274     DisplayBothClocks();
13275 }
13276
13277 /* Stop clocks and reset to a fresh time control */
13278 void
13279 ResetClocks() 
13280 {
13281     (void) StopClockTimer();
13282     if (appData.icsActive) {
13283         whiteTimeRemaining = blackTimeRemaining = 0;
13284     } else { /* [HGM] correct new time quote for time odds */
13285         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13286         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13287     }
13288     if (whiteFlag || blackFlag) {
13289         DisplayTitle("");
13290         whiteFlag = blackFlag = FALSE;
13291     }
13292     DisplayBothClocks();
13293 }
13294
13295 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13296
13297 /* Decrement running clock by amount of time that has passed */
13298 void
13299 DecrementClocks()
13300 {
13301     long timeRemaining;
13302     long lastTickLength, fudge;
13303     TimeMark now;
13304
13305     if (!appData.clockMode) return;
13306     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13307         
13308     GetTimeMark(&now);
13309
13310     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13311
13312     /* Fudge if we woke up a little too soon */
13313     fudge = intendedTickLength - lastTickLength;
13314     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13315
13316     if (WhiteOnMove(forwardMostMove)) {
13317         if(whiteNPS >= 0) lastTickLength = 0;
13318         timeRemaining = whiteTimeRemaining -= lastTickLength;
13319         DisplayWhiteClock(whiteTimeRemaining - fudge,
13320                           WhiteOnMove(currentMove));
13321     } else {
13322         if(blackNPS >= 0) lastTickLength = 0;
13323         timeRemaining = blackTimeRemaining -= lastTickLength;
13324         DisplayBlackClock(blackTimeRemaining - fudge,
13325                           !WhiteOnMove(currentMove));
13326     }
13327
13328     if (CheckFlags()) return;
13329         
13330     tickStartTM = now;
13331     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13332     StartClockTimer(intendedTickLength);
13333
13334     /* if the time remaining has fallen below the alarm threshold, sound the
13335      * alarm. if the alarm has sounded and (due to a takeback or time control
13336      * with increment) the time remaining has increased to a level above the
13337      * threshold, reset the alarm so it can sound again. 
13338      */
13339     
13340     if (appData.icsActive && appData.icsAlarm) {
13341
13342         /* make sure we are dealing with the user's clock */
13343         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13344                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13345            )) return;
13346
13347         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13348             alarmSounded = FALSE;
13349         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13350             PlayAlarmSound();
13351             alarmSounded = TRUE;
13352         }
13353     }
13354 }
13355
13356
13357 /* A player has just moved, so stop the previously running
13358    clock and (if in clock mode) start the other one.
13359    We redisplay both clocks in case we're in ICS mode, because
13360    ICS gives us an update to both clocks after every move.
13361    Note that this routine is called *after* forwardMostMove
13362    is updated, so the last fractional tick must be subtracted
13363    from the color that is *not* on move now.
13364 */
13365 void
13366 SwitchClocks()
13367 {
13368     long lastTickLength;
13369     TimeMark now;
13370     int flagged = FALSE;
13371
13372     GetTimeMark(&now);
13373
13374     if (StopClockTimer() && appData.clockMode) {
13375         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13376         if (WhiteOnMove(forwardMostMove)) {
13377             if(blackNPS >= 0) lastTickLength = 0;
13378             blackTimeRemaining -= lastTickLength;
13379            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13380 //         if(pvInfoList[forwardMostMove-1].time == -1)
13381                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13382                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13383         } else {
13384            if(whiteNPS >= 0) lastTickLength = 0;
13385            whiteTimeRemaining -= lastTickLength;
13386            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13387 //         if(pvInfoList[forwardMostMove-1].time == -1)
13388                  pvInfoList[forwardMostMove-1].time = 
13389                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13390         }
13391         flagged = CheckFlags();
13392     }
13393     CheckTimeControl();
13394
13395     if (flagged || !appData.clockMode) return;
13396
13397     switch (gameMode) {
13398       case MachinePlaysBlack:
13399       case MachinePlaysWhite:
13400       case BeginningOfGame:
13401         if (pausing) return;
13402         break;
13403
13404       case EditGame:
13405       case PlayFromGameFile:
13406       case IcsExamining:
13407         return;
13408
13409       default:
13410         break;
13411     }
13412
13413     tickStartTM = now;
13414     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13415       whiteTimeRemaining : blackTimeRemaining);
13416     StartClockTimer(intendedTickLength);
13417 }
13418         
13419
13420 /* Stop both clocks */
13421 void
13422 StopClocks()
13423 {       
13424     long lastTickLength;
13425     TimeMark now;
13426
13427     if (!StopClockTimer()) return;
13428     if (!appData.clockMode) return;
13429
13430     GetTimeMark(&now);
13431
13432     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13433     if (WhiteOnMove(forwardMostMove)) {
13434         if(whiteNPS >= 0) lastTickLength = 0;
13435         whiteTimeRemaining -= lastTickLength;
13436         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13437     } else {
13438         if(blackNPS >= 0) lastTickLength = 0;
13439         blackTimeRemaining -= lastTickLength;
13440         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13441     }
13442     CheckFlags();
13443 }
13444         
13445 /* Start clock of player on move.  Time may have been reset, so
13446    if clock is already running, stop and restart it. */
13447 void
13448 StartClocks()
13449 {
13450     (void) StopClockTimer(); /* in case it was running already */
13451     DisplayBothClocks();
13452     if (CheckFlags()) return;
13453
13454     if (!appData.clockMode) return;
13455     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13456
13457     GetTimeMark(&tickStartTM);
13458     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13459       whiteTimeRemaining : blackTimeRemaining);
13460
13461    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13462     whiteNPS = blackNPS = -1; 
13463     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13464        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13465         whiteNPS = first.nps;
13466     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13467        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13468         blackNPS = first.nps;
13469     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13470         whiteNPS = second.nps;
13471     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13472         blackNPS = second.nps;
13473     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13474
13475     StartClockTimer(intendedTickLength);
13476 }
13477
13478 char *
13479 TimeString(ms)
13480      long ms;
13481 {
13482     long second, minute, hour, day;
13483     char *sign = "";
13484     static char buf[32];
13485     
13486     if (ms > 0 && ms <= 9900) {
13487       /* convert milliseconds to tenths, rounding up */
13488       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13489
13490       sprintf(buf, " %03.1f ", tenths/10.0);
13491       return buf;
13492     }
13493
13494     /* convert milliseconds to seconds, rounding up */
13495     /* use floating point to avoid strangeness of integer division
13496        with negative dividends on many machines */
13497     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13498
13499     if (second < 0) {
13500         sign = "-";
13501         second = -second;
13502     }
13503     
13504     day = second / (60 * 60 * 24);
13505     second = second % (60 * 60 * 24);
13506     hour = second / (60 * 60);
13507     second = second % (60 * 60);
13508     minute = second / 60;
13509     second = second % 60;
13510     
13511     if (day > 0)
13512       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13513               sign, day, hour, minute, second);
13514     else if (hour > 0)
13515       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13516     else
13517       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13518     
13519     return buf;
13520 }
13521
13522
13523 /*
13524  * This is necessary because some C libraries aren't ANSI C compliant yet.
13525  */
13526 char *
13527 StrStr(string, match)
13528      char *string, *match;
13529 {
13530     int i, length;
13531     
13532     length = strlen(match);
13533     
13534     for (i = strlen(string) - length; i >= 0; i--, string++)
13535       if (!strncmp(match, string, length))
13536         return string;
13537     
13538     return NULL;
13539 }
13540
13541 char *
13542 StrCaseStr(string, match)
13543      char *string, *match;
13544 {
13545     int i, j, length;
13546     
13547     length = strlen(match);
13548     
13549     for (i = strlen(string) - length; i >= 0; i--, string++) {
13550         for (j = 0; j < length; j++) {
13551             if (ToLower(match[j]) != ToLower(string[j]))
13552               break;
13553         }
13554         if (j == length) return string;
13555     }
13556
13557     return NULL;
13558 }
13559
13560 #ifndef _amigados
13561 int
13562 StrCaseCmp(s1, s2)
13563      char *s1, *s2;
13564 {
13565     char c1, c2;
13566     
13567     for (;;) {
13568         c1 = ToLower(*s1++);
13569         c2 = ToLower(*s2++);
13570         if (c1 > c2) return 1;
13571         if (c1 < c2) return -1;
13572         if (c1 == NULLCHAR) return 0;
13573     }
13574 }
13575
13576
13577 int
13578 ToLower(c)
13579      int c;
13580 {
13581     return isupper(c) ? tolower(c) : c;
13582 }
13583
13584
13585 int
13586 ToUpper(c)
13587      int c;
13588 {
13589     return islower(c) ? toupper(c) : c;
13590 }
13591 #endif /* !_amigados    */
13592
13593 char *
13594 StrSave(s)
13595      char *s;
13596 {
13597     char *ret;
13598
13599     if ((ret = (char *) malloc(strlen(s) + 1))) {
13600         strcpy(ret, s);
13601     }
13602     return ret;
13603 }
13604
13605 char *
13606 StrSavePtr(s, savePtr)
13607      char *s, **savePtr;
13608 {
13609     if (*savePtr) {
13610         free(*savePtr);
13611     }
13612     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13613         strcpy(*savePtr, s);
13614     }
13615     return(*savePtr);
13616 }
13617
13618 char *
13619 PGNDate()
13620 {
13621     time_t clock;
13622     struct tm *tm;
13623     char buf[MSG_SIZ];
13624
13625     clock = time((time_t *)NULL);
13626     tm = localtime(&clock);
13627     sprintf(buf, "%04d.%02d.%02d",
13628             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13629     return StrSave(buf);
13630 }
13631
13632
13633 char *
13634 PositionToFEN(move, overrideCastling)
13635      int move;
13636      char *overrideCastling;
13637 {
13638     int i, j, fromX, fromY, toX, toY;
13639     int whiteToPlay;
13640     char buf[128];
13641     char *p, *q;
13642     int emptycount;
13643     ChessSquare piece;
13644
13645     whiteToPlay = (gameMode == EditPosition) ?
13646       !blackPlaysFirst : (move % 2 == 0);
13647     p = buf;
13648
13649     /* Piece placement data */
13650     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13651         emptycount = 0;
13652         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13653             if (boards[move][i][j] == EmptySquare) {
13654                 emptycount++;
13655             } else { ChessSquare piece = boards[move][i][j];
13656                 if (emptycount > 0) {
13657                     if(emptycount<10) /* [HGM] can be >= 10 */
13658                         *p++ = '0' + emptycount;
13659                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13660                     emptycount = 0;
13661                 }
13662                 if(PieceToChar(piece) == '+') {
13663                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13664                     *p++ = '+';
13665                     piece = (ChessSquare)(DEMOTED piece);
13666                 } 
13667                 *p++ = PieceToChar(piece);
13668                 if(p[-1] == '~') {
13669                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13670                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13671                     *p++ = '~';
13672                 }
13673             }
13674         }
13675         if (emptycount > 0) {
13676             if(emptycount<10) /* [HGM] can be >= 10 */
13677                 *p++ = '0' + emptycount;
13678             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13679             emptycount = 0;
13680         }
13681         *p++ = '/';
13682     }
13683     *(p - 1) = ' ';
13684
13685     /* [HGM] print Crazyhouse or Shogi holdings */
13686     if( gameInfo.holdingsWidth ) {
13687         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13688         q = p;
13689         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13690             piece = boards[move][i][BOARD_WIDTH-1];
13691             if( piece != EmptySquare )
13692               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13693                   *p++ = PieceToChar(piece);
13694         }
13695         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13696             piece = boards[move][BOARD_HEIGHT-i-1][0];
13697             if( piece != EmptySquare )
13698               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13699                   *p++ = PieceToChar(piece);
13700         }
13701
13702         if( q == p ) *p++ = '-';
13703         *p++ = ']';
13704         *p++ = ' ';
13705     }
13706
13707     /* Active color */
13708     *p++ = whiteToPlay ? 'w' : 'b';
13709     *p++ = ' ';
13710
13711   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13712     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13713   } else {
13714   if(nrCastlingRights) {
13715      q = p;
13716      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13717        /* [HGM] write directly from rights */
13718            if(castlingRights[move][2] >= 0 &&
13719               castlingRights[move][0] >= 0   )
13720                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13721            if(castlingRights[move][2] >= 0 &&
13722               castlingRights[move][1] >= 0   )
13723                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13724            if(castlingRights[move][5] >= 0 &&
13725               castlingRights[move][3] >= 0   )
13726                 *p++ = castlingRights[move][3] + AAA;
13727            if(castlingRights[move][5] >= 0 &&
13728               castlingRights[move][4] >= 0   )
13729                 *p++ = castlingRights[move][4] + AAA;
13730      } else {
13731
13732         /* [HGM] write true castling rights */
13733         if( nrCastlingRights == 6 ) {
13734             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13735                castlingRights[move][2] >= 0  ) *p++ = 'K';
13736             if(castlingRights[move][1] == BOARD_LEFT &&
13737                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13738             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13739                castlingRights[move][5] >= 0  ) *p++ = 'k';
13740             if(castlingRights[move][4] == BOARD_LEFT &&
13741                castlingRights[move][5] >= 0  ) *p++ = 'q';
13742         }
13743      }
13744      if (q == p) *p++ = '-'; /* No castling rights */
13745      *p++ = ' ';
13746   }
13747
13748   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13749      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13750     /* En passant target square */
13751     if (move > backwardMostMove) {
13752         fromX = moveList[move - 1][0] - AAA;
13753         fromY = moveList[move - 1][1] - ONE;
13754         toX = moveList[move - 1][2] - AAA;
13755         toY = moveList[move - 1][3] - ONE;
13756         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13757             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13758             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13759             fromX == toX) {
13760             /* 2-square pawn move just happened */
13761             *p++ = toX + AAA;
13762             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13763         } else {
13764             *p++ = '-';
13765         }
13766     } else if(move == backwardMostMove) {
13767         // [HGM] perhaps we should always do it like this, and forget the above?
13768         if(epStatus[move] >= 0) {
13769             *p++ = epStatus[move] + AAA;
13770             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13771         } else {
13772             *p++ = '-';
13773         }
13774     } else {
13775         *p++ = '-';
13776     }
13777     *p++ = ' ';
13778   }
13779   }
13780
13781     /* [HGM] find reversible plies */
13782     {   int i = 0, j=move;
13783
13784         if (appData.debugMode) { int k;
13785             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13786             for(k=backwardMostMove; k<=forwardMostMove; k++)
13787                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13788
13789         }
13790
13791         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13792         if( j == backwardMostMove ) i += initialRulePlies;
13793         sprintf(p, "%d ", i);
13794         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13795     }
13796     /* Fullmove number */
13797     sprintf(p, "%d", (move / 2) + 1);
13798     
13799     return StrSave(buf);
13800 }
13801
13802 Boolean
13803 ParseFEN(board, blackPlaysFirst, fen)
13804     Board board;
13805      int *blackPlaysFirst;
13806      char *fen;
13807 {
13808     int i, j;
13809     char *p;
13810     int emptycount;
13811     ChessSquare piece;
13812
13813     p = fen;
13814
13815     /* [HGM] by default clear Crazyhouse holdings, if present */
13816     if(gameInfo.holdingsWidth) {
13817        for(i=0; i<BOARD_HEIGHT; i++) {
13818            board[i][0]             = EmptySquare; /* black holdings */
13819            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13820            board[i][1]             = (ChessSquare) 0; /* black counts */
13821            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13822        }
13823     }
13824
13825     /* Piece placement data */
13826     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13827         j = 0;
13828         for (;;) {
13829             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13830                 if (*p == '/') p++;
13831                 emptycount = gameInfo.boardWidth - j;
13832                 while (emptycount--)
13833                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13834                 break;
13835 #if(BOARD_SIZE >= 10)
13836             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13837                 p++; emptycount=10;
13838                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13839                 while (emptycount--)
13840                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13841 #endif
13842             } else if (isdigit(*p)) {
13843                 emptycount = *p++ - '0';
13844                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13845                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13846                 while (emptycount--)
13847                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13848             } else if (*p == '+' || isalpha(*p)) {
13849                 if (j >= gameInfo.boardWidth) return FALSE;
13850                 if(*p=='+') {
13851                     piece = CharToPiece(*++p);
13852                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13853                     piece = (ChessSquare) (PROMOTED piece ); p++;
13854                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13855                 } else piece = CharToPiece(*p++);
13856
13857                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13858                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13859                     piece = (ChessSquare) (PROMOTED piece);
13860                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13861                     p++;
13862                 }
13863                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13864             } else {
13865                 return FALSE;
13866             }
13867         }
13868     }
13869     while (*p == '/' || *p == ' ') p++;
13870
13871     /* [HGM] look for Crazyhouse holdings here */
13872     while(*p==' ') p++;
13873     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13874         if(*p == '[') p++;
13875         if(*p == '-' ) *p++; /* empty holdings */ else {
13876             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13877             /* if we would allow FEN reading to set board size, we would   */
13878             /* have to add holdings and shift the board read so far here   */
13879             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13880                 *p++;
13881                 if((int) piece >= (int) BlackPawn ) {
13882                     i = (int)piece - (int)BlackPawn;
13883                     i = PieceToNumber((ChessSquare)i);
13884                     if( i >= gameInfo.holdingsSize ) return FALSE;
13885                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13886                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13887                 } else {
13888                     i = (int)piece - (int)WhitePawn;
13889                     i = PieceToNumber((ChessSquare)i);
13890                     if( i >= gameInfo.holdingsSize ) return FALSE;
13891                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13892                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13893                 }
13894             }
13895         }
13896         if(*p == ']') *p++;
13897     }
13898
13899     while(*p == ' ') p++;
13900
13901     /* Active color */
13902     switch (*p++) {
13903       case 'w':
13904         *blackPlaysFirst = FALSE;
13905         break;
13906       case 'b': 
13907         *blackPlaysFirst = TRUE;
13908         break;
13909       default:
13910         return FALSE;
13911     }
13912
13913     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13914     /* return the extra info in global variiables             */
13915
13916     /* set defaults in case FEN is incomplete */
13917     FENepStatus = EP_UNKNOWN;
13918     for(i=0; i<nrCastlingRights; i++ ) {
13919         FENcastlingRights[i] =
13920             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13921     }   /* assume possible unless obviously impossible */
13922     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13923     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13924     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
13925                            && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13926     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13927     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13928     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
13929                            && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13930     FENrulePlies = 0;
13931
13932     while(*p==' ') p++;
13933     if(nrCastlingRights) {
13934       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13935           /* castling indicator present, so default becomes no castlings */
13936           for(i=0; i<nrCastlingRights; i++ ) {
13937                  FENcastlingRights[i] = -1;
13938           }
13939       }
13940       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13941              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13942              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13943              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13944         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13945
13946         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13947             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13948             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13949         }
13950         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
13951             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // scanning fails in these variants
13952         if(whiteKingFile<0 || board[0][whiteKingFile]!=WhiteUnicorn
13953                            && board[0][whiteKingFile]!=WhiteKing) whiteKingFile = -1;
13954         if(blackKingFile<0 || board[BOARD_HEIGHT-1][blackKingFile]!=BlackUnicorn
13955                            && board[BOARD_HEIGHT-1][blackKingFile]!=BlackKing) blackKingFile = -1;
13956         switch(c) {
13957           case'K':
13958               for(i=BOARD_RGHT-1; i>whiteKingFile && board[0][i]!=WhiteRook; i--);
13959               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13960               FENcastlingRights[2] = whiteKingFile;
13961               break;
13962           case'Q':
13963               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13964               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13965               FENcastlingRights[2] = whiteKingFile;
13966               break;
13967           case'k':
13968               for(i=BOARD_RGHT-1; i>blackKingFile && board[BOARD_HEIGHT-1][i]!=BlackRook; i--);
13969               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13970               FENcastlingRights[5] = blackKingFile;
13971               break;
13972           case'q':
13973               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13974               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13975               FENcastlingRights[5] = blackKingFile;
13976           case '-':
13977               break;
13978           default: /* FRC castlings */
13979               if(c >= 'a') { /* black rights */
13980                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13981                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13982                   if(i == BOARD_RGHT) break;
13983                   FENcastlingRights[5] = i;
13984                   c -= AAA;
13985                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13986                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13987                   if(c > i)
13988                       FENcastlingRights[3] = c;
13989                   else
13990                       FENcastlingRights[4] = c;
13991               } else { /* white rights */
13992                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13993                     if(board[0][i] == WhiteKing) break;
13994                   if(i == BOARD_RGHT) break;
13995                   FENcastlingRights[2] = i;
13996                   c -= AAA - 'a' + 'A';
13997                   if(board[0][c] >= WhiteKing) break;
13998                   if(c > i)
13999                       FENcastlingRights[0] = c;
14000                   else
14001                       FENcastlingRights[1] = c;
14002               }
14003         }
14004       }
14005       for(i=0; i<nrCastlingRights; i++)
14006         if(FENcastlingRights[i] >= 0) initialRights[i] = FENcastlingRights[i];
14007     if (appData.debugMode) {
14008         fprintf(debugFP, "FEN castling rights:");
14009         for(i=0; i<nrCastlingRights; i++)
14010         fprintf(debugFP, " %d", FENcastlingRights[i]);
14011         fprintf(debugFP, "\n");
14012     }
14013
14014       while(*p==' ') p++;
14015     }
14016
14017     /* read e.p. field in games that know e.p. capture */
14018     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14019        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
14020       if(*p=='-') {
14021         p++; FENepStatus = EP_NONE;
14022       } else {
14023          char c = *p++ - AAA;
14024
14025          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14026          if(*p >= '0' && *p <='9') *p++;
14027          FENepStatus = c;
14028       }
14029     }
14030
14031
14032     if(sscanf(p, "%d", &i) == 1) {
14033         FENrulePlies = i; /* 50-move ply counter */
14034         /* (The move number is still ignored)    */
14035     }
14036
14037     return TRUE;
14038 }
14039       
14040 void
14041 EditPositionPasteFEN(char *fen)
14042 {
14043   if (fen != NULL) {
14044     Board initial_position;
14045
14046     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14047       DisplayError(_("Bad FEN position in clipboard"), 0);
14048       return ;
14049     } else {
14050       int savedBlackPlaysFirst = blackPlaysFirst;
14051       EditPositionEvent();
14052       blackPlaysFirst = savedBlackPlaysFirst;
14053       CopyBoard(boards[0], initial_position);
14054           /* [HGM] copy FEN attributes as well */
14055           {   int i;
14056               initialRulePlies = FENrulePlies;
14057               epStatus[0] = FENepStatus;
14058               for( i=0; i<nrCastlingRights; i++ )
14059                   castlingRights[0][i] = FENcastlingRights[i];
14060           }
14061       EditPositionDone(FALSE);
14062       DisplayBothClocks();
14063       DrawPosition(FALSE, boards[currentMove]);
14064     }
14065   }
14066 }
14067
14068 static char cseq[12] = "\\   ";
14069
14070 Boolean set_cont_sequence(char *new_seq)
14071 {
14072     int len;
14073     Boolean ret;
14074
14075     // handle bad attempts to set the sequence
14076         if (!new_seq)
14077                 return 0; // acceptable error - no debug
14078
14079     len = strlen(new_seq);
14080     ret = (len > 0) && (len < sizeof(cseq));
14081     if (ret)
14082         strcpy(cseq, new_seq);
14083     else if (appData.debugMode)
14084         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14085     return ret;
14086 }
14087
14088 /*
14089     reformat a source message so words don't cross the width boundary.  internal
14090     newlines are not removed.  returns the wrapped size (no null character unless
14091     included in source message).  If dest is NULL, only calculate the size required
14092     for the dest buffer.  lp argument indicats line position upon entry, and it's
14093     passed back upon exit.
14094 */
14095 int wrap(char *dest, char *src, int count, int width, int *lp)
14096 {
14097     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14098
14099     cseq_len = strlen(cseq);
14100     old_line = line = *lp;
14101     ansi = len = clen = 0;
14102
14103     for (i=0; i < count; i++)
14104     {
14105         if (src[i] == '\033')
14106             ansi = 1;
14107
14108         // if we hit the width, back up
14109         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14110         {
14111             // store i & len in case the word is too long
14112             old_i = i, old_len = len;
14113
14114             // find the end of the last word
14115             while (i && src[i] != ' ' && src[i] != '\n')
14116             {
14117                 i--;
14118                 len--;
14119             }
14120
14121             // word too long?  restore i & len before splitting it
14122             if ((old_i-i+clen) >= width)
14123             {
14124                 i = old_i;
14125                 len = old_len;
14126             }
14127
14128             // extra space?
14129             if (i && src[i-1] == ' ')
14130                 len--;
14131
14132             if (src[i] != ' ' && src[i] != '\n')
14133             {
14134                 i--;
14135                 if (len)
14136                     len--;
14137             }
14138
14139             // now append the newline and continuation sequence
14140             if (dest)
14141                 dest[len] = '\n';
14142             len++;
14143             if (dest)
14144                 strncpy(dest+len, cseq, cseq_len);
14145             len += cseq_len;
14146             line = cseq_len;
14147             clen = cseq_len;
14148             continue;
14149         }
14150
14151         if (dest)
14152             dest[len] = src[i];
14153         len++;
14154         if (!ansi)
14155             line++;
14156         if (src[i] == '\n')
14157             line = 0;
14158         if (src[i] == 'm')
14159             ansi = 0;
14160     }
14161     if (dest && appData.debugMode)
14162     {
14163         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14164             count, width, line, len, *lp);
14165         show_bytes(debugFP, src, count);
14166         fprintf(debugFP, "\ndest: ");
14167         show_bytes(debugFP, dest, len);
14168         fprintf(debugFP, "\n");
14169     }
14170     *lp = dest ? line : old_line;
14171
14172     return len;
14173 }