9e0dad5ce32c214642ff20bf08854dcee953d53e
[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((int nr));
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   case VariantMakruk:
359     flags &= ~F_ALL_CASTLE_OK;
360     break;
361   default:
362     break;
363   }
364   return flags;
365 }
366
367 FILE *gameFileFP, *debugFP;
368
369 /* 
370     [AS] Note: sometimes, the sscanf() function is used to parse the input
371     into a fixed-size buffer. Because of this, we must be prepared to
372     receive strings as long as the size of the input buffer, which is currently
373     set to 4K for Windows and 8K for the rest.
374     So, we must either allocate sufficiently large buffers here, or
375     reduce the size of the input buffer in the input reading part.
376 */
377
378 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
379 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
380 char thinkOutput1[MSG_SIZ*10];
381
382 ChessProgramState first, second;
383
384 /* premove variables */
385 int premoveToX = 0;
386 int premoveToY = 0;
387 int premoveFromX = 0;
388 int premoveFromY = 0;
389 int premovePromoChar = 0;
390 int gotPremove = 0;
391 Boolean alarmSounded;
392 /* end premove variables */
393
394 char *ics_prefix = "$";
395 int ics_type = ICS_GENERIC;
396
397 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
398 int pauseExamForwardMostMove = 0;
399 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
400 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
401 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
402 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
403 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
404 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
405 int whiteFlag = FALSE, blackFlag = FALSE;
406 int userOfferedDraw = FALSE;
407 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
408 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
409 int cmailMoveType[CMAIL_MAX_GAMES];
410 long ics_clock_paused = 0;
411 ProcRef icsPR = NoProc, cmailPR = NoProc;
412 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
413 GameMode gameMode = BeginningOfGame;
414 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
415 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
416 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
417 int hiddenThinkOutputState = 0; /* [AS] */
418 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
419 int adjudicateLossPlies = 6;
420 char white_holding[64], black_holding[64];
421 TimeMark lastNodeCountTime;
422 long lastNodeCount=0;
423 int have_sent_ICS_logon = 0;
424 int movesPerSession;
425 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
426 long timeControl_2; /* [AS] Allow separate time controls */
427 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
428 long timeRemaining[2][MAX_MOVES];
429 int matchGame = 0;
430 TimeMark programStartTime;
431 char ics_handle[MSG_SIZ];
432 int have_set_title = 0;
433
434 /* animateTraining preserves the state of appData.animate
435  * when Training mode is activated. This allows the
436  * response to be animated when appData.animate == TRUE and
437  * appData.animateDragging == TRUE.
438  */
439 Boolean animateTraining;
440
441 GameInfo gameInfo;
442
443 AppData appData;
444
445 Board boards[MAX_MOVES];
446 /* [HGM] Following 7 needed for accurate legality tests: */
447 signed char  epStatus[MAX_MOVES];
448 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
449 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
450 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
451 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
452 int   initialRulePlies, FENrulePlies;
453 char  FENepStatus;
454 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
455 int loadFlag = 0; 
456 int shuffleOpenings;
457 int mute; // mute all sounds
458
459 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
460     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
461         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
462     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
463         BlackKing, BlackBishop, BlackKnight, BlackRook }
464 };
465
466 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
467     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
468         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
469     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
470         BlackKing, BlackKing, BlackKnight, BlackRook }
471 };
472
473 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
474     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
475         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
476     { BlackRook, BlackMan, BlackBishop, BlackQueen,
477         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
478 };
479
480 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] white and black different armies! */
481     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
482         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
483     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
484         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
485 };
486
487 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
488     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
489         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
490     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
491         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
492 };
493
494 ChessSquare makrukArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
495     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
496         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
497     { BlackRook, BlackKnight, BlackMan, BlackFerz,
498         BlackKing, BlackMan, BlackKnight, BlackRook }
499 };
500
501
502 #if (BOARD_SIZE>=10)
503 ChessSquare ShogiArray[2][BOARD_SIZE] = {
504     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
505         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
506     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
507         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
508 };
509
510 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
511     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
512         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
513     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
514         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
515 };
516
517 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
518     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
519         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
521         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
522 };
523
524 ChessSquare GreatArray[2][BOARD_SIZE] = {
525     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
526         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
527     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
528         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
529 };
530
531 ChessSquare JanusArray[2][BOARD_SIZE] = {
532     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
533         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
534     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
535         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
536 };
537
538 #ifdef GOTHIC
539 ChessSquare GothicArray[2][BOARD_SIZE] = {
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
541         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
543         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
544 };
545 #else // !GOTHIC
546 #define GothicArray CapablancaArray
547 #endif // !GOTHIC
548
549 #ifdef FALCON
550 ChessSquare FalconArray[2][BOARD_SIZE] = {
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
552         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
554         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
555 };
556 #else // !FALCON
557 #define FalconArray CapablancaArray
558 #endif // !FALCON
559
560 #else // !(BOARD_SIZE>=10)
561 #define XiangqiPosition FIDEArray
562 #define CapablancaArray FIDEArray
563 #define GothicArray FIDEArray
564 #define GreatArray FIDEArray
565 #endif // !(BOARD_SIZE>=10)
566
567 #if (BOARD_SIZE>=12)
568 ChessSquare CourierArray[2][BOARD_SIZE] = {
569     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
570         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
571     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
572         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
573 };
574 #else // !(BOARD_SIZE>=12)
575 #define CourierArray CapablancaArray
576 #endif // !(BOARD_SIZE>=12)
577
578
579 Board initialPosition;
580
581
582 /* Convert str to a rating. Checks for special cases of "----",
583
584    "++++", etc. Also strips ()'s */
585 int
586 string_to_rating(str)
587   char *str;
588 {
589   while(*str && !isdigit(*str)) ++str;
590   if (!*str)
591     return 0;   /* One of the special "no rating" cases */
592   else
593     return atoi(str);
594 }
595
596 void
597 ClearProgramStats()
598 {
599     /* Init programStats */
600     programStats.movelist[0] = 0;
601     programStats.depth = 0;
602     programStats.nr_moves = 0;
603     programStats.moves_left = 0;
604     programStats.nodes = 0;
605     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
606     programStats.score = 0;
607     programStats.got_only_move = 0;
608     programStats.got_fail = 0;
609     programStats.line_is_book = 0;
610 }
611
612 void
613 InitBackEnd1()
614 {
615     int matched, min, sec;
616
617     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
618
619     GetTimeMark(&programStartTime);
620     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
621
622     ClearProgramStats();
623     programStats.ok_to_send = 1;
624     programStats.seen_stat = 0;
625
626     /*
627      * Initialize game list
628      */
629     ListNew(&gameList);
630
631
632     /*
633      * Internet chess server status
634      */
635     if (appData.icsActive) {
636         appData.matchMode = FALSE;
637         appData.matchGames = 0;
638 #if ZIPPY       
639         appData.noChessProgram = !appData.zippyPlay;
640 #else
641         appData.zippyPlay = FALSE;
642         appData.zippyTalk = FALSE;
643         appData.noChessProgram = TRUE;
644 #endif
645         if (*appData.icsHelper != NULLCHAR) {
646             appData.useTelnet = TRUE;
647             appData.telnetProgram = appData.icsHelper;
648         }
649     } else {
650         appData.zippyTalk = appData.zippyPlay = FALSE;
651     }
652
653     /* [AS] Initialize pv info list [HGM] and game state */
654     {
655         int i, j;
656
657         for( i=0; i<MAX_MOVES; i++ ) {
658             pvInfoList[i].depth = -1;
659             epStatus[i]=EP_NONE;
660             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
661         }
662     }
663
664     /*
665      * Parse timeControl resource
666      */
667     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
668                           appData.movesPerSession)) {
669         char buf[MSG_SIZ];
670         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
671         DisplayFatalError(buf, 0, 2);
672     }
673
674     /*
675      * Parse searchTime resource
676      */
677     if (*appData.searchTime != NULLCHAR) {
678         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
679         if (matched == 1) {
680             searchTime = min * 60;
681         } else if (matched == 2) {
682             searchTime = min * 60 + sec;
683         } else {
684             char buf[MSG_SIZ];
685             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
686             DisplayFatalError(buf, 0, 2);
687         }
688     }
689
690     /* [AS] Adjudication threshold */
691     adjudicateLossThreshold = appData.adjudicateLossThreshold;
692     
693     first.which = "first";
694     second.which = "second";
695     first.maybeThinking = second.maybeThinking = FALSE;
696     first.pr = second.pr = NoProc;
697     first.isr = second.isr = NULL;
698     first.sendTime = second.sendTime = 2;
699     first.sendDrawOffers = 1;
700     if (appData.firstPlaysBlack) {
701         first.twoMachinesColor = "black\n";
702         second.twoMachinesColor = "white\n";
703     } else {
704         first.twoMachinesColor = "white\n";
705         second.twoMachinesColor = "black\n";
706     }
707     first.program = appData.firstChessProgram;
708     second.program = appData.secondChessProgram;
709     first.host = appData.firstHost;
710     second.host = appData.secondHost;
711     first.dir = appData.firstDirectory;
712     second.dir = appData.secondDirectory;
713     first.other = &second;
714     second.other = &first;
715     first.initString = appData.initString;
716     second.initString = appData.secondInitString;
717     first.computerString = appData.firstComputerString;
718     second.computerString = appData.secondComputerString;
719     first.useSigint = second.useSigint = TRUE;
720     first.useSigterm = second.useSigterm = TRUE;
721     first.reuse = appData.reuseFirst;
722     second.reuse = appData.reuseSecond;
723     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
724     second.nps = appData.secondNPS;
725     first.useSetboard = second.useSetboard = FALSE;
726     first.useSAN = second.useSAN = FALSE;
727     first.usePing = second.usePing = FALSE;
728     first.lastPing = second.lastPing = 0;
729     first.lastPong = second.lastPong = 0;
730     first.usePlayother = second.usePlayother = FALSE;
731     first.useColors = second.useColors = TRUE;
732     first.useUsermove = second.useUsermove = FALSE;
733     first.sendICS = second.sendICS = FALSE;
734     first.sendName = second.sendName = appData.icsActive;
735     first.sdKludge = second.sdKludge = FALSE;
736     first.stKludge = second.stKludge = FALSE;
737     TidyProgramName(first.program, first.host, first.tidy);
738     TidyProgramName(second.program, second.host, second.tidy);
739     first.matchWins = second.matchWins = 0;
740     strcpy(first.variants, appData.variant);
741     strcpy(second.variants, appData.variant);
742     first.analysisSupport = second.analysisSupport = 2; /* detect */
743     first.analyzing = second.analyzing = FALSE;
744     first.initDone = second.initDone = FALSE;
745
746     /* New features added by Tord: */
747     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
748     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
749     /* End of new features added by Tord. */
750     first.fenOverride  = appData.fenOverride1;
751     second.fenOverride = appData.fenOverride2;
752
753     /* [HGM] time odds: set factor for each machine */
754     first.timeOdds  = appData.firstTimeOdds;
755     second.timeOdds = appData.secondTimeOdds;
756     { float norm = 1;
757         if(appData.timeOddsMode) {
758             norm = first.timeOdds;
759             if(norm > second.timeOdds) norm = second.timeOdds;
760         }
761         first.timeOdds /= norm;
762         second.timeOdds /= norm;
763     }
764
765     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
766     first.accumulateTC = appData.firstAccumulateTC;
767     second.accumulateTC = appData.secondAccumulateTC;
768     first.maxNrOfSessions = second.maxNrOfSessions = 1;
769
770     /* [HGM] debug */
771     first.debug = second.debug = FALSE;
772     first.supportsNPS = second.supportsNPS = UNKNOWN;
773
774     /* [HGM] options */
775     first.optionSettings  = appData.firstOptions;
776     second.optionSettings = appData.secondOptions;
777
778     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
779     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
780     first.isUCI = appData.firstIsUCI; /* [AS] */
781     second.isUCI = appData.secondIsUCI; /* [AS] */
782     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
783     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
784
785     if (appData.firstProtocolVersion > PROTOVER ||
786         appData.firstProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.firstProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       first.protocolVersion = appData.firstProtocolVersion;
793     }
794
795     if (appData.secondProtocolVersion > PROTOVER ||
796         appData.secondProtocolVersion < 1) {
797       char buf[MSG_SIZ];
798       sprintf(buf, _("protocol version %d not supported"),
799               appData.secondProtocolVersion);
800       DisplayFatalError(buf, 0, 2);
801     } else {
802       second.protocolVersion = appData.secondProtocolVersion;
803     }
804
805     if (appData.icsActive) {
806         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
807     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
808         appData.clockMode = FALSE;
809         first.sendTime = second.sendTime = 0;
810     }
811     
812 #if ZIPPY
813     /* Override some settings from environment variables, for backward
814        compatibility.  Unfortunately it's not feasible to have the env
815        vars just set defaults, at least in xboard.  Ugh.
816     */
817     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
818       ZippyInit();
819     }
820 #endif
821     
822     if (appData.noChessProgram) {
823         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
824         sprintf(programVersion, "%s", PACKAGE_STRING);
825     } else {
826       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
827       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
828       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
829     }
830
831     if (!appData.icsActive) {
832       char buf[MSG_SIZ];
833       /* Check for variants that are supported only in ICS mode,
834          or not at all.  Some that are accepted here nevertheless
835          have bugs; see comments below.
836       */
837       VariantClass variant = StringToVariant(appData.variant);
838       switch (variant) {
839       case VariantBughouse:     /* need four players and two boards */
840       case VariantKriegspiel:   /* need to hide pieces and move details */
841       /* case VariantFischeRandom: (Fabien: moved below) */
842         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
843         DisplayFatalError(buf, 0, 2);
844         return;
845
846       case VariantUnknown:
847       case VariantLoadable:
848       case Variant29:
849       case Variant30:
850       case Variant31:
851       case Variant32:
852       case Variant33:
853       case Variant34:
854       case Variant35:
855       case Variant36:
856       default:
857         sprintf(buf, _("Unknown variant name %s"), appData.variant);
858         DisplayFatalError(buf, 0, 2);
859         return;
860
861       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
862       case VariantFairy:      /* [HGM] TestLegality definitely off! */
863       case VariantGothic:     /* [HGM] should work */
864       case VariantCapablanca: /* [HGM] should work */
865       case VariantCourier:    /* [HGM] initial forced moves not implemented */
866       case VariantShogi:      /* [HGM] drops not tested for legality */
867       case VariantKnightmate: /* [HGM] should work */
868       case VariantCylinder:   /* [HGM] untested */
869       case VariantFalcon:     /* [HGM] untested */
870       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
871                                  offboard interposition not understood */
872       case VariantNormal:     /* definitely works! */
873       case VariantWildCastle: /* pieces not automatically shuffled */
874       case VariantNoCastle:   /* pieces not automatically shuffled */
875       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
876       case VariantLosers:     /* should work except for win condition,
877                                  and doesn't know captures are mandatory */
878       case VariantSuicide:    /* should work except for win condition,
879                                  and doesn't know captures are mandatory */
880       case VariantGiveaway:   /* should work except for win condition,
881                                  and doesn't know captures are mandatory */
882       case VariantTwoKings:   /* should work */
883       case VariantAtomic:     /* should work except for win condition */
884       case Variant3Check:     /* should work except for win condition */
885       case VariantShatranj:   /* should work except for all win conditions */
886       case VariantMakruk:     /* should work except for daw countdown */
887       case VariantBerolina:   /* might work if TestLegality is off */
888       case VariantCapaRandom: /* should work */
889       case VariantJanus:      /* should work */
890       case VariantSuper:      /* experimental */
891       case VariantGreat:      /* experimental, requires legality testing to be off */
892         break;
893       }
894     }
895
896     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
897     InitEngineUCI( installDir, &second );
898 }
899
900 int NextIntegerFromString( char ** str, long * value )
901 {
902     int result = -1;
903     char * s = *str;
904
905     while( *s == ' ' || *s == '\t' ) {
906         s++;
907     }
908
909     *value = 0;
910
911     if( *s >= '0' && *s <= '9' ) {
912         while( *s >= '0' && *s <= '9' ) {
913             *value = *value * 10 + (*s - '0');
914             s++;
915         }
916
917         result = 0;
918     }
919
920     *str = s;
921
922     return result;
923 }
924
925 int NextTimeControlFromString( char ** str, long * value )
926 {
927     long temp;
928     int result = NextIntegerFromString( str, &temp );
929
930     if( result == 0 ) {
931         *value = temp * 60; /* Minutes */
932         if( **str == ':' ) {
933             (*str)++;
934             result = NextIntegerFromString( str, &temp );
935             *value += temp; /* Seconds */
936         }
937     }
938
939     return result;
940 }
941
942 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
943 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
944     int result = -1; long temp, temp2;
945
946     if(**str != '+') return -1; // old params remain in force!
947     (*str)++;
948     if( NextTimeControlFromString( str, &temp ) ) return -1;
949
950     if(**str != '/') {
951         /* time only: incremental or sudden-death time control */
952         if(**str == '+') { /* increment follows; read it */
953             (*str)++;
954             if(result = NextIntegerFromString( str, &temp2)) return -1;
955             *inc = temp2 * 1000;
956         } else *inc = 0;
957         *moves = 0; *tc = temp * 1000; 
958         return 0;
959     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
960
961     (*str)++; /* classical time control */
962     result = NextTimeControlFromString( str, &temp2);
963     if(result == 0) {
964         *moves = temp/60;
965         *tc    = temp2 * 1000;
966         *inc   = 0;
967     }
968     return result;
969 }
970
971 int GetTimeQuota(int movenr)
972 {   /* [HGM] get time to add from the multi-session time-control string */
973     int moves=1; /* kludge to force reading of first session */
974     long time, increment;
975     char *s = fullTimeControlString;
976
977     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
978     do {
979         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
980         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
981         if(movenr == -1) return time;    /* last move before new session     */
982         if(!moves) return increment;     /* current session is incremental   */
983         if(movenr >= 0) movenr -= moves; /* we already finished this session */
984     } while(movenr >= -1);               /* try again for next session       */
985
986     return 0; // no new time quota on this move
987 }
988
989 int
990 ParseTimeControl(tc, ti, mps)
991      char *tc;
992      int ti;
993      int mps;
994 {
995   long tc1;
996   long tc2;
997   char buf[MSG_SIZ];
998   
999   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1000   if(ti > 0) {
1001     if(mps)
1002       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1003     else sprintf(buf, "+%s+%d", tc, ti);
1004   } else {
1005     if(mps)
1006              sprintf(buf, "+%d/%s", mps, tc);
1007     else sprintf(buf, "+%s", tc);
1008   }
1009   fullTimeControlString = StrSave(buf);
1010   
1011   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1012     return FALSE;
1013   }
1014   
1015   if( *tc == '/' ) {
1016     /* Parse second time control */
1017     tc++;
1018     
1019     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1020       return FALSE;
1021     }
1022     
1023     if( tc2 == 0 ) {
1024       return FALSE;
1025     }
1026     
1027     timeControl_2 = tc2 * 1000;
1028   }
1029   else {
1030     timeControl_2 = 0;
1031   }
1032   
1033   if( tc1 == 0 ) {
1034     return FALSE;
1035   }
1036   
1037   timeControl = tc1 * 1000;
1038   
1039   if (ti >= 0) {
1040     timeIncrement = ti * 1000;  /* convert to ms */
1041     movesPerSession = 0;
1042   } else {
1043     timeIncrement = 0;
1044     movesPerSession = mps;
1045   }
1046   return TRUE;
1047 }
1048
1049 void
1050 InitBackEnd2()
1051 {
1052     if (appData.debugMode) {
1053         fprintf(debugFP, "%s\n", programVersion);
1054     }
1055
1056     set_cont_sequence(appData.wrapContSeq);
1057     if (appData.matchGames > 0) {
1058         appData.matchMode = TRUE;
1059     } else if (appData.matchMode) {
1060         appData.matchGames = 1;
1061     }
1062     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1063         appData.matchGames = appData.sameColorGames;
1064     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1065         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1066         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1067     }
1068     Reset(TRUE, FALSE);
1069     if (appData.noChessProgram || first.protocolVersion == 1) {
1070       InitBackEnd3();
1071     } else {
1072       /* kludge: allow timeout for initial "feature" commands */
1073       FreezeUI();
1074       DisplayMessage("", _("Starting chess program"));
1075       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1076     }
1077 }
1078
1079 void
1080 InitBackEnd3 P((void))
1081 {
1082     GameMode initialMode;
1083     char buf[MSG_SIZ];
1084     int err;
1085
1086     InitChessProgram(&first, startedFromSetupPosition);
1087
1088
1089     if (appData.icsActive) {
1090 #ifdef WIN32
1091         /* [DM] Make a console window if needed [HGM] merged ifs */
1092         ConsoleCreate(); 
1093 #endif
1094         err = establish();
1095         if (err != 0) {
1096             if (*appData.icsCommPort != NULLCHAR) {
1097                 sprintf(buf, _("Could not open comm port %s"),  
1098                         appData.icsCommPort);
1099             } else {
1100                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1101                         appData.icsHost, appData.icsPort);
1102             }
1103             DisplayFatalError(buf, err, 1);
1104             return;
1105         }
1106         SetICSMode();
1107         telnetISR =
1108           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1109         fromUserISR =
1110           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1111         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1112             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1113     } else if (appData.noChessProgram) {
1114         SetNCPMode();
1115     } else {
1116         SetGNUMode();
1117     }
1118
1119     if (*appData.cmailGameName != NULLCHAR) {
1120         SetCmailMode();
1121         OpenLoopback(&cmailPR);
1122         cmailISR =
1123           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1124     }
1125     
1126     ThawUI();
1127     DisplayMessage("", "");
1128     if (StrCaseCmp(appData.initialMode, "") == 0) {
1129       initialMode = BeginningOfGame;
1130     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1131       initialMode = TwoMachinesPlay;
1132     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1133       initialMode = AnalyzeFile; 
1134     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1135       initialMode = AnalyzeMode;
1136     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1137       initialMode = MachinePlaysWhite;
1138     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1139       initialMode = MachinePlaysBlack;
1140     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1141       initialMode = EditGame;
1142     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1143       initialMode = EditPosition;
1144     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1145       initialMode = Training;
1146     } else {
1147       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1148       DisplayFatalError(buf, 0, 2);
1149       return;
1150     }
1151
1152     if (appData.matchMode) {
1153         /* Set up machine vs. machine match */
1154         if (appData.noChessProgram) {
1155             DisplayFatalError(_("Can't have a match with no chess programs"),
1156                               0, 2);
1157             return;
1158         }
1159         matchMode = TRUE;
1160         matchGame = 1;
1161         if (*appData.loadGameFile != NULLCHAR) {
1162             int index = appData.loadGameIndex; // [HGM] autoinc
1163             if(index<0) lastIndex = index = 1;
1164             if (!LoadGameFromFile(appData.loadGameFile,
1165                                   index,
1166                                   appData.loadGameFile, FALSE)) {
1167                 DisplayFatalError(_("Bad game file"), 0, 1);
1168                 return;
1169             }
1170         } else if (*appData.loadPositionFile != NULLCHAR) {
1171             int index = appData.loadPositionIndex; // [HGM] autoinc
1172             if(index<0) lastIndex = index = 1;
1173             if (!LoadPositionFromFile(appData.loadPositionFile,
1174                                       index,
1175                                       appData.loadPositionFile)) {
1176                 DisplayFatalError(_("Bad position file"), 0, 1);
1177                 return;
1178             }
1179         }
1180         TwoMachinesEvent();
1181     } else if (*appData.cmailGameName != NULLCHAR) {
1182         /* Set up cmail mode */
1183         ReloadCmailMsgEvent(TRUE);
1184     } else {
1185         /* Set up other modes */
1186         if (initialMode == AnalyzeFile) {
1187           if (*appData.loadGameFile == NULLCHAR) {
1188             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1189             return;
1190           }
1191         }
1192         if (*appData.loadGameFile != NULLCHAR) {
1193             (void) LoadGameFromFile(appData.loadGameFile,
1194                                     appData.loadGameIndex,
1195                                     appData.loadGameFile, TRUE);
1196         } else if (*appData.loadPositionFile != NULLCHAR) {
1197             (void) LoadPositionFromFile(appData.loadPositionFile,
1198                                         appData.loadPositionIndex,
1199                                         appData.loadPositionFile);
1200             /* [HGM] try to make self-starting even after FEN load */
1201             /* to allow automatic setup of fairy variants with wtm */
1202             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1203                 gameMode = BeginningOfGame;
1204                 setboardSpoiledMachineBlack = 1;
1205             }
1206             /* [HGM] loadPos: make that every new game uses the setup */
1207             /* from file as long as we do not switch variant          */
1208             if(!blackPlaysFirst) { int i;
1209                 startedFromPositionFile = TRUE;
1210                 CopyBoard(filePosition, boards[0]);
1211                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1212             }
1213         }
1214         if (initialMode == AnalyzeMode) {
1215           if (appData.noChessProgram) {
1216             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1217             return;
1218           }
1219           if (appData.icsActive) {
1220             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1221             return;
1222           }
1223           AnalyzeModeEvent();
1224         } else if (initialMode == AnalyzeFile) {
1225           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1226           ShowThinkingEvent();
1227           AnalyzeFileEvent();
1228           AnalysisPeriodicEvent(1);
1229         } else if (initialMode == MachinePlaysWhite) {
1230           if (appData.noChessProgram) {
1231             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1232                               0, 2);
1233             return;
1234           }
1235           if (appData.icsActive) {
1236             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1237                               0, 2);
1238             return;
1239           }
1240           MachineWhiteEvent();
1241         } else if (initialMode == MachinePlaysBlack) {
1242           if (appData.noChessProgram) {
1243             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1244                               0, 2);
1245             return;
1246           }
1247           if (appData.icsActive) {
1248             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1249                               0, 2);
1250             return;
1251           }
1252           MachineBlackEvent();
1253         } else if (initialMode == TwoMachinesPlay) {
1254           if (appData.noChessProgram) {
1255             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1256                               0, 2);
1257             return;
1258           }
1259           if (appData.icsActive) {
1260             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1261                               0, 2);
1262             return;
1263           }
1264           TwoMachinesEvent();
1265         } else if (initialMode == EditGame) {
1266           EditGameEvent();
1267         } else if (initialMode == EditPosition) {
1268           EditPositionEvent();
1269         } else if (initialMode == Training) {
1270           if (*appData.loadGameFile == NULLCHAR) {
1271             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1272             return;
1273           }
1274           TrainingEvent();
1275         }
1276     }
1277 }
1278
1279 /*
1280  * Establish will establish a contact to a remote host.port.
1281  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1282  *  used to talk to the host.
1283  * Returns 0 if okay, error code if not.
1284  */
1285 int
1286 establish()
1287 {
1288     char buf[MSG_SIZ];
1289
1290     if (*appData.icsCommPort != NULLCHAR) {
1291         /* Talk to the host through a serial comm port */
1292         return OpenCommPort(appData.icsCommPort, &icsPR);
1293
1294     } else if (*appData.gateway != NULLCHAR) {
1295         if (*appData.remoteShell == NULLCHAR) {
1296             /* Use the rcmd protocol to run telnet program on a gateway host */
1297             snprintf(buf, sizeof(buf), "%s %s %s",
1298                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1299             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1300
1301         } else {
1302             /* Use the rsh program to run telnet program on a gateway host */
1303             if (*appData.remoteUser == NULLCHAR) {
1304                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1305                         appData.gateway, appData.telnetProgram,
1306                         appData.icsHost, appData.icsPort);
1307             } else {
1308                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1309                         appData.remoteShell, appData.gateway, 
1310                         appData.remoteUser, appData.telnetProgram,
1311                         appData.icsHost, appData.icsPort);
1312             }
1313             return StartChildProcess(buf, "", &icsPR);
1314
1315         }
1316     } else if (appData.useTelnet) {
1317         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1318
1319     } else {
1320         /* TCP socket interface differs somewhat between
1321            Unix and NT; handle details in the front end.
1322            */
1323         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1324     }
1325 }
1326
1327 void
1328 show_bytes(fp, buf, count)
1329      FILE *fp;
1330      char *buf;
1331      int count;
1332 {
1333     while (count--) {
1334         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1335             fprintf(fp, "\\%03o", *buf & 0xff);
1336         } else {
1337             putc(*buf, fp);
1338         }
1339         buf++;
1340     }
1341     fflush(fp);
1342 }
1343
1344 /* Returns an errno value */
1345 int
1346 OutputMaybeTelnet(pr, message, count, outError)
1347      ProcRef pr;
1348      char *message;
1349      int count;
1350      int *outError;
1351 {
1352     char buf[8192], *p, *q, *buflim;
1353     int left, newcount, outcount;
1354
1355     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1356         *appData.gateway != NULLCHAR) {
1357         if (appData.debugMode) {
1358             fprintf(debugFP, ">ICS: ");
1359             show_bytes(debugFP, message, count);
1360             fprintf(debugFP, "\n");
1361         }
1362         return OutputToProcess(pr, message, count, outError);
1363     }
1364
1365     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1366     p = message;
1367     q = buf;
1368     left = count;
1369     newcount = 0;
1370     while (left) {
1371         if (q >= buflim) {
1372             if (appData.debugMode) {
1373                 fprintf(debugFP, ">ICS: ");
1374                 show_bytes(debugFP, buf, newcount);
1375                 fprintf(debugFP, "\n");
1376             }
1377             outcount = OutputToProcess(pr, buf, newcount, outError);
1378             if (outcount < newcount) return -1; /* to be sure */
1379             q = buf;
1380             newcount = 0;
1381         }
1382         if (*p == '\n') {
1383             *q++ = '\r';
1384             newcount++;
1385         } else if (((unsigned char) *p) == TN_IAC) {
1386             *q++ = (char) TN_IAC;
1387             newcount ++;
1388         }
1389         *q++ = *p++;
1390         newcount++;
1391         left--;
1392     }
1393     if (appData.debugMode) {
1394         fprintf(debugFP, ">ICS: ");
1395         show_bytes(debugFP, buf, newcount);
1396         fprintf(debugFP, "\n");
1397     }
1398     outcount = OutputToProcess(pr, buf, newcount, outError);
1399     if (outcount < newcount) return -1; /* to be sure */
1400     return count;
1401 }
1402
1403 void
1404 read_from_player(isr, closure, message, count, error)
1405      InputSourceRef isr;
1406      VOIDSTAR closure;
1407      char *message;
1408      int count;
1409      int error;
1410 {
1411     int outError, outCount;
1412     static int gotEof = 0;
1413
1414     /* Pass data read from player on to ICS */
1415     if (count > 0) {
1416         gotEof = 0;
1417         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1418         if (outCount < count) {
1419             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1420         }
1421     } else if (count < 0) {
1422         RemoveInputSource(isr);
1423         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1424     } else if (gotEof++ > 0) {
1425         RemoveInputSource(isr);
1426         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1427     }
1428 }
1429
1430 void
1431 KeepAlive()
1432 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1433     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1434     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1435     SendToICS("date\n");
1436     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1437 }
1438
1439 /* added routine for printf style output to ics */
1440 void ics_printf(char *format, ...)
1441 {
1442     char buffer[MSG_SIZ];
1443     va_list args;
1444
1445     va_start(args, format);
1446     vsnprintf(buffer, sizeof(buffer), format, args);
1447     buffer[sizeof(buffer)-1] = '\0';
1448     SendToICS(buffer);
1449     va_end(args);
1450 }
1451
1452 void
1453 SendToICS(s)
1454      char *s;
1455 {
1456     int count, outCount, outError;
1457
1458     if (icsPR == NULL) return;
1459
1460     count = strlen(s);
1461     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1462     if (outCount < count) {
1463         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1464     }
1465 }
1466
1467 /* This is used for sending logon scripts to the ICS. Sending
1468    without a delay causes problems when using timestamp on ICC
1469    (at least on my machine). */
1470 void
1471 SendToICSDelayed(s,msdelay)
1472      char *s;
1473      long msdelay;
1474 {
1475     int count, outCount, outError;
1476
1477     if (icsPR == NULL) return;
1478
1479     count = strlen(s);
1480     if (appData.debugMode) {
1481         fprintf(debugFP, ">ICS: ");
1482         show_bytes(debugFP, s, count);
1483         fprintf(debugFP, "\n");
1484     }
1485     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1486                                       msdelay);
1487     if (outCount < count) {
1488         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1489     }
1490 }
1491
1492
1493 /* Remove all highlighting escape sequences in s
1494    Also deletes any suffix starting with '(' 
1495    */
1496 char *
1497 StripHighlightAndTitle(s)
1498      char *s;
1499 {
1500     static char retbuf[MSG_SIZ];
1501     char *p = retbuf;
1502
1503     while (*s != NULLCHAR) {
1504         while (*s == '\033') {
1505             while (*s != NULLCHAR && !isalpha(*s)) s++;
1506             if (*s != NULLCHAR) s++;
1507         }
1508         while (*s != NULLCHAR && *s != '\033') {
1509             if (*s == '(' || *s == '[') {
1510                 *p = NULLCHAR;
1511                 return retbuf;
1512             }
1513             *p++ = *s++;
1514         }
1515     }
1516     *p = NULLCHAR;
1517     return retbuf;
1518 }
1519
1520 /* Remove all highlighting escape sequences in s */
1521 char *
1522 StripHighlight(s)
1523      char *s;
1524 {
1525     static char retbuf[MSG_SIZ];
1526     char *p = retbuf;
1527
1528     while (*s != NULLCHAR) {
1529         while (*s == '\033') {
1530             while (*s != NULLCHAR && !isalpha(*s)) s++;
1531             if (*s != NULLCHAR) s++;
1532         }
1533         while (*s != NULLCHAR && *s != '\033') {
1534             *p++ = *s++;
1535         }
1536     }
1537     *p = NULLCHAR;
1538     return retbuf;
1539 }
1540
1541 char *variantNames[] = VARIANT_NAMES;
1542 char *
1543 VariantName(v)
1544      VariantClass v;
1545 {
1546     return variantNames[v];
1547 }
1548
1549
1550 /* Identify a variant from the strings the chess servers use or the
1551    PGN Variant tag names we use. */
1552 VariantClass
1553 StringToVariant(e)
1554      char *e;
1555 {
1556     char *p;
1557     int wnum = -1;
1558     VariantClass v = VariantNormal;
1559     int i, found = FALSE;
1560     char buf[MSG_SIZ];
1561
1562     if (!e) return v;
1563
1564     /* [HGM] skip over optional board-size prefixes */
1565     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1566         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1567         while( *e++ != '_');
1568     }
1569
1570     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1571         v = VariantNormal;
1572         found = TRUE;
1573     } else
1574     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1575       if (StrCaseStr(e, variantNames[i])) {
1576         v = (VariantClass) i;
1577         found = TRUE;
1578         break;
1579       }
1580     }
1581
1582     if (!found) {
1583       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1584           || StrCaseStr(e, "wild/fr") 
1585           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1586         v = VariantFischeRandom;
1587       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1588                  (i = 1, p = StrCaseStr(e, "w"))) {
1589         p += i;
1590         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1591         if (isdigit(*p)) {
1592           wnum = atoi(p);
1593         } else {
1594           wnum = -1;
1595         }
1596         switch (wnum) {
1597         case 0: /* FICS only, actually */
1598         case 1:
1599           /* Castling legal even if K starts on d-file */
1600           v = VariantWildCastle;
1601           break;
1602         case 2:
1603         case 3:
1604         case 4:
1605           /* Castling illegal even if K & R happen to start in
1606              normal positions. */
1607           v = VariantNoCastle;
1608           break;
1609         case 5:
1610         case 7:
1611         case 8:
1612         case 10:
1613         case 11:
1614         case 12:
1615         case 13:
1616         case 14:
1617         case 15:
1618         case 18:
1619         case 19:
1620           /* Castling legal iff K & R start in normal positions */
1621           v = VariantNormal;
1622           break;
1623         case 6:
1624         case 20:
1625         case 21:
1626           /* Special wilds for position setup; unclear what to do here */
1627           v = VariantLoadable;
1628           break;
1629         case 9:
1630           /* Bizarre ICC game */
1631           v = VariantTwoKings;
1632           break;
1633         case 16:
1634           v = VariantKriegspiel;
1635           break;
1636         case 17:
1637           v = VariantLosers;
1638           break;
1639         case 22:
1640           v = VariantFischeRandom;
1641           break;
1642         case 23:
1643           v = VariantCrazyhouse;
1644           break;
1645         case 24:
1646           v = VariantBughouse;
1647           break;
1648         case 25:
1649           v = Variant3Check;
1650           break;
1651         case 26:
1652           /* Not quite the same as FICS suicide! */
1653           v = VariantGiveaway;
1654           break;
1655         case 27:
1656           v = VariantAtomic;
1657           break;
1658         case 28:
1659           v = VariantShatranj;
1660           break;
1661
1662         /* Temporary names for future ICC types.  The name *will* change in 
1663            the next xboard/WinBoard release after ICC defines it. */
1664         case 29:
1665           v = Variant29;
1666           break;
1667         case 30:
1668           v = Variant30;
1669           break;
1670         case 31:
1671           v = Variant31;
1672           break;
1673         case 32:
1674           v = Variant32;
1675           break;
1676         case 33:
1677           v = Variant33;
1678           break;
1679         case 34:
1680           v = Variant34;
1681           break;
1682         case 35:
1683           v = Variant35;
1684           break;
1685         case 36:
1686           v = Variant36;
1687           break;
1688         case 37:
1689           v = VariantShogi;
1690           break;
1691         case 38:
1692           v = VariantXiangqi;
1693           break;
1694         case 39:
1695           v = VariantCourier;
1696           break;
1697         case 40:
1698           v = VariantGothic;
1699           break;
1700         case 41:
1701           v = VariantCapablanca;
1702           break;
1703         case 42:
1704           v = VariantKnightmate;
1705           break;
1706         case 43:
1707           v = VariantFairy;
1708           break;
1709         case 44:
1710           v = VariantCylinder;
1711           break;
1712         case 45:
1713           v = VariantFalcon;
1714           break;
1715         case 46:
1716           v = VariantCapaRandom;
1717           break;
1718         case 47:
1719           v = VariantBerolina;
1720           break;
1721         case 48:
1722           v = VariantJanus;
1723           break;
1724         case 49:
1725           v = VariantSuper;
1726           break;
1727         case 50:
1728           v = VariantGreat;
1729           break;
1730         case -1:
1731           /* Found "wild" or "w" in the string but no number;
1732              must assume it's normal chess. */
1733           v = VariantNormal;
1734           break;
1735         default:
1736           sprintf(buf, _("Unknown wild type %d"), wnum);
1737           DisplayError(buf, 0);
1738           v = VariantUnknown;
1739           break;
1740         }
1741       }
1742     }
1743     if (appData.debugMode) {
1744       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1745               e, wnum, VariantName(v));
1746     }
1747     return v;
1748 }
1749
1750 static int leftover_start = 0, leftover_len = 0;
1751 char star_match[STAR_MATCH_N][MSG_SIZ];
1752
1753 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1754    advance *index beyond it, and set leftover_start to the new value of
1755    *index; else return FALSE.  If pattern contains the character '*', it
1756    matches any sequence of characters not containing '\r', '\n', or the
1757    character following the '*' (if any), and the matched sequence(s) are
1758    copied into star_match.
1759    */
1760 int
1761 looking_at(buf, index, pattern)
1762      char *buf;
1763      int *index;
1764      char *pattern;
1765 {
1766     char *bufp = &buf[*index], *patternp = pattern;
1767     int star_count = 0;
1768     char *matchp = star_match[0];
1769     
1770     for (;;) {
1771         if (*patternp == NULLCHAR) {
1772             *index = leftover_start = bufp - buf;
1773             *matchp = NULLCHAR;
1774             return TRUE;
1775         }
1776         if (*bufp == NULLCHAR) return FALSE;
1777         if (*patternp == '*') {
1778             if (*bufp == *(patternp + 1)) {
1779                 *matchp = NULLCHAR;
1780                 matchp = star_match[++star_count];
1781                 patternp += 2;
1782                 bufp++;
1783                 continue;
1784             } else if (*bufp == '\n' || *bufp == '\r') {
1785                 patternp++;
1786                 if (*patternp == NULLCHAR)
1787                   continue;
1788                 else
1789                   return FALSE;
1790             } else {
1791                 *matchp++ = *bufp++;
1792                 continue;
1793             }
1794         }
1795         if (*patternp != *bufp) return FALSE;
1796         patternp++;
1797         bufp++;
1798     }
1799 }
1800
1801 void
1802 SendToPlayer(data, length)
1803      char *data;
1804      int length;
1805 {
1806     int error, outCount;
1807     outCount = OutputToProcess(NoProc, data, length, &error);
1808     if (outCount < length) {
1809         DisplayFatalError(_("Error writing to display"), error, 1);
1810     }
1811 }
1812
1813 void
1814 PackHolding(packed, holding)
1815      char packed[];
1816      char *holding;
1817 {
1818     char *p = holding;
1819     char *q = packed;
1820     int runlength = 0;
1821     int curr = 9999;
1822     do {
1823         if (*p == curr) {
1824             runlength++;
1825         } else {
1826             switch (runlength) {
1827               case 0:
1828                 break;
1829               case 1:
1830                 *q++ = curr;
1831                 break;
1832               case 2:
1833                 *q++ = curr;
1834                 *q++ = curr;
1835                 break;
1836               default:
1837                 sprintf(q, "%d", runlength);
1838                 while (*q) q++;
1839                 *q++ = curr;
1840                 break;
1841             }
1842             runlength = 1;
1843             curr = *p;
1844         }
1845     } while (*p++);
1846     *q = NULLCHAR;
1847 }
1848
1849 /* Telnet protocol requests from the front end */
1850 void
1851 TelnetRequest(ddww, option)
1852      unsigned char ddww, option;
1853 {
1854     unsigned char msg[3];
1855     int outCount, outError;
1856
1857     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1858
1859     if (appData.debugMode) {
1860         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1861         switch (ddww) {
1862           case TN_DO:
1863             ddwwStr = "DO";
1864             break;
1865           case TN_DONT:
1866             ddwwStr = "DONT";
1867             break;
1868           case TN_WILL:
1869             ddwwStr = "WILL";
1870             break;
1871           case TN_WONT:
1872             ddwwStr = "WONT";
1873             break;
1874           default:
1875             ddwwStr = buf1;
1876             sprintf(buf1, "%d", ddww);
1877             break;
1878         }
1879         switch (option) {
1880           case TN_ECHO:
1881             optionStr = "ECHO";
1882             break;
1883           default:
1884             optionStr = buf2;
1885             sprintf(buf2, "%d", option);
1886             break;
1887         }
1888         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1889     }
1890     msg[0] = TN_IAC;
1891     msg[1] = ddww;
1892     msg[2] = option;
1893     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1894     if (outCount < 3) {
1895         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1896     }
1897 }
1898
1899 void
1900 DoEcho()
1901 {
1902     if (!appData.icsActive) return;
1903     TelnetRequest(TN_DO, TN_ECHO);
1904 }
1905
1906 void
1907 DontEcho()
1908 {
1909     if (!appData.icsActive) return;
1910     TelnetRequest(TN_DONT, TN_ECHO);
1911 }
1912
1913 void
1914 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1915 {
1916     /* put the holdings sent to us by the server on the board holdings area */
1917     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1918     char p;
1919     ChessSquare piece;
1920
1921     if(gameInfo.holdingsWidth < 2)  return;
1922     if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1923         return; // prevent overwriting by pre-board holdings
1924
1925     if( (int)lowestPiece >= BlackPawn ) {
1926         holdingsColumn = 0;
1927         countsColumn = 1;
1928         holdingsStartRow = BOARD_HEIGHT-1;
1929         direction = -1;
1930     } else {
1931         holdingsColumn = BOARD_WIDTH-1;
1932         countsColumn = BOARD_WIDTH-2;
1933         holdingsStartRow = 0;
1934         direction = 1;
1935     }
1936
1937     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1938         board[i][holdingsColumn] = EmptySquare;
1939         board[i][countsColumn]   = (ChessSquare) 0;
1940     }
1941     while( (p=*holdings++) != NULLCHAR ) {
1942         piece = CharToPiece( ToUpper(p) );
1943         if(piece == EmptySquare) continue;
1944         /*j = (int) piece - (int) WhitePawn;*/
1945         j = PieceToNumber(piece);
1946         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1947         if(j < 0) continue;               /* should not happen */
1948         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1949         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1950         board[holdingsStartRow+j*direction][countsColumn]++;
1951     }
1952 }
1953
1954
1955 void
1956 VariantSwitch(Board board, VariantClass newVariant)
1957 {
1958    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1959    Board oldBoard;
1960
1961    startedFromPositionFile = FALSE;
1962    if(gameInfo.variant == newVariant) return;
1963
1964    /* [HGM] This routine is called each time an assignment is made to
1965     * gameInfo.variant during a game, to make sure the board sizes
1966     * are set to match the new variant. If that means adding or deleting
1967     * holdings, we shift the playing board accordingly
1968     * This kludge is needed because in ICS observe mode, we get boards
1969     * of an ongoing game without knowing the variant, and learn about the
1970     * latter only later. This can be because of the move list we requested,
1971     * in which case the game history is refilled from the beginning anyway,
1972     * but also when receiving holdings of a crazyhouse game. In the latter
1973     * case we want to add those holdings to the already received position.
1974     */
1975
1976    
1977    if (appData.debugMode) {
1978      fprintf(debugFP, "Switch board from %s to %s\n",
1979              VariantName(gameInfo.variant), VariantName(newVariant));
1980      setbuf(debugFP, NULL);
1981    }
1982    shuffleOpenings = 0;       /* [HGM] shuffle */
1983    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1984    switch(newVariant) 
1985      {
1986      case VariantShogi:
1987        newWidth = 9;  newHeight = 9;
1988        gameInfo.holdingsSize = 7;
1989      case VariantBughouse:
1990      case VariantCrazyhouse:
1991        newHoldingsWidth = 2; break;
1992      case VariantGreat:
1993        newWidth = 10;
1994      case VariantSuper:
1995        newHoldingsWidth = 2;
1996        gameInfo.holdingsSize = 8;
1997        break;
1998      case VariantGothic:
1999      case VariantCapablanca:
2000      case VariantCapaRandom:
2001        newWidth = 10;
2002      default:
2003        newHoldingsWidth = gameInfo.holdingsSize = 0;
2004      };
2005    
2006    if(newWidth  != gameInfo.boardWidth  ||
2007       newHeight != gameInfo.boardHeight ||
2008       newHoldingsWidth != gameInfo.holdingsWidth ) {
2009      
2010      /* shift position to new playing area, if needed */
2011      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2012        for(i=0; i<BOARD_HEIGHT; i++) 
2013          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2014            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2015              board[i][j];
2016        for(i=0; i<newHeight; i++) {
2017          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2018          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2019        }
2020      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2021        for(i=0; i<BOARD_HEIGHT; i++)
2022          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2023            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2024              board[i][j];
2025      }
2026      gameInfo.boardWidth  = newWidth;
2027      gameInfo.boardHeight = newHeight;
2028      gameInfo.holdingsWidth = newHoldingsWidth;
2029      gameInfo.variant = newVariant;
2030      InitDrawingSizes(-2, 0);
2031    } else gameInfo.variant = newVariant;
2032    CopyBoard(oldBoard, board);   // remember correctly formatted board
2033      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2034    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2035 }
2036
2037 static int loggedOn = FALSE;
2038
2039 /*-- Game start info cache: --*/
2040 int gs_gamenum;
2041 char gs_kind[MSG_SIZ];
2042 static char player1Name[128] = "";
2043 static char player2Name[128] = "";
2044 static char cont_seq[] = "\n\\   ";
2045 static int player1Rating = -1;
2046 static int player2Rating = -1;
2047 /*----------------------------*/
2048
2049 ColorClass curColor = ColorNormal;
2050 int suppressKibitz = 0;
2051
2052 void
2053 read_from_ics(isr, closure, data, count, error)
2054      InputSourceRef isr;
2055      VOIDSTAR closure;
2056      char *data;
2057      int count;
2058      int error;
2059 {
2060 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2061 #define STARTED_NONE 0
2062 #define STARTED_MOVES 1
2063 #define STARTED_BOARD 2
2064 #define STARTED_OBSERVE 3
2065 #define STARTED_HOLDINGS 4
2066 #define STARTED_CHATTER 5
2067 #define STARTED_COMMENT 6
2068 #define STARTED_MOVES_NOHIDE 7
2069     
2070     static int started = STARTED_NONE;
2071     static char parse[20000];
2072     static int parse_pos = 0;
2073     static char buf[BUF_SIZE + 1];
2074     static int firstTime = TRUE, intfSet = FALSE;
2075     static ColorClass prevColor = ColorNormal;
2076     static int savingComment = FALSE;
2077     static int cmatch = 0; // continuation sequence match
2078     char *bp;
2079     char str[500];
2080     int i, oldi;
2081     int buf_len;
2082     int next_out;
2083     int tkind;
2084     int backup;    /* [DM] For zippy color lines */
2085     char *p;
2086     char talker[MSG_SIZ]; // [HGM] chat
2087     int channel;
2088
2089     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2090
2091     if (appData.debugMode) {
2092       if (!error) {
2093         fprintf(debugFP, "<ICS: ");
2094         show_bytes(debugFP, data, count);
2095         fprintf(debugFP, "\n");
2096       }
2097     }
2098
2099     if (appData.debugMode) { int f = forwardMostMove;
2100         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2101                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2102     }
2103     if (count > 0) {
2104         /* If last read ended with a partial line that we couldn't parse,
2105            prepend it to the new read and try again. */
2106         if (leftover_len > 0) {
2107             for (i=0; i<leftover_len; i++)
2108               buf[i] = buf[leftover_start + i];
2109         }
2110
2111     /* copy new characters into the buffer */
2112     bp = buf + leftover_len;
2113     buf_len=leftover_len;
2114     for (i=0; i<count; i++)
2115     {
2116         // ignore these
2117         if (data[i] == '\r')
2118             continue;
2119
2120         // join lines split by ICS?
2121         if (!appData.noJoin)
2122         {
2123             /*
2124                 Joining just consists of finding matches against the
2125                 continuation sequence, and discarding that sequence
2126                 if found instead of copying it.  So, until a match
2127                 fails, there's nothing to do since it might be the
2128                 complete sequence, and thus, something we don't want
2129                 copied.
2130             */
2131             if (data[i] == cont_seq[cmatch])
2132             {
2133                 cmatch++;
2134                 if (cmatch == strlen(cont_seq))
2135                 {
2136                     cmatch = 0; // complete match.  just reset the counter
2137
2138                     /*
2139                         it's possible for the ICS to not include the space
2140                         at the end of the last word, making our [correct]
2141                         join operation fuse two separate words.  the server
2142                         does this when the space occurs at the width setting.
2143                     */
2144                     if (!buf_len || buf[buf_len-1] != ' ')
2145                     {
2146                         *bp++ = ' ';
2147                         buf_len++;
2148                     }
2149                 }
2150                 continue;
2151             }
2152             else if (cmatch)
2153             {
2154                 /*
2155                     match failed, so we have to copy what matched before
2156                     falling through and copying this character.  In reality,
2157                     this will only ever be just the newline character, but
2158                     it doesn't hurt to be precise.
2159                 */
2160                 strncpy(bp, cont_seq, cmatch);
2161                 bp += cmatch;
2162                 buf_len += cmatch;
2163                 cmatch = 0;
2164             }
2165         }
2166
2167         // copy this char
2168         *bp++ = data[i];
2169         buf_len++;
2170     }
2171
2172         buf[buf_len] = NULLCHAR;
2173 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2174         next_out = 0;
2175         leftover_start = 0;
2176         
2177         i = 0;
2178         while (i < buf_len) {
2179             /* Deal with part of the TELNET option negotiation
2180                protocol.  We refuse to do anything beyond the
2181                defaults, except that we allow the WILL ECHO option,
2182                which ICS uses to turn off password echoing when we are
2183                directly connected to it.  We reject this option
2184                if localLineEditing mode is on (always on in xboard)
2185                and we are talking to port 23, which might be a real
2186                telnet server that will try to keep WILL ECHO on permanently.
2187              */
2188             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2189                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2190                 unsigned char option;
2191                 oldi = i;
2192                 switch ((unsigned char) buf[++i]) {
2193                   case TN_WILL:
2194                     if (appData.debugMode)
2195                       fprintf(debugFP, "\n<WILL ");
2196                     switch (option = (unsigned char) buf[++i]) {
2197                       case TN_ECHO:
2198                         if (appData.debugMode)
2199                           fprintf(debugFP, "ECHO ");
2200                         /* Reply only if this is a change, according
2201                            to the protocol rules. */
2202                         if (remoteEchoOption) break;
2203                         if (appData.localLineEditing &&
2204                             atoi(appData.icsPort) == TN_PORT) {
2205                             TelnetRequest(TN_DONT, TN_ECHO);
2206                         } else {
2207                             EchoOff();
2208                             TelnetRequest(TN_DO, TN_ECHO);
2209                             remoteEchoOption = TRUE;
2210                         }
2211                         break;
2212                       default:
2213                         if (appData.debugMode)
2214                           fprintf(debugFP, "%d ", option);
2215                         /* Whatever this is, we don't want it. */
2216                         TelnetRequest(TN_DONT, option);
2217                         break;
2218                     }
2219                     break;
2220                   case TN_WONT:
2221                     if (appData.debugMode)
2222                       fprintf(debugFP, "\n<WONT ");
2223                     switch (option = (unsigned char) buf[++i]) {
2224                       case TN_ECHO:
2225                         if (appData.debugMode)
2226                           fprintf(debugFP, "ECHO ");
2227                         /* Reply only if this is a change, according
2228                            to the protocol rules. */
2229                         if (!remoteEchoOption) break;
2230                         EchoOn();
2231                         TelnetRequest(TN_DONT, TN_ECHO);
2232                         remoteEchoOption = FALSE;
2233                         break;
2234                       default:
2235                         if (appData.debugMode)
2236                           fprintf(debugFP, "%d ", (unsigned char) option);
2237                         /* Whatever this is, it must already be turned
2238                            off, because we never agree to turn on
2239                            anything non-default, so according to the
2240                            protocol rules, we don't reply. */
2241                         break;
2242                     }
2243                     break;
2244                   case TN_DO:
2245                     if (appData.debugMode)
2246                       fprintf(debugFP, "\n<DO ");
2247                     switch (option = (unsigned char) buf[++i]) {
2248                       default:
2249                         /* Whatever this is, we refuse to do it. */
2250                         if (appData.debugMode)
2251                           fprintf(debugFP, "%d ", option);
2252                         TelnetRequest(TN_WONT, option);
2253                         break;
2254                     }
2255                     break;
2256                   case TN_DONT:
2257                     if (appData.debugMode)
2258                       fprintf(debugFP, "\n<DONT ");
2259                     switch (option = (unsigned char) buf[++i]) {
2260                       default:
2261                         if (appData.debugMode)
2262                           fprintf(debugFP, "%d ", option);
2263                         /* Whatever this is, we are already not doing
2264                            it, because we never agree to do anything
2265                            non-default, so according to the protocol
2266                            rules, we don't reply. */
2267                         break;
2268                     }
2269                     break;
2270                   case TN_IAC:
2271                     if (appData.debugMode)
2272                       fprintf(debugFP, "\n<IAC ");
2273                     /* Doubled IAC; pass it through */
2274                     i--;
2275                     break;
2276                   default:
2277                     if (appData.debugMode)
2278                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2279                     /* Drop all other telnet commands on the floor */
2280                     break;
2281                 }
2282                 if (oldi > next_out)
2283                   SendToPlayer(&buf[next_out], oldi - next_out);
2284                 if (++i > next_out)
2285                   next_out = i;
2286                 continue;
2287             }
2288                 
2289             /* OK, this at least will *usually* work */
2290             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2291                 loggedOn = TRUE;
2292             }
2293             
2294             if (loggedOn && !intfSet) {
2295                 if (ics_type == ICS_ICC) {
2296                   sprintf(str,
2297                           "/set-quietly interface %s\n/set-quietly style 12\n",
2298                           programVersion);
2299                 } else if (ics_type == ICS_CHESSNET) {
2300                   sprintf(str, "/style 12\n");
2301                 } else {
2302                   strcpy(str, "alias $ @\n$set interface ");
2303                   strcat(str, programVersion);
2304                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2305 #ifdef WIN32
2306                   strcat(str, "$iset nohighlight 1\n");
2307 #endif
2308                   strcat(str, "$iset lock 1\n$style 12\n");
2309                 }
2310                 SendToICS(str);
2311                 NotifyFrontendLogin();
2312                 intfSet = TRUE;
2313             }
2314
2315             if (started == STARTED_COMMENT) {
2316                 /* Accumulate characters in comment */
2317                 parse[parse_pos++] = buf[i];
2318                 if (buf[i] == '\n') {
2319                     parse[parse_pos] = NULLCHAR;
2320                     if(chattingPartner>=0) {
2321                         char mess[MSG_SIZ];
2322                         sprintf(mess, "%s%s", talker, parse);
2323                         OutputChatMessage(chattingPartner, mess);
2324                         chattingPartner = -1;
2325                     } else
2326                     if(!suppressKibitz) // [HGM] kibitz
2327                         AppendComment(forwardMostMove, StripHighlight(parse));
2328                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2329                         int nrDigit = 0, nrAlph = 0, j;
2330                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2331                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2332                         parse[parse_pos] = NULLCHAR;
2333                         // try to be smart: if it does not look like search info, it should go to
2334                         // ICS interaction window after all, not to engine-output window.
2335                         for(j=0; j<parse_pos; j++) { // count letters and digits
2336                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2337                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2338                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2339                         }
2340                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2341                             int depth=0; float score;
2342                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2343                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2344                                 pvInfoList[forwardMostMove-1].depth = depth;
2345                                 pvInfoList[forwardMostMove-1].score = 100*score;
2346                             }
2347                             OutputKibitz(suppressKibitz, parse);
2348                             next_out = i+1; // [HGM] suppress printing in ICS window
2349                         } else {
2350                             char tmp[MSG_SIZ];
2351                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2352                             SendToPlayer(tmp, strlen(tmp));
2353                         }
2354                     }
2355                     started = STARTED_NONE;
2356                 } else {
2357                     /* Don't match patterns against characters in comment */
2358                     i++;
2359                     continue;
2360                 }
2361             }
2362             if (started == STARTED_CHATTER) {
2363                 if (buf[i] != '\n') {
2364                     /* Don't match patterns against characters in chatter */
2365                     i++;
2366                     continue;
2367                 }
2368                 started = STARTED_NONE;
2369             }
2370
2371             /* Kludge to deal with rcmd protocol */
2372             if (firstTime && looking_at(buf, &i, "\001*")) {
2373                 DisplayFatalError(&buf[1], 0, 1);
2374                 continue;
2375             } else {
2376                 firstTime = FALSE;
2377             }
2378
2379             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2380                 ics_type = ICS_ICC;
2381                 ics_prefix = "/";
2382                 if (appData.debugMode)
2383                   fprintf(debugFP, "ics_type %d\n", ics_type);
2384                 continue;
2385             }
2386             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2387                 ics_type = ICS_FICS;
2388                 ics_prefix = "$";
2389                 if (appData.debugMode)
2390                   fprintf(debugFP, "ics_type %d\n", ics_type);
2391                 continue;
2392             }
2393             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2394                 ics_type = ICS_CHESSNET;
2395                 ics_prefix = "/";
2396                 if (appData.debugMode)
2397                   fprintf(debugFP, "ics_type %d\n", ics_type);
2398                 continue;
2399             }
2400
2401             if (!loggedOn &&
2402                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2403                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2404                  looking_at(buf, &i, "will be \"*\""))) {
2405               strcpy(ics_handle, star_match[0]);
2406               continue;
2407             }
2408
2409             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2410               char buf[MSG_SIZ];
2411               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2412               DisplayIcsInteractionTitle(buf);
2413               have_set_title = TRUE;
2414             }
2415
2416             /* skip finger notes */
2417             if (started == STARTED_NONE &&
2418                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2419                  (buf[i] == '1' && buf[i+1] == '0')) &&
2420                 buf[i+2] == ':' && buf[i+3] == ' ') {
2421               started = STARTED_CHATTER;
2422               i += 3;
2423               continue;
2424             }
2425
2426             /* skip formula vars */
2427             if (started == STARTED_NONE &&
2428                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2429               started = STARTED_CHATTER;
2430               i += 3;
2431               continue;
2432             }
2433
2434             oldi = i;
2435             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2436             if (appData.autoKibitz && started == STARTED_NONE && 
2437                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2438                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2439                 if(looking_at(buf, &i, "* kibitzes: ") &&
2440                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2441                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2442                         suppressKibitz = TRUE;
2443                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2444                                 && (gameMode == IcsPlayingWhite)) ||
2445                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2446                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2447                             started = STARTED_CHATTER; // own kibitz we simply discard
2448                         else {
2449                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2450                             parse_pos = 0; parse[0] = NULLCHAR;
2451                             savingComment = TRUE;
2452                             suppressKibitz = gameMode != IcsObserving ? 2 :
2453                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2454                         } 
2455                         continue;
2456                 } else
2457                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2458                     // suppress the acknowledgements of our own autoKibitz
2459                     char *p;
2460                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2461                     SendToPlayer(star_match[0], strlen(star_match[0]));
2462                     looking_at(buf, &i, "*% "); // eat prompt
2463                     next_out = i;
2464                 }
2465             } // [HGM] kibitz: end of patch
2466
2467 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2468
2469             // [HGM] chat: intercept tells by users for which we have an open chat window
2470             channel = -1;
2471             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2472                                            looking_at(buf, &i, "* whispers:") ||
2473                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2474                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2475                 int p;
2476                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2477                 chattingPartner = -1;
2478
2479                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2480                 for(p=0; p<MAX_CHAT; p++) {
2481                     if(channel == atoi(chatPartner[p])) {
2482                     talker[0] = '['; strcat(talker, "] ");
2483                     chattingPartner = p; break;
2484                     }
2485                 } else
2486                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2487                 for(p=0; p<MAX_CHAT; p++) {
2488                     if(!strcmp("WHISPER", chatPartner[p])) {
2489                         talker[0] = '['; strcat(talker, "] ");
2490                         chattingPartner = p; break;
2491                     }
2492                 }
2493                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2494                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2495                     talker[0] = 0;
2496                     chattingPartner = p; break;
2497                 }
2498                 if(chattingPartner<0) i = oldi; else {
2499                     started = STARTED_COMMENT;
2500                     parse_pos = 0; parse[0] = NULLCHAR;
2501                     savingComment = 3 + chattingPartner; // counts as TRUE
2502                     suppressKibitz = TRUE;
2503                 }
2504             } // [HGM] chat: end of patch
2505
2506             if (appData.zippyTalk || appData.zippyPlay) {
2507                 /* [DM] Backup address for color zippy lines */
2508                 backup = i;
2509 #if ZIPPY
2510        #ifdef WIN32
2511                if (loggedOn == TRUE)
2512                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2513                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2514        #else
2515                 if (ZippyControl(buf, &i) ||
2516                     ZippyConverse(buf, &i) ||
2517                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2518                       loggedOn = TRUE;
2519                       if (!appData.colorize) continue;
2520                 }
2521        #endif
2522 #endif
2523             } // [DM] 'else { ' deleted
2524                 if (
2525                     /* Regular tells and says */
2526                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2527                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2528                     looking_at(buf, &i, "* says: ") ||
2529                     /* Don't color "message" or "messages" output */
2530                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2531                     looking_at(buf, &i, "*. * at *:*: ") ||
2532                     looking_at(buf, &i, "--* (*:*): ") ||
2533                     /* Message notifications (same color as tells) */
2534                     looking_at(buf, &i, "* has left a message ") ||
2535                     looking_at(buf, &i, "* just sent you a message:\n") ||
2536                     /* Whispers and kibitzes */
2537                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2538                     looking_at(buf, &i, "* kibitzes: ") ||
2539                     /* Channel tells */
2540                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2541
2542                   if (tkind == 1 && strchr(star_match[0], ':')) {
2543                       /* Avoid "tells you:" spoofs in channels */
2544                      tkind = 3;
2545                   }
2546                   if (star_match[0][0] == NULLCHAR ||
2547                       strchr(star_match[0], ' ') ||
2548                       (tkind == 3 && strchr(star_match[1], ' '))) {
2549                     /* Reject bogus matches */
2550                     i = oldi;
2551                   } else {
2552                     if (appData.colorize) {
2553                       if (oldi > next_out) {
2554                         SendToPlayer(&buf[next_out], oldi - next_out);
2555                         next_out = oldi;
2556                       }
2557                       switch (tkind) {
2558                       case 1:
2559                         Colorize(ColorTell, FALSE);
2560                         curColor = ColorTell;
2561                         break;
2562                       case 2:
2563                         Colorize(ColorKibitz, FALSE);
2564                         curColor = ColorKibitz;
2565                         break;
2566                       case 3:
2567                         p = strrchr(star_match[1], '(');
2568                         if (p == NULL) {
2569                           p = star_match[1];
2570                         } else {
2571                           p++;
2572                         }
2573                         if (atoi(p) == 1) {
2574                           Colorize(ColorChannel1, FALSE);
2575                           curColor = ColorChannel1;
2576                         } else {
2577                           Colorize(ColorChannel, FALSE);
2578                           curColor = ColorChannel;
2579                         }
2580                         break;
2581                       case 5:
2582                         curColor = ColorNormal;
2583                         break;
2584                       }
2585                     }
2586                     if (started == STARTED_NONE && appData.autoComment &&
2587                         (gameMode == IcsObserving ||
2588                          gameMode == IcsPlayingWhite ||
2589                          gameMode == IcsPlayingBlack)) {
2590                       parse_pos = i - oldi;
2591                       memcpy(parse, &buf[oldi], parse_pos);
2592                       parse[parse_pos] = NULLCHAR;
2593                       started = STARTED_COMMENT;
2594                       savingComment = TRUE;
2595                     } else {
2596                       started = STARTED_CHATTER;
2597                       savingComment = FALSE;
2598                     }
2599                     loggedOn = TRUE;
2600                     continue;
2601                   }
2602                 }
2603
2604                 if (looking_at(buf, &i, "* s-shouts: ") ||
2605                     looking_at(buf, &i, "* c-shouts: ")) {
2606                     if (appData.colorize) {
2607                         if (oldi > next_out) {
2608                             SendToPlayer(&buf[next_out], oldi - next_out);
2609                             next_out = oldi;
2610                         }
2611                         Colorize(ColorSShout, FALSE);
2612                         curColor = ColorSShout;
2613                     }
2614                     loggedOn = TRUE;
2615                     started = STARTED_CHATTER;
2616                     continue;
2617                 }
2618
2619                 if (looking_at(buf, &i, "--->")) {
2620                     loggedOn = TRUE;
2621                     continue;
2622                 }
2623
2624                 if (looking_at(buf, &i, "* shouts: ") ||
2625                     looking_at(buf, &i, "--> ")) {
2626                     if (appData.colorize) {
2627                         if (oldi > next_out) {
2628                             SendToPlayer(&buf[next_out], oldi - next_out);
2629                             next_out = oldi;
2630                         }
2631                         Colorize(ColorShout, FALSE);
2632                         curColor = ColorShout;
2633                     }
2634                     loggedOn = TRUE;
2635                     started = STARTED_CHATTER;
2636                     continue;
2637                 }
2638
2639                 if (looking_at( buf, &i, "Challenge:")) {
2640                     if (appData.colorize) {
2641                         if (oldi > next_out) {
2642                             SendToPlayer(&buf[next_out], oldi - next_out);
2643                             next_out = oldi;
2644                         }
2645                         Colorize(ColorChallenge, FALSE);
2646                         curColor = ColorChallenge;
2647                     }
2648                     loggedOn = TRUE;
2649                     continue;
2650                 }
2651
2652                 if (looking_at(buf, &i, "* offers you") ||
2653                     looking_at(buf, &i, "* offers to be") ||
2654                     looking_at(buf, &i, "* would like to") ||
2655                     looking_at(buf, &i, "* requests to") ||
2656                     looking_at(buf, &i, "Your opponent offers") ||
2657                     looking_at(buf, &i, "Your opponent requests")) {
2658
2659                     if (appData.colorize) {
2660                         if (oldi > next_out) {
2661                             SendToPlayer(&buf[next_out], oldi - next_out);
2662                             next_out = oldi;
2663                         }
2664                         Colorize(ColorRequest, FALSE);
2665                         curColor = ColorRequest;
2666                     }
2667                     continue;
2668                 }
2669
2670                 if (looking_at(buf, &i, "* (*) seeking")) {
2671                     if (appData.colorize) {
2672                         if (oldi > next_out) {
2673                             SendToPlayer(&buf[next_out], oldi - next_out);
2674                             next_out = oldi;
2675                         }
2676                         Colorize(ColorSeek, FALSE);
2677                         curColor = ColorSeek;
2678                     }
2679                     continue;
2680             }
2681
2682             if (looking_at(buf, &i, "\\   ")) {
2683                 if (prevColor != ColorNormal) {
2684                     if (oldi > next_out) {
2685                         SendToPlayer(&buf[next_out], oldi - next_out);
2686                         next_out = oldi;
2687                     }
2688                     Colorize(prevColor, TRUE);
2689                     curColor = prevColor;
2690                 }
2691                 if (savingComment) {
2692                     parse_pos = i - oldi;
2693                     memcpy(parse, &buf[oldi], parse_pos);
2694                     parse[parse_pos] = NULLCHAR;
2695                     started = STARTED_COMMENT;
2696                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2697                         chattingPartner = savingComment - 3; // kludge to remember the box
2698                 } else {
2699                     started = STARTED_CHATTER;
2700                 }
2701                 continue;
2702             }
2703
2704             if (looking_at(buf, &i, "Black Strength :") ||
2705                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2706                 looking_at(buf, &i, "<10>") ||
2707                 looking_at(buf, &i, "#@#")) {
2708                 /* Wrong board style */
2709                 loggedOn = TRUE;
2710                 SendToICS(ics_prefix);
2711                 SendToICS("set style 12\n");
2712                 SendToICS(ics_prefix);
2713                 SendToICS("refresh\n");
2714                 continue;
2715             }
2716             
2717             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2718                 ICSInitScript();
2719                 have_sent_ICS_logon = 1;
2720                 continue;
2721             }
2722               
2723             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2724                 (looking_at(buf, &i, "\n<12> ") ||
2725                  looking_at(buf, &i, "<12> "))) {
2726                 loggedOn = TRUE;
2727                 if (oldi > next_out) {
2728                     SendToPlayer(&buf[next_out], oldi - next_out);
2729                 }
2730                 next_out = i;
2731                 started = STARTED_BOARD;
2732                 parse_pos = 0;
2733                 continue;
2734             }
2735
2736             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2737                 looking_at(buf, &i, "<b1> ")) {
2738                 if (oldi > next_out) {
2739                     SendToPlayer(&buf[next_out], oldi - next_out);
2740                 }
2741                 next_out = i;
2742                 started = STARTED_HOLDINGS;
2743                 parse_pos = 0;
2744                 continue;
2745             }
2746
2747             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2748                 loggedOn = TRUE;
2749                 /* Header for a move list -- first line */
2750
2751                 switch (ics_getting_history) {
2752                   case H_FALSE:
2753                     switch (gameMode) {
2754                       case IcsIdle:
2755                       case BeginningOfGame:
2756                         /* User typed "moves" or "oldmoves" while we
2757                            were idle.  Pretend we asked for these
2758                            moves and soak them up so user can step
2759                            through them and/or save them.
2760                            */
2761                         Reset(FALSE, TRUE);
2762                         gameMode = IcsObserving;
2763                         ModeHighlight();
2764                         ics_gamenum = -1;
2765                         ics_getting_history = H_GOT_UNREQ_HEADER;
2766                         break;
2767                       case EditGame: /*?*/
2768                       case EditPosition: /*?*/
2769                         /* Should above feature work in these modes too? */
2770                         /* For now it doesn't */
2771                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2772                         break;
2773                       default:
2774                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2775                         break;
2776                     }
2777                     break;
2778                   case H_REQUESTED:
2779                     /* Is this the right one? */
2780                     if (gameInfo.white && gameInfo.black &&
2781                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2782                         strcmp(gameInfo.black, star_match[2]) == 0) {
2783                         /* All is well */
2784                         ics_getting_history = H_GOT_REQ_HEADER;
2785                     }
2786                     break;
2787                   case H_GOT_REQ_HEADER:
2788                   case H_GOT_UNREQ_HEADER:
2789                   case H_GOT_UNWANTED_HEADER:
2790                   case H_GETTING_MOVES:
2791                     /* Should not happen */
2792                     DisplayError(_("Error gathering move list: two headers"), 0);
2793                     ics_getting_history = H_FALSE;
2794                     break;
2795                 }
2796
2797                 /* Save player ratings into gameInfo if needed */
2798                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2799                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2800                     (gameInfo.whiteRating == -1 ||
2801                      gameInfo.blackRating == -1)) {
2802
2803                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2804                     gameInfo.blackRating = string_to_rating(star_match[3]);
2805                     if (appData.debugMode)
2806                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2807                               gameInfo.whiteRating, gameInfo.blackRating);
2808                 }
2809                 continue;
2810             }
2811
2812             if (looking_at(buf, &i,
2813               "* * match, initial time: * minute*, increment: * second")) {
2814                 /* Header for a move list -- second line */
2815                 /* Initial board will follow if this is a wild game */
2816                 if (gameInfo.event != NULL) free(gameInfo.event);
2817                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2818                 gameInfo.event = StrSave(str);
2819                 /* [HGM] we switched variant. Translate boards if needed. */
2820                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2821                 continue;
2822             }
2823
2824             if (looking_at(buf, &i, "Move  ")) {
2825                 /* Beginning of a move list */
2826                 switch (ics_getting_history) {
2827                   case H_FALSE:
2828                     /* Normally should not happen */
2829                     /* Maybe user hit reset while we were parsing */
2830                     break;
2831                   case H_REQUESTED:
2832                     /* Happens if we are ignoring a move list that is not
2833                      * the one we just requested.  Common if the user
2834                      * tries to observe two games without turning off
2835                      * getMoveList */
2836                     break;
2837                   case H_GETTING_MOVES:
2838                     /* Should not happen */
2839                     DisplayError(_("Error gathering move list: nested"), 0);
2840                     ics_getting_history = H_FALSE;
2841                     break;
2842                   case H_GOT_REQ_HEADER:
2843                     ics_getting_history = H_GETTING_MOVES;
2844                     started = STARTED_MOVES;
2845                     parse_pos = 0;
2846                     if (oldi > next_out) {
2847                         SendToPlayer(&buf[next_out], oldi - next_out);
2848                     }
2849                     break;
2850                   case H_GOT_UNREQ_HEADER:
2851                     ics_getting_history = H_GETTING_MOVES;
2852                     started = STARTED_MOVES_NOHIDE;
2853                     parse_pos = 0;
2854                     break;
2855                   case H_GOT_UNWANTED_HEADER:
2856                     ics_getting_history = H_FALSE;
2857                     break;
2858                 }
2859                 continue;
2860             }                           
2861             
2862             if (looking_at(buf, &i, "% ") ||
2863                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2864                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2865                 if(suppressKibitz) next_out = i;
2866                 savingComment = FALSE;
2867                 suppressKibitz = 0;
2868                 switch (started) {
2869                   case STARTED_MOVES:
2870                   case STARTED_MOVES_NOHIDE:
2871                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2872                     parse[parse_pos + i - oldi] = NULLCHAR;
2873                     ParseGameHistory(parse);
2874 #if ZIPPY
2875                     if (appData.zippyPlay && first.initDone) {
2876                         FeedMovesToProgram(&first, forwardMostMove);
2877                         if (gameMode == IcsPlayingWhite) {
2878                             if (WhiteOnMove(forwardMostMove)) {
2879                                 if (first.sendTime) {
2880                                   if (first.useColors) {
2881                                     SendToProgram("black\n", &first); 
2882                                   }
2883                                   SendTimeRemaining(&first, TRUE);
2884                                 }
2885                                 if (first.useColors) {
2886                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2887                                 }
2888                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2889                                 first.maybeThinking = TRUE;
2890                             } else {
2891                                 if (first.usePlayother) {
2892                                   if (first.sendTime) {
2893                                     SendTimeRemaining(&first, TRUE);
2894                                   }
2895                                   SendToProgram("playother\n", &first);
2896                                   firstMove = FALSE;
2897                                 } else {
2898                                   firstMove = TRUE;
2899                                 }
2900                             }
2901                         } else if (gameMode == IcsPlayingBlack) {
2902                             if (!WhiteOnMove(forwardMostMove)) {
2903                                 if (first.sendTime) {
2904                                   if (first.useColors) {
2905                                     SendToProgram("white\n", &first);
2906                                   }
2907                                   SendTimeRemaining(&first, FALSE);
2908                                 }
2909                                 if (first.useColors) {
2910                                   SendToProgram("black\n", &first);
2911                                 }
2912                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2913                                 first.maybeThinking = TRUE;
2914                             } else {
2915                                 if (first.usePlayother) {
2916                                   if (first.sendTime) {
2917                                     SendTimeRemaining(&first, FALSE);
2918                                   }
2919                                   SendToProgram("playother\n", &first);
2920                                   firstMove = FALSE;
2921                                 } else {
2922                                   firstMove = TRUE;
2923                                 }
2924                             }
2925                         }                       
2926                     }
2927 #endif
2928                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2929                         /* Moves came from oldmoves or moves command
2930                            while we weren't doing anything else.
2931                            */
2932                         currentMove = forwardMostMove;
2933                         ClearHighlights();/*!!could figure this out*/
2934                         flipView = appData.flipView;
2935                         DrawPosition(TRUE, boards[currentMove]);
2936                         DisplayBothClocks();
2937                         sprintf(str, "%s vs. %s",
2938                                 gameInfo.white, gameInfo.black);
2939                         DisplayTitle(str);
2940                         gameMode = IcsIdle;
2941                     } else {
2942                         /* Moves were history of an active game */
2943                         if (gameInfo.resultDetails != NULL) {
2944                             free(gameInfo.resultDetails);
2945                             gameInfo.resultDetails = NULL;
2946                         }
2947                     }
2948                     HistorySet(parseList, backwardMostMove,
2949                                forwardMostMove, currentMove-1);
2950                     DisplayMove(currentMove - 1);
2951                     if (started == STARTED_MOVES) next_out = i;
2952                     started = STARTED_NONE;
2953                     ics_getting_history = H_FALSE;
2954                     break;
2955
2956                   case STARTED_OBSERVE:
2957                     started = STARTED_NONE;
2958                     SendToICS(ics_prefix);
2959                     SendToICS("refresh\n");
2960                     break;
2961
2962                   default:
2963                     break;
2964                 }
2965                 if(bookHit) { // [HGM] book: simulate book reply
2966                     static char bookMove[MSG_SIZ]; // a bit generous?
2967
2968                     programStats.nodes = programStats.depth = programStats.time = 
2969                     programStats.score = programStats.got_only_move = 0;
2970                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2971
2972                     strcpy(bookMove, "move ");
2973                     strcat(bookMove, bookHit);
2974                     HandleMachineMove(bookMove, &first);
2975                 }
2976                 continue;
2977             }
2978             
2979             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2980                  started == STARTED_HOLDINGS ||
2981                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2982                 /* Accumulate characters in move list or board */
2983                 parse[parse_pos++] = buf[i];
2984             }
2985             
2986             /* Start of game messages.  Mostly we detect start of game
2987                when the first board image arrives.  On some versions
2988                of the ICS, though, we need to do a "refresh" after starting
2989                to observe in order to get the current board right away. */
2990             if (looking_at(buf, &i, "Adding game * to observation list")) {
2991                 started = STARTED_OBSERVE;
2992                 continue;
2993             }
2994
2995             /* Handle auto-observe */
2996             if (appData.autoObserve &&
2997                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2998                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2999                 char *player;
3000                 /* Choose the player that was highlighted, if any. */
3001                 if (star_match[0][0] == '\033' ||
3002                     star_match[1][0] != '\033') {
3003                     player = star_match[0];
3004                 } else {
3005                     player = star_match[2];
3006                 }
3007                 sprintf(str, "%sobserve %s\n",
3008                         ics_prefix, StripHighlightAndTitle(player));
3009                 SendToICS(str);
3010
3011                 /* Save ratings from notify string */
3012                 strcpy(player1Name, star_match[0]);
3013                 player1Rating = string_to_rating(star_match[1]);
3014                 strcpy(player2Name, star_match[2]);
3015                 player2Rating = string_to_rating(star_match[3]);
3016
3017                 if (appData.debugMode)
3018                   fprintf(debugFP, 
3019                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3020                           player1Name, player1Rating,
3021                           player2Name, player2Rating);
3022
3023                 continue;
3024             }
3025
3026             /* Deal with automatic examine mode after a game,
3027                and with IcsObserving -> IcsExamining transition */
3028             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3029                 looking_at(buf, &i, "has made you an examiner of game *")) {
3030
3031                 int gamenum = atoi(star_match[0]);
3032                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3033                     gamenum == ics_gamenum) {
3034                     /* We were already playing or observing this game;
3035                        no need to refetch history */
3036                     gameMode = IcsExamining;
3037                     if (pausing) {
3038                         pauseExamForwardMostMove = forwardMostMove;
3039                     } else if (currentMove < forwardMostMove) {
3040                         ForwardInner(forwardMostMove);
3041                     }
3042                 } else {
3043                     /* I don't think this case really can happen */
3044                     SendToICS(ics_prefix);
3045                     SendToICS("refresh\n");
3046                 }
3047                 continue;
3048             }    
3049             
3050             /* Error messages */
3051 //          if (ics_user_moved) {
3052             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3053                 if (looking_at(buf, &i, "Illegal move") ||
3054                     looking_at(buf, &i, "Not a legal move") ||
3055                     looking_at(buf, &i, "Your king is in check") ||
3056                     looking_at(buf, &i, "It isn't your turn") ||
3057                     looking_at(buf, &i, "It is not your move")) {
3058                     /* Illegal move */
3059                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3060                         currentMove = forwardMostMove-1;
3061                         DisplayMove(currentMove - 1); /* before DMError */
3062                         DrawPosition(FALSE, boards[currentMove]);
3063                         SwitchClocks(forwardMostMove-1); // [HGM] race
3064                         DisplayBothClocks();
3065                     }
3066                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3067                     ics_user_moved = 0;
3068                     continue;
3069                 }
3070             }
3071
3072             if (looking_at(buf, &i, "still have time") ||
3073                 looking_at(buf, &i, "not out of time") ||
3074                 looking_at(buf, &i, "either player is out of time") ||
3075                 looking_at(buf, &i, "has timeseal; checking")) {
3076                 /* We must have called his flag a little too soon */
3077                 whiteFlag = blackFlag = FALSE;
3078                 continue;
3079             }
3080
3081             if (looking_at(buf, &i, "added * seconds to") ||
3082                 looking_at(buf, &i, "seconds were added to")) {
3083                 /* Update the clocks */
3084                 SendToICS(ics_prefix);
3085                 SendToICS("refresh\n");
3086                 continue;
3087             }
3088
3089             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3090                 ics_clock_paused = TRUE;
3091                 StopClocks();
3092                 continue;
3093             }
3094
3095             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3096                 ics_clock_paused = FALSE;
3097                 StartClocks();
3098                 continue;
3099             }
3100
3101             /* Grab player ratings from the Creating: message.
3102                Note we have to check for the special case when
3103                the ICS inserts things like [white] or [black]. */
3104             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3105                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3106                 /* star_matches:
3107                    0    player 1 name (not necessarily white)
3108                    1    player 1 rating
3109                    2    empty, white, or black (IGNORED)
3110                    3    player 2 name (not necessarily black)
3111                    4    player 2 rating
3112                    
3113                    The names/ratings are sorted out when the game
3114                    actually starts (below).
3115                 */
3116                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3117                 player1Rating = string_to_rating(star_match[1]);
3118                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3119                 player2Rating = string_to_rating(star_match[4]);
3120
3121                 if (appData.debugMode)
3122                   fprintf(debugFP, 
3123                           "Ratings from 'Creating:' %s %d, %s %d\n",
3124                           player1Name, player1Rating,
3125                           player2Name, player2Rating);
3126
3127                 continue;
3128             }
3129             
3130             /* Improved generic start/end-of-game messages */
3131             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3132                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3133                 /* If tkind == 0: */
3134                 /* star_match[0] is the game number */
3135                 /*           [1] is the white player's name */
3136                 /*           [2] is the black player's name */
3137                 /* For end-of-game: */
3138                 /*           [3] is the reason for the game end */
3139                 /*           [4] is a PGN end game-token, preceded by " " */
3140                 /* For start-of-game: */
3141                 /*           [3] begins with "Creating" or "Continuing" */
3142                 /*           [4] is " *" or empty (don't care). */
3143                 int gamenum = atoi(star_match[0]);
3144                 char *whitename, *blackname, *why, *endtoken;
3145                 ChessMove endtype = (ChessMove) 0;
3146
3147                 if (tkind == 0) {
3148                   whitename = star_match[1];
3149                   blackname = star_match[2];
3150                   why = star_match[3];
3151                   endtoken = star_match[4];
3152                 } else {
3153                   whitename = star_match[1];
3154                   blackname = star_match[3];
3155                   why = star_match[5];
3156                   endtoken = star_match[6];
3157                 }
3158
3159                 /* Game start messages */
3160                 if (strncmp(why, "Creating ", 9) == 0 ||
3161                     strncmp(why, "Continuing ", 11) == 0) {
3162                     gs_gamenum = gamenum;
3163                     strcpy(gs_kind, strchr(why, ' ') + 1);
3164                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3165 #if ZIPPY
3166                     if (appData.zippyPlay) {
3167                         ZippyGameStart(whitename, blackname);
3168                     }
3169 #endif /*ZIPPY*/
3170                     continue;
3171                 }
3172
3173                 /* Game end messages */
3174                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3175                     ics_gamenum != gamenum) {
3176                     continue;
3177                 }
3178                 while (endtoken[0] == ' ') endtoken++;
3179                 switch (endtoken[0]) {
3180                   case '*':
3181                   default:
3182                     endtype = GameUnfinished;
3183                     break;
3184                   case '0':
3185                     endtype = BlackWins;
3186                     break;
3187                   case '1':
3188                     if (endtoken[1] == '/')
3189                       endtype = GameIsDrawn;
3190                     else
3191                       endtype = WhiteWins;
3192                     break;
3193                 }
3194                 GameEnds(endtype, why, GE_ICS);
3195 #if ZIPPY
3196                 if (appData.zippyPlay && first.initDone) {
3197                     ZippyGameEnd(endtype, why);
3198                     if (first.pr == NULL) {
3199                       /* Start the next process early so that we'll
3200                          be ready for the next challenge */
3201                       StartChessProgram(&first);
3202                     }
3203                     /* Send "new" early, in case this command takes
3204                        a long time to finish, so that we'll be ready
3205                        for the next challenge. */
3206                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3207                     Reset(TRUE, TRUE);
3208                 }
3209 #endif /*ZIPPY*/
3210                 continue;
3211             }
3212
3213             if (looking_at(buf, &i, "Removing game * from observation") ||
3214                 looking_at(buf, &i, "no longer observing game *") ||
3215                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3216                 if (gameMode == IcsObserving &&
3217                     atoi(star_match[0]) == ics_gamenum)
3218                   {
3219                       /* icsEngineAnalyze */
3220                       if (appData.icsEngineAnalyze) {
3221                             ExitAnalyzeMode();
3222                             ModeHighlight();
3223                       }
3224                       StopClocks();
3225                       gameMode = IcsIdle;
3226                       ics_gamenum = -1;
3227                       ics_user_moved = FALSE;
3228                   }
3229                 continue;
3230             }
3231
3232             if (looking_at(buf, &i, "no longer examining game *")) {
3233                 if (gameMode == IcsExamining &&
3234                     atoi(star_match[0]) == ics_gamenum)
3235                   {
3236                       gameMode = IcsIdle;
3237                       ics_gamenum = -1;
3238                       ics_user_moved = FALSE;
3239                   }
3240                 continue;
3241             }
3242
3243             /* Advance leftover_start past any newlines we find,
3244                so only partial lines can get reparsed */
3245             if (looking_at(buf, &i, "\n")) {
3246                 prevColor = curColor;
3247                 if (curColor != ColorNormal) {
3248                     if (oldi > next_out) {
3249                         SendToPlayer(&buf[next_out], oldi - next_out);
3250                         next_out = oldi;
3251                     }
3252                     Colorize(ColorNormal, FALSE);
3253                     curColor = ColorNormal;
3254                 }
3255                 if (started == STARTED_BOARD) {
3256                     started = STARTED_NONE;
3257                     parse[parse_pos] = NULLCHAR;
3258                     ParseBoard12(parse);
3259                     ics_user_moved = 0;
3260
3261                     /* Send premove here */
3262                     if (appData.premove) {
3263                       char str[MSG_SIZ];
3264                       if (currentMove == 0 &&
3265                           gameMode == IcsPlayingWhite &&
3266                           appData.premoveWhite) {
3267                         sprintf(str, "%s\n", appData.premoveWhiteText);
3268                         if (appData.debugMode)
3269                           fprintf(debugFP, "Sending premove:\n");
3270                         SendToICS(str);
3271                       } else if (currentMove == 1 &&
3272                                  gameMode == IcsPlayingBlack &&
3273                                  appData.premoveBlack) {
3274                         sprintf(str, "%s\n", appData.premoveBlackText);
3275                         if (appData.debugMode)
3276                           fprintf(debugFP, "Sending premove:\n");
3277                         SendToICS(str);
3278                       } else if (gotPremove) {
3279                         gotPremove = 0;
3280                         ClearPremoveHighlights();
3281                         if (appData.debugMode)
3282                           fprintf(debugFP, "Sending premove:\n");
3283                           UserMoveEvent(premoveFromX, premoveFromY, 
3284                                         premoveToX, premoveToY, 
3285                                         premovePromoChar);
3286                       }
3287                     }
3288
3289                     /* Usually suppress following prompt */
3290                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3291                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3292                         if (looking_at(buf, &i, "*% ")) {
3293                             savingComment = FALSE;
3294                             suppressKibitz = 0;
3295                         }
3296                     }
3297                     next_out = i;
3298                 } else if (started == STARTED_HOLDINGS) {
3299                     int gamenum;
3300                     char new_piece[MSG_SIZ];
3301                     started = STARTED_NONE;
3302                     parse[parse_pos] = NULLCHAR;
3303                     if (appData.debugMode)
3304                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3305                                                         parse, currentMove);
3306                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3307                         gamenum == ics_gamenum) {
3308                         if (gameInfo.variant == VariantNormal) {
3309                           /* [HGM] We seem to switch variant during a game!
3310                            * Presumably no holdings were displayed, so we have
3311                            * to move the position two files to the right to
3312                            * create room for them!
3313                            */
3314                           VariantClass newVariant;
3315                           switch(gameInfo.boardWidth) { // base guess on board width
3316                                 case 9:  newVariant = VariantShogi; break;
3317                                 case 10: newVariant = VariantGreat; break;
3318                                 default: newVariant = VariantCrazyhouse; break;
3319                           }
3320                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3321                           /* Get a move list just to see the header, which
3322                              will tell us whether this is really bug or zh */
3323                           if (ics_getting_history == H_FALSE) {
3324                             ics_getting_history = H_REQUESTED;
3325                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3326                             SendToICS(str);
3327                           }
3328                         }
3329                         new_piece[0] = NULLCHAR;
3330                         sscanf(parse, "game %d white [%s black [%s <- %s",
3331                                &gamenum, white_holding, black_holding,
3332                                new_piece);
3333                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3334                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3335                         /* [HGM] copy holdings to board holdings area */
3336                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3337                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3338                         boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3339 #if ZIPPY
3340                         if (appData.zippyPlay && first.initDone) {
3341                             ZippyHoldings(white_holding, black_holding,
3342                                           new_piece);
3343                         }
3344 #endif /*ZIPPY*/
3345                         if (tinyLayout || smallLayout) {
3346                             char wh[16], bh[16];
3347                             PackHolding(wh, white_holding);
3348                             PackHolding(bh, black_holding);
3349                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3350                                     gameInfo.white, gameInfo.black);
3351                         } else {
3352                             sprintf(str, "%s [%s] vs. %s [%s]",
3353                                     gameInfo.white, white_holding,
3354                                     gameInfo.black, black_holding);
3355                         }
3356
3357                         DrawPosition(FALSE, boards[currentMove]);
3358                         DisplayTitle(str);
3359                     }
3360                     /* Suppress following prompt */
3361                     if (looking_at(buf, &i, "*% ")) {
3362                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3363                         savingComment = FALSE;
3364                         suppressKibitz = 0;
3365                     }
3366                     next_out = i;
3367                 }
3368                 continue;
3369             }
3370
3371             i++;                /* skip unparsed character and loop back */
3372         }
3373         
3374         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3375 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3376 //          SendToPlayer(&buf[next_out], i - next_out);
3377             started != STARTED_HOLDINGS && leftover_start > next_out) {
3378             SendToPlayer(&buf[next_out], leftover_start - next_out);
3379             next_out = i;
3380         }
3381         
3382         leftover_len = buf_len - leftover_start;
3383         /* if buffer ends with something we couldn't parse,
3384            reparse it after appending the next read */
3385         
3386     } else if (count == 0) {
3387         RemoveInputSource(isr);
3388         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3389     } else {
3390         DisplayFatalError(_("Error reading from ICS"), error, 1);
3391     }
3392 }
3393
3394
3395 /* Board style 12 looks like this:
3396    
3397    <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
3398    
3399  * The "<12> " is stripped before it gets to this routine.  The two
3400  * trailing 0's (flip state and clock ticking) are later addition, and
3401  * some chess servers may not have them, or may have only the first.
3402  * Additional trailing fields may be added in the future.  
3403  */
3404
3405 #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"
3406
3407 #define RELATION_OBSERVING_PLAYED    0
3408 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3409 #define RELATION_PLAYING_MYMOVE      1
3410 #define RELATION_PLAYING_NOTMYMOVE  -1
3411 #define RELATION_EXAMINING           2
3412 #define RELATION_ISOLATED_BOARD     -3
3413 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3414
3415 void
3416 ParseBoard12(string)
3417      char *string;
3418
3419     GameMode newGameMode;
3420     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3421     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3422     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3423     char to_play, board_chars[200];
3424     char move_str[500], str[500], elapsed_time[500];
3425     char black[32], white[32];
3426     Board board;
3427     int prevMove = currentMove;
3428     int ticking = 2;
3429     ChessMove moveType;
3430     int fromX, fromY, toX, toY;
3431     char promoChar;
3432     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3433     char *bookHit = NULL; // [HGM] book
3434     Boolean weird = FALSE, reqFlag = FALSE;
3435
3436     fromX = fromY = toX = toY = -1;
3437     
3438     newGame = FALSE;
3439
3440     if (appData.debugMode)
3441       fprintf(debugFP, _("Parsing board: %s\n"), string);
3442
3443     move_str[0] = NULLCHAR;
3444     elapsed_time[0] = NULLCHAR;
3445     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3446         int  i = 0, j;
3447         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3448             if(string[i] == ' ') { ranks++; files = 0; }
3449             else files++;
3450             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3451             i++;
3452         }
3453         for(j = 0; j <i; j++) board_chars[j] = string[j];
3454         board_chars[i] = '\0';
3455         string += i + 1;
3456     }
3457     n = sscanf(string, PATTERN, &to_play, &double_push,
3458                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3459                &gamenum, white, black, &relation, &basetime, &increment,
3460                &white_stren, &black_stren, &white_time, &black_time,
3461                &moveNum, str, elapsed_time, move_str, &ics_flip,
3462                &ticking);
3463
3464     if (n < 21) {
3465         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3466         DisplayError(str, 0);
3467         return;
3468     }
3469
3470     /* Convert the move number to internal form */
3471     moveNum = (moveNum - 1) * 2;
3472     if (to_play == 'B') moveNum++;
3473     if (moveNum >= MAX_MOVES) {
3474       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3475                         0, 1);
3476       return;
3477     }
3478     
3479     switch (relation) {
3480       case RELATION_OBSERVING_PLAYED:
3481       case RELATION_OBSERVING_STATIC:
3482         if (gamenum == -1) {
3483             /* Old ICC buglet */
3484             relation = RELATION_OBSERVING_STATIC;
3485         }
3486         newGameMode = IcsObserving;
3487         break;
3488       case RELATION_PLAYING_MYMOVE:
3489       case RELATION_PLAYING_NOTMYMOVE:
3490         newGameMode =
3491           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3492             IcsPlayingWhite : IcsPlayingBlack;
3493         break;
3494       case RELATION_EXAMINING:
3495         newGameMode = IcsExamining;
3496         break;
3497       case RELATION_ISOLATED_BOARD:
3498       default:
3499         /* Just display this board.  If user was doing something else,
3500            we will forget about it until the next board comes. */ 
3501         newGameMode = IcsIdle;
3502         break;
3503       case RELATION_STARTING_POSITION:
3504         newGameMode = gameMode;
3505         break;
3506     }
3507     
3508     /* Modify behavior for initial board display on move listing
3509        of wild games.
3510        */
3511     switch (ics_getting_history) {
3512       case H_FALSE:
3513       case H_REQUESTED:
3514         break;
3515       case H_GOT_REQ_HEADER:
3516       case H_GOT_UNREQ_HEADER:
3517         /* This is the initial position of the current game */
3518         gamenum = ics_gamenum;
3519         moveNum = 0;            /* old ICS bug workaround */
3520         if (to_play == 'B') {
3521           startedFromSetupPosition = TRUE;
3522           blackPlaysFirst = TRUE;
3523           moveNum = 1;
3524           if (forwardMostMove == 0) forwardMostMove = 1;
3525           if (backwardMostMove == 0) backwardMostMove = 1;
3526           if (currentMove == 0) currentMove = 1;
3527         }
3528         newGameMode = gameMode;
3529         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3530         break;
3531       case H_GOT_UNWANTED_HEADER:
3532         /* This is an initial board that we don't want */
3533         return;
3534       case H_GETTING_MOVES:
3535         /* Should not happen */
3536         DisplayError(_("Error gathering move list: extra board"), 0);
3537         ics_getting_history = H_FALSE;
3538         return;
3539     }
3540
3541    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3542                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3543      /* [HGM] We seem to have switched variant unexpectedly
3544       * Try to guess new variant from board size
3545       */
3546           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3547           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3548           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3549           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3550           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3551           if(!weird) newVariant = VariantNormal;
3552           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3553           /* Get a move list just to see the header, which
3554              will tell us whether this is really bug or zh */
3555           if (ics_getting_history == H_FALSE) {
3556             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3557             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3558             SendToICS(str);
3559           }
3560     }
3561     
3562     /* Take action if this is the first board of a new game, or of a
3563        different game than is currently being displayed.  */
3564     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3565         relation == RELATION_ISOLATED_BOARD) {
3566         
3567         /* Forget the old game and get the history (if any) of the new one */
3568         if (gameMode != BeginningOfGame) {
3569           Reset(TRUE, TRUE);
3570         }
3571         newGame = TRUE;
3572         if (appData.autoRaiseBoard) BoardToTop();
3573         prevMove = -3;
3574         if (gamenum == -1) {
3575             newGameMode = IcsIdle;
3576         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3577                    appData.getMoveList && !reqFlag) {
3578             /* Need to get game history */
3579             ics_getting_history = H_REQUESTED;
3580             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3581             SendToICS(str);
3582         }
3583         
3584         /* Initially flip the board to have black on the bottom if playing
3585            black or if the ICS flip flag is set, but let the user change
3586            it with the Flip View button. */
3587         flipView = appData.autoFlipView ? 
3588           (newGameMode == IcsPlayingBlack) || ics_flip :
3589           appData.flipView;
3590         
3591         /* Done with values from previous mode; copy in new ones */
3592         gameMode = newGameMode;
3593         ModeHighlight();
3594         ics_gamenum = gamenum;
3595         if (gamenum == gs_gamenum) {
3596             int klen = strlen(gs_kind);
3597             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3598             sprintf(str, "ICS %s", gs_kind);
3599             gameInfo.event = StrSave(str);
3600         } else {
3601             gameInfo.event = StrSave("ICS game");
3602         }
3603         gameInfo.site = StrSave(appData.icsHost);
3604         gameInfo.date = PGNDate();
3605         gameInfo.round = StrSave("-");
3606         gameInfo.white = StrSave(white);
3607         gameInfo.black = StrSave(black);
3608         timeControl = basetime * 60 * 1000;
3609         timeControl_2 = 0;
3610         timeIncrement = increment * 1000;
3611         movesPerSession = 0;
3612         gameInfo.timeControl = TimeControlTagValue();
3613         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3614   if (appData.debugMode) {
3615     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3616     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3617     setbuf(debugFP, NULL);
3618   }
3619
3620         gameInfo.outOfBook = NULL;
3621         
3622         /* Do we have the ratings? */
3623         if (strcmp(player1Name, white) == 0 &&
3624             strcmp(player2Name, black) == 0) {
3625             if (appData.debugMode)
3626               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3627                       player1Rating, player2Rating);
3628             gameInfo.whiteRating = player1Rating;
3629             gameInfo.blackRating = player2Rating;
3630         } else if (strcmp(player2Name, white) == 0 &&
3631                    strcmp(player1Name, black) == 0) {
3632             if (appData.debugMode)
3633               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3634                       player2Rating, player1Rating);
3635             gameInfo.whiteRating = player2Rating;
3636             gameInfo.blackRating = player1Rating;
3637         }
3638         player1Name[0] = player2Name[0] = NULLCHAR;
3639
3640         /* Silence shouts if requested */
3641         if (appData.quietPlay &&
3642             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3643             SendToICS(ics_prefix);
3644             SendToICS("set shout 0\n");
3645         }
3646     }
3647     
3648     /* Deal with midgame name changes */
3649     if (!newGame) {
3650         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3651             if (gameInfo.white) free(gameInfo.white);
3652             gameInfo.white = StrSave(white);
3653         }
3654         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3655             if (gameInfo.black) free(gameInfo.black);
3656             gameInfo.black = StrSave(black);
3657         }
3658     }
3659     
3660     /* Throw away game result if anything actually changes in examine mode */
3661     if (gameMode == IcsExamining && !newGame) {
3662         gameInfo.result = GameUnfinished;
3663         if (gameInfo.resultDetails != NULL) {
3664             free(gameInfo.resultDetails);
3665             gameInfo.resultDetails = NULL;
3666         }
3667     }
3668     
3669     /* In pausing && IcsExamining mode, we ignore boards coming
3670        in if they are in a different variation than we are. */
3671     if (pauseExamInvalid) return;
3672     if (pausing && gameMode == IcsExamining) {
3673         if (moveNum <= pauseExamForwardMostMove) {
3674             pauseExamInvalid = TRUE;
3675             forwardMostMove = pauseExamForwardMostMove;
3676             return;
3677         }
3678     }
3679     
3680   if (appData.debugMode) {
3681     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3682   }
3683     /* Parse the board */
3684     for (k = 0; k < ranks; k++) {
3685       for (j = 0; j < files; j++)
3686         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3687       if(gameInfo.holdingsWidth > 1) {
3688            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3689            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3690       }
3691     }
3692     CopyBoard(boards[moveNum], board);
3693     boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3694     if (moveNum == 0) {
3695         startedFromSetupPosition =
3696           !CompareBoards(board, initialPosition);
3697         if(startedFromSetupPosition)
3698             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3699     }
3700
3701     /* [HGM] Set castling rights. Take the outermost Rooks,
3702        to make it also work for FRC opening positions. Note that board12
3703        is really defective for later FRC positions, as it has no way to
3704        indicate which Rook can castle if they are on the same side of King.
3705        For the initial position we grant rights to the outermost Rooks,
3706        and remember thos rights, and we then copy them on positions
3707        later in an FRC game. This means WB might not recognize castlings with
3708        Rooks that have moved back to their original position as illegal,
3709        but in ICS mode that is not its job anyway.
3710     */
3711     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3712     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3713
3714         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3715             if(board[0][i] == WhiteRook) j = i;
3716         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3717         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3718             if(board[0][i] == WhiteRook) j = i;
3719         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3720         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3721             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3722         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3723         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3724             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3725         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3726
3727         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3728         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3729             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3730         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3731             if(board[BOARD_HEIGHT-1][k] == bKing)
3732                 initialRights[5] = castlingRights[moveNum][5] = k;
3733         if(gameInfo.variant == VariantTwoKings) {
3734             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3735             if(board[0][4] == wKing) initialRights[2] = castlingRights[moveNum][2] = 4;
3736             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = castlingRights[moveNum][5] = 4;
3737         }
3738     } else { int r;
3739         r = castlingRights[moveNum][0] = initialRights[0];
3740         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3741         r = castlingRights[moveNum][1] = initialRights[1];
3742         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3743         r = castlingRights[moveNum][3] = initialRights[3];
3744         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3745         r = castlingRights[moveNum][4] = initialRights[4];
3746         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3747         /* wildcastle kludge: always assume King has rights */
3748         r = castlingRights[moveNum][2] = initialRights[2];
3749         r = castlingRights[moveNum][5] = initialRights[5];
3750     }
3751     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3752     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3753
3754     
3755     if (ics_getting_history == H_GOT_REQ_HEADER ||
3756         ics_getting_history == H_GOT_UNREQ_HEADER) {
3757         /* This was an initial position from a move list, not
3758            the current position */
3759         return;
3760     }
3761     
3762     /* Update currentMove and known move number limits */
3763     newMove = newGame || moveNum > forwardMostMove;
3764
3765     if (newGame) {
3766         forwardMostMove = backwardMostMove = currentMove = moveNum;
3767         if (gameMode == IcsExamining && moveNum == 0) {
3768           /* Workaround for ICS limitation: we are not told the wild
3769              type when starting to examine a game.  But if we ask for
3770              the move list, the move list header will tell us */
3771             ics_getting_history = H_REQUESTED;
3772             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3773             SendToICS(str);
3774         }
3775     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3776                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3777 #if ZIPPY
3778         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3779         /* [HGM] applied this also to an engine that is silently watching        */
3780         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3781             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3782             gameInfo.variant == currentlyInitializedVariant) {
3783           takeback = forwardMostMove - moveNum;
3784           for (i = 0; i < takeback; i++) {
3785             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3786             SendToProgram("undo\n", &first);
3787           }
3788         }
3789 #endif
3790
3791         forwardMostMove = moveNum;
3792         if (!pausing || currentMove > forwardMostMove)
3793           currentMove = forwardMostMove;
3794     } else {
3795         /* New part of history that is not contiguous with old part */ 
3796         if (pausing && gameMode == IcsExamining) {
3797             pauseExamInvalid = TRUE;
3798             forwardMostMove = pauseExamForwardMostMove;
3799             return;
3800         }
3801         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3802 #if ZIPPY
3803             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3804                 // [HGM] when we will receive the move list we now request, it will be
3805                 // fed to the engine from the first move on. So if the engine is not
3806                 // in the initial position now, bring it there.
3807                 InitChessProgram(&first, 0);
3808             }
3809 #endif
3810             ics_getting_history = H_REQUESTED;
3811             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3812             SendToICS(str);
3813         }
3814         forwardMostMove = backwardMostMove = currentMove = moveNum;
3815     }
3816     
3817     /* Update the clocks */
3818     if (strchr(elapsed_time, '.')) {
3819       /* Time is in ms */
3820       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3821       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3822     } else {
3823       /* Time is in seconds */
3824       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3825       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3826     }
3827       
3828
3829 #if ZIPPY
3830     if (appData.zippyPlay && newGame &&
3831         gameMode != IcsObserving && gameMode != IcsIdle &&
3832         gameMode != IcsExamining)
3833       ZippyFirstBoard(moveNum, basetime, increment);
3834 #endif
3835     
3836     /* Put the move on the move list, first converting
3837        to canonical algebraic form. */
3838     if (moveNum > 0) {
3839   if (appData.debugMode) {
3840     if (appData.debugMode) { int f = forwardMostMove;
3841         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3842                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3843     }
3844     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3845     fprintf(debugFP, "moveNum = %d\n", moveNum);
3846     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3847     setbuf(debugFP, NULL);
3848   }
3849         if (moveNum <= backwardMostMove) {
3850             /* We don't know what the board looked like before
3851                this move.  Punt. */
3852             strcpy(parseList[moveNum - 1], move_str);
3853             strcat(parseList[moveNum - 1], " ");
3854             strcat(parseList[moveNum - 1], elapsed_time);
3855             moveList[moveNum - 1][0] = NULLCHAR;
3856         } else if (strcmp(move_str, "none") == 0) {
3857             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3858             /* Again, we don't know what the board looked like;
3859                this is really the start of the game. */
3860             parseList[moveNum - 1][0] = NULLCHAR;
3861             moveList[moveNum - 1][0] = NULLCHAR;
3862             backwardMostMove = moveNum;
3863             startedFromSetupPosition = TRUE;
3864             fromX = fromY = toX = toY = -1;
3865         } else {
3866           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3867           //                 So we parse the long-algebraic move string in stead of the SAN move
3868           int valid; char buf[MSG_SIZ], *prom;
3869
3870           // str looks something like "Q/a1-a2"; kill the slash
3871           if(str[1] == '/') 
3872                 sprintf(buf, "%c%s", str[0], str+2);
3873           else  strcpy(buf, str); // might be castling
3874           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3875                 strcat(buf, prom); // long move lacks promo specification!
3876           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3877                 if(appData.debugMode) 
3878                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3879                 strcpy(move_str, buf);
3880           }
3881           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3882                                 &fromX, &fromY, &toX, &toY, &promoChar)
3883                || ParseOneMove(buf, moveNum - 1, &moveType,
3884                                 &fromX, &fromY, &toX, &toY, &promoChar);
3885           // end of long SAN patch
3886           if (valid) {
3887             (void) CoordsToAlgebraic(boards[moveNum - 1],
3888                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3889                                      fromY, fromX, toY, toX, promoChar,
3890                                      parseList[moveNum-1]);
3891             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3892                              castlingRights[moveNum]) ) {
3893               case MT_NONE:
3894               case MT_STALEMATE:
3895               default:
3896                 break;
3897               case MT_CHECK:
3898                 if(gameInfo.variant != VariantShogi)
3899                     strcat(parseList[moveNum - 1], "+");
3900                 break;
3901               case MT_CHECKMATE:
3902               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3903                 strcat(parseList[moveNum - 1], "#");
3904                 break;
3905             }
3906             strcat(parseList[moveNum - 1], " ");
3907             strcat(parseList[moveNum - 1], elapsed_time);
3908             /* currentMoveString is set as a side-effect of ParseOneMove */
3909             strcpy(moveList[moveNum - 1], currentMoveString);
3910             strcat(moveList[moveNum - 1], "\n");
3911           } else {
3912             /* Move from ICS was illegal!?  Punt. */
3913   if (appData.debugMode) {
3914     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3915     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3916   }
3917             strcpy(parseList[moveNum - 1], move_str);
3918             strcat(parseList[moveNum - 1], " ");
3919             strcat(parseList[moveNum - 1], elapsed_time);
3920             moveList[moveNum - 1][0] = NULLCHAR;
3921             fromX = fromY = toX = toY = -1;
3922           }
3923         }
3924   if (appData.debugMode) {
3925     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3926     setbuf(debugFP, NULL);
3927   }
3928
3929 #if ZIPPY
3930         /* Send move to chess program (BEFORE animating it). */
3931         if (appData.zippyPlay && !newGame && newMove && 
3932            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3933
3934             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3935                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3936                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3937                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3938                             move_str);
3939                     DisplayError(str, 0);
3940                 } else {
3941                     if (first.sendTime) {
3942                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3943                     }
3944                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3945                     if (firstMove && !bookHit) {
3946                         firstMove = FALSE;
3947                         if (first.useColors) {
3948                           SendToProgram(gameMode == IcsPlayingWhite ?
3949                                         "white\ngo\n" :
3950                                         "black\ngo\n", &first);
3951                         } else {
3952                           SendToProgram("go\n", &first);
3953                         }
3954                         first.maybeThinking = TRUE;
3955                     }
3956                 }
3957             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3958               if (moveList[moveNum - 1][0] == NULLCHAR) {
3959                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3960                 DisplayError(str, 0);
3961               } else {
3962                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3963                 SendMoveToProgram(moveNum - 1, &first);
3964               }
3965             }
3966         }
3967 #endif
3968     }
3969
3970     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3971         /* If move comes from a remote source, animate it.  If it
3972            isn't remote, it will have already been animated. */
3973         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3974             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3975         }
3976         if (!pausing && appData.highlightLastMove) {
3977             SetHighlights(fromX, fromY, toX, toY);
3978         }
3979     }
3980     
3981     /* Start the clocks */
3982     whiteFlag = blackFlag = FALSE;
3983     appData.clockMode = !(basetime == 0 && increment == 0);
3984     if (ticking == 0) {
3985       ics_clock_paused = TRUE;
3986       StopClocks();
3987     } else if (ticking == 1) {
3988       ics_clock_paused = FALSE;
3989     }
3990     if (gameMode == IcsIdle ||
3991         relation == RELATION_OBSERVING_STATIC ||
3992         relation == RELATION_EXAMINING ||
3993         ics_clock_paused)
3994       DisplayBothClocks();
3995     else
3996       StartClocks();
3997     
3998     /* Display opponents and material strengths */
3999     if (gameInfo.variant != VariantBughouse &&
4000         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4001         if (tinyLayout || smallLayout) {
4002             if(gameInfo.variant == VariantNormal)
4003                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4004                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4005                     basetime, increment);
4006             else
4007                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4008                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4009                     basetime, increment, (int) gameInfo.variant);
4010         } else {
4011             if(gameInfo.variant == VariantNormal)
4012                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4013                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4014                     basetime, increment);
4015             else
4016                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4017                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4018                     basetime, increment, VariantName(gameInfo.variant));
4019         }
4020         DisplayTitle(str);
4021   if (appData.debugMode) {
4022     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4023   }
4024     }
4025
4026    
4027     /* Display the board */
4028     if (!pausing && !appData.noGUI) {
4029       
4030       if (appData.premove)
4031           if (!gotPremove || 
4032              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4033              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4034               ClearPremoveHighlights();
4035
4036       DrawPosition(FALSE, boards[currentMove]);
4037       DisplayMove(moveNum - 1);
4038       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4039             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4040               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4041         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4042       }
4043     }
4044
4045     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4046 #if ZIPPY
4047     if(bookHit) { // [HGM] book: simulate book reply
4048         static char bookMove[MSG_SIZ]; // a bit generous?
4049
4050         programStats.nodes = programStats.depth = programStats.time = 
4051         programStats.score = programStats.got_only_move = 0;
4052         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4053
4054         strcpy(bookMove, "move ");
4055         strcat(bookMove, bookHit);
4056         HandleMachineMove(bookMove, &first);
4057     }
4058 #endif
4059 }
4060
4061 void
4062 GetMoveListEvent()
4063 {
4064     char buf[MSG_SIZ];
4065     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4066         ics_getting_history = H_REQUESTED;
4067         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4068         SendToICS(buf);
4069     }
4070 }
4071
4072 void
4073 AnalysisPeriodicEvent(force)
4074      int force;
4075 {
4076     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4077          && !force) || !appData.periodicUpdates)
4078       return;
4079
4080     /* Send . command to Crafty to collect stats */
4081     SendToProgram(".\n", &first);
4082
4083     /* Don't send another until we get a response (this makes
4084        us stop sending to old Crafty's which don't understand
4085        the "." command (sending illegal cmds resets node count & time,
4086        which looks bad)) */
4087     programStats.ok_to_send = 0;
4088 }
4089
4090 void ics_update_width(new_width)
4091         int new_width;
4092 {
4093         ics_printf("set width %d\n", new_width);
4094 }
4095
4096 void
4097 SendMoveToProgram(moveNum, cps)
4098      int moveNum;
4099      ChessProgramState *cps;
4100 {
4101     char buf[MSG_SIZ];
4102
4103     if (cps->useUsermove) {
4104       SendToProgram("usermove ", cps);
4105     }
4106     if (cps->useSAN) {
4107       char *space;
4108       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4109         int len = space - parseList[moveNum];
4110         memcpy(buf, parseList[moveNum], len);
4111         buf[len++] = '\n';
4112         buf[len] = NULLCHAR;
4113       } else {
4114         sprintf(buf, "%s\n", parseList[moveNum]);
4115       }
4116       SendToProgram(buf, cps);
4117     } else {
4118       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4119         AlphaRank(moveList[moveNum], 4);
4120         SendToProgram(moveList[moveNum], cps);
4121         AlphaRank(moveList[moveNum], 4); // and back
4122       } else
4123       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4124        * the engine. It would be nice to have a better way to identify castle 
4125        * moves here. */
4126       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4127                                                                          && cps->useOOCastle) {
4128         int fromX = moveList[moveNum][0] - AAA; 
4129         int fromY = moveList[moveNum][1] - ONE;
4130         int toX = moveList[moveNum][2] - AAA; 
4131         int toY = moveList[moveNum][3] - ONE;
4132         if((boards[moveNum][fromY][fromX] == WhiteKing 
4133             && boards[moveNum][toY][toX] == WhiteRook)
4134            || (boards[moveNum][fromY][fromX] == BlackKing 
4135                && boards[moveNum][toY][toX] == BlackRook)) {
4136           if(toX > fromX) SendToProgram("O-O\n", cps);
4137           else SendToProgram("O-O-O\n", cps);
4138         }
4139         else SendToProgram(moveList[moveNum], cps);
4140       }
4141       else SendToProgram(moveList[moveNum], cps);
4142       /* End of additions by Tord */
4143     }
4144
4145     /* [HGM] setting up the opening has brought engine in force mode! */
4146     /*       Send 'go' if we are in a mode where machine should play. */
4147     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4148         (gameMode == TwoMachinesPlay   ||
4149 #ifdef ZIPPY
4150          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4151 #endif
4152          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4153         SendToProgram("go\n", cps);
4154   if (appData.debugMode) {
4155     fprintf(debugFP, "(extra)\n");
4156   }
4157     }
4158     setboardSpoiledMachineBlack = 0;
4159 }
4160
4161 void
4162 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4163      ChessMove moveType;
4164      int fromX, fromY, toX, toY;
4165 {
4166     char user_move[MSG_SIZ];
4167
4168     switch (moveType) {
4169       default:
4170         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4171                 (int)moveType, fromX, fromY, toX, toY);
4172         DisplayError(user_move + strlen("say "), 0);
4173         break;
4174       case WhiteKingSideCastle:
4175       case BlackKingSideCastle:
4176       case WhiteQueenSideCastleWild:
4177       case BlackQueenSideCastleWild:
4178       /* PUSH Fabien */
4179       case WhiteHSideCastleFR:
4180       case BlackHSideCastleFR:
4181       /* POP Fabien */
4182         sprintf(user_move, "o-o\n");
4183         break;
4184       case WhiteQueenSideCastle:
4185       case BlackQueenSideCastle:
4186       case WhiteKingSideCastleWild:
4187       case BlackKingSideCastleWild:
4188       /* PUSH Fabien */
4189       case WhiteASideCastleFR:
4190       case BlackASideCastleFR:
4191       /* POP Fabien */
4192         sprintf(user_move, "o-o-o\n");
4193         break;
4194       case WhitePromotionQueen:
4195       case BlackPromotionQueen:
4196       case WhitePromotionRook:
4197       case BlackPromotionRook:
4198       case WhitePromotionBishop:
4199       case BlackPromotionBishop:
4200       case WhitePromotionKnight:
4201       case BlackPromotionKnight:
4202       case WhitePromotionKing:
4203       case BlackPromotionKing:
4204       case WhitePromotionChancellor:
4205       case BlackPromotionChancellor:
4206       case WhitePromotionArchbishop:
4207       case BlackPromotionArchbishop:
4208         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4209             sprintf(user_move, "%c%c%c%c=%c\n",
4210                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4211                 PieceToChar(WhiteFerz));
4212         else if(gameInfo.variant == VariantGreat)
4213             sprintf(user_move, "%c%c%c%c=%c\n",
4214                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4215                 PieceToChar(WhiteMan));
4216         else
4217             sprintf(user_move, "%c%c%c%c=%c\n",
4218                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4219                 PieceToChar(PromoPiece(moveType)));
4220         break;
4221       case WhiteDrop:
4222       case BlackDrop:
4223         sprintf(user_move, "%c@%c%c\n",
4224                 ToUpper(PieceToChar((ChessSquare) fromX)),
4225                 AAA + toX, ONE + toY);
4226         break;
4227       case NormalMove:
4228       case WhiteCapturesEnPassant:
4229       case BlackCapturesEnPassant:
4230       case IllegalMove:  /* could be a variant we don't quite understand */
4231         sprintf(user_move, "%c%c%c%c\n",
4232                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4233         break;
4234     }
4235     SendToICS(user_move);
4236     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4237         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4238 }
4239
4240 void
4241 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4242      int rf, ff, rt, ft;
4243      char promoChar;
4244      char move[7];
4245 {
4246     if (rf == DROP_RANK) {
4247         sprintf(move, "%c@%c%c\n",
4248                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4249     } else {
4250         if (promoChar == 'x' || promoChar == NULLCHAR) {
4251             sprintf(move, "%c%c%c%c\n",
4252                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4253         } else {
4254             sprintf(move, "%c%c%c%c%c\n",
4255                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4256         }
4257     }
4258 }
4259
4260 void
4261 ProcessICSInitScript(f)
4262      FILE *f;
4263 {
4264     char buf[MSG_SIZ];
4265
4266     while (fgets(buf, MSG_SIZ, f)) {
4267         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4268     }
4269
4270     fclose(f);
4271 }
4272
4273
4274 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4275 void
4276 AlphaRank(char *move, int n)
4277 {
4278 //    char *p = move, c; int x, y;
4279
4280     if (appData.debugMode) {
4281         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4282     }
4283
4284     if(move[1]=='*' && 
4285        move[2]>='0' && move[2]<='9' &&
4286        move[3]>='a' && move[3]<='x'    ) {
4287         move[1] = '@';
4288         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4289         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4290     } else
4291     if(move[0]>='0' && move[0]<='9' &&
4292        move[1]>='a' && move[1]<='x' &&
4293        move[2]>='0' && move[2]<='9' &&
4294        move[3]>='a' && move[3]<='x'    ) {
4295         /* input move, Shogi -> normal */
4296         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4297         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4298         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4299         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4300     } else
4301     if(move[1]=='@' &&
4302        move[3]>='0' && move[3]<='9' &&
4303        move[2]>='a' && move[2]<='x'    ) {
4304         move[1] = '*';
4305         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4306         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4307     } else
4308     if(
4309        move[0]>='a' && move[0]<='x' &&
4310        move[3]>='0' && move[3]<='9' &&
4311        move[2]>='a' && move[2]<='x'    ) {
4312          /* output move, normal -> Shogi */
4313         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4314         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4315         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4316         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4317         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4318     }
4319     if (appData.debugMode) {
4320         fprintf(debugFP, "   out = '%s'\n", move);
4321     }
4322 }
4323
4324 /* Parser for moves from gnuchess, ICS, or user typein box */
4325 Boolean
4326 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4327      char *move;
4328      int moveNum;
4329      ChessMove *moveType;
4330      int *fromX, *fromY, *toX, *toY;
4331      char *promoChar;
4332 {       
4333     if (appData.debugMode) {
4334         fprintf(debugFP, "move to parse: %s\n", move);
4335     }
4336     *moveType = yylexstr(moveNum, move);
4337
4338     switch (*moveType) {
4339       case WhitePromotionChancellor:
4340       case BlackPromotionChancellor:
4341       case WhitePromotionArchbishop:
4342       case BlackPromotionArchbishop:
4343       case WhitePromotionQueen:
4344       case BlackPromotionQueen:
4345       case WhitePromotionRook:
4346       case BlackPromotionRook:
4347       case WhitePromotionBishop:
4348       case BlackPromotionBishop:
4349       case WhitePromotionKnight:
4350       case BlackPromotionKnight:
4351       case WhitePromotionKing:
4352       case BlackPromotionKing:
4353       case NormalMove:
4354       case WhiteCapturesEnPassant:
4355       case BlackCapturesEnPassant:
4356       case WhiteKingSideCastle:
4357       case WhiteQueenSideCastle:
4358       case BlackKingSideCastle:
4359       case BlackQueenSideCastle:
4360       case WhiteKingSideCastleWild:
4361       case WhiteQueenSideCastleWild:
4362       case BlackKingSideCastleWild:
4363       case BlackQueenSideCastleWild:
4364       /* Code added by Tord: */
4365       case WhiteHSideCastleFR:
4366       case WhiteASideCastleFR:
4367       case BlackHSideCastleFR:
4368       case BlackASideCastleFR:
4369       /* End of code added by Tord */
4370       case IllegalMove:         /* bug or odd chess variant */
4371         *fromX = currentMoveString[0] - AAA;
4372         *fromY = currentMoveString[1] - ONE;
4373         *toX = currentMoveString[2] - AAA;
4374         *toY = currentMoveString[3] - ONE;
4375         *promoChar = currentMoveString[4];
4376         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4377             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4378     if (appData.debugMode) {
4379         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4380     }
4381             *fromX = *fromY = *toX = *toY = 0;
4382             return FALSE;
4383         }
4384         if (appData.testLegality) {
4385           return (*moveType != IllegalMove);
4386         } else {
4387           return !(*fromX == *toX && *fromY == *toY);
4388         }
4389
4390       case WhiteDrop:
4391       case BlackDrop:
4392         *fromX = *moveType == WhiteDrop ?
4393           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4394           (int) CharToPiece(ToLower(currentMoveString[0]));
4395         *fromY = DROP_RANK;
4396         *toX = currentMoveString[2] - AAA;
4397         *toY = currentMoveString[3] - ONE;
4398         *promoChar = NULLCHAR;
4399         return TRUE;
4400
4401       case AmbiguousMove:
4402       case ImpossibleMove:
4403       case (ChessMove) 0:       /* end of file */
4404       case ElapsedTime:
4405       case Comment:
4406       case PGNTag:
4407       case NAG:
4408       case WhiteWins:
4409       case BlackWins:
4410       case GameIsDrawn:
4411       default:
4412     if (appData.debugMode) {
4413         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4414     }
4415         /* bug? */
4416         *fromX = *fromY = *toX = *toY = 0;
4417         *promoChar = NULLCHAR;
4418         return FALSE;
4419     }
4420 }
4421
4422 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4423 // All positions will have equal probability, but the current method will not provide a unique
4424 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4425 #define DARK 1
4426 #define LITE 2
4427 #define ANY 3
4428
4429 int squaresLeft[4];
4430 int piecesLeft[(int)BlackPawn];
4431 int seed, nrOfShuffles;
4432
4433 void GetPositionNumber()
4434 {       // sets global variable seed
4435         int i;
4436
4437         seed = appData.defaultFrcPosition;
4438         if(seed < 0) { // randomize based on time for negative FRC position numbers
4439                 for(i=0; i<50; i++) seed += random();
4440                 seed = random() ^ random() >> 8 ^ random() << 8;
4441                 if(seed<0) seed = -seed;
4442         }
4443 }
4444
4445 int put(Board board, int pieceType, int rank, int n, int shade)
4446 // put the piece on the (n-1)-th empty squares of the given shade
4447 {
4448         int i;
4449
4450         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4451                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4452                         board[rank][i] = (ChessSquare) pieceType;
4453                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4454                         squaresLeft[ANY]--;
4455                         piecesLeft[pieceType]--; 
4456                         return i;
4457                 }
4458         }
4459         return -1;
4460 }
4461
4462
4463 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4464 // calculate where the next piece goes, (any empty square), and put it there
4465 {
4466         int i;
4467
4468         i = seed % squaresLeft[shade];
4469         nrOfShuffles *= squaresLeft[shade];
4470         seed /= squaresLeft[shade];
4471         put(board, pieceType, rank, i, shade);
4472 }
4473
4474 void AddTwoPieces(Board board, int pieceType, int rank)
4475 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4476 {
4477         int i, n=squaresLeft[ANY], j=n-1, k;
4478
4479         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4480         i = seed % k;  // pick one
4481         nrOfShuffles *= k;
4482         seed /= k;
4483         while(i >= j) i -= j--;
4484         j = n - 1 - j; i += j;
4485         put(board, pieceType, rank, j, ANY);
4486         put(board, pieceType, rank, i, ANY);
4487 }
4488
4489 void SetUpShuffle(Board board, int number)
4490 {
4491         int i, p, first=1;
4492
4493         GetPositionNumber(); nrOfShuffles = 1;
4494
4495         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4496         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4497         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4498
4499         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4500
4501         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4502             p = (int) board[0][i];
4503             if(p < (int) BlackPawn) piecesLeft[p] ++;
4504             board[0][i] = EmptySquare;
4505         }
4506
4507         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4508             // shuffles restricted to allow normal castling put KRR first
4509             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4510                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4511             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4512                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4513             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4514                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4515             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4516                 put(board, WhiteRook, 0, 0, ANY);
4517             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4518         }
4519
4520         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4521             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4522             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4523                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4524                 while(piecesLeft[p] >= 2) {
4525                     AddOnePiece(board, p, 0, LITE);
4526                     AddOnePiece(board, p, 0, DARK);
4527                 }
4528                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4529             }
4530
4531         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4532             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4533             // but we leave King and Rooks for last, to possibly obey FRC restriction
4534             if(p == (int)WhiteRook) continue;
4535             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4536             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4537         }
4538
4539         // now everything is placed, except perhaps King (Unicorn) and Rooks
4540
4541         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4542             // Last King gets castling rights
4543             while(piecesLeft[(int)WhiteUnicorn]) {
4544                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4545                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4546             }
4547
4548             while(piecesLeft[(int)WhiteKing]) {
4549                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4550                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4551             }
4552
4553
4554         } else {
4555             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4556             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4557         }
4558
4559         // Only Rooks can be left; simply place them all
4560         while(piecesLeft[(int)WhiteRook]) {
4561                 i = put(board, WhiteRook, 0, 0, ANY);
4562                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4563                         if(first) {
4564                                 first=0;
4565                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4566                         }
4567                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4568                 }
4569         }
4570         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4571             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4572         }
4573
4574         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4575 }
4576
4577 int SetCharTable( char *table, const char * map )
4578 /* [HGM] moved here from winboard.c because of its general usefulness */
4579 /*       Basically a safe strcpy that uses the last character as King */
4580 {
4581     int result = FALSE; int NrPieces;
4582
4583     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4584                     && NrPieces >= 12 && !(NrPieces&1)) {
4585         int i; /* [HGM] Accept even length from 12 to 34 */
4586
4587         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4588         for( i=0; i<NrPieces/2-1; i++ ) {
4589             table[i] = map[i];
4590             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4591         }
4592         table[(int) WhiteKing]  = map[NrPieces/2-1];
4593         table[(int) BlackKing]  = map[NrPieces-1];
4594
4595         result = TRUE;
4596     }
4597
4598     return result;
4599 }
4600
4601 void Prelude(Board board)
4602 {       // [HGM] superchess: random selection of exo-pieces
4603         int i, j, k; ChessSquare p; 
4604         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4605
4606         GetPositionNumber(); // use FRC position number
4607
4608         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4609             SetCharTable(pieceToChar, appData.pieceToCharTable);
4610             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4611                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4612         }
4613
4614         j = seed%4;                 seed /= 4; 
4615         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4616         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4617         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4618         j = seed%3 + (seed%3 >= j); seed /= 3; 
4619         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4620         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4621         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4622         j = seed%3;                 seed /= 3; 
4623         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4624         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4625         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4626         j = seed%2 + (seed%2 >= j); seed /= 2; 
4627         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4628         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4629         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4630         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4631         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4632         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4633         put(board, exoPieces[0],    0, 0, ANY);
4634         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4635 }
4636
4637 void
4638 InitPosition(redraw)
4639      int redraw;
4640 {
4641     ChessSquare (* pieces)[BOARD_SIZE];
4642     int i, j, pawnRow, overrule,
4643     oldx = gameInfo.boardWidth,
4644     oldy = gameInfo.boardHeight,
4645     oldh = gameInfo.holdingsWidth,
4646     oldv = gameInfo.variant;
4647
4648     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4649
4650     /* [AS] Initialize pv info list [HGM] and game status */
4651     {
4652         for( i=0; i<MAX_MOVES; i++ ) {
4653             pvInfoList[i].depth = 0;
4654             epStatus[i]=EP_NONE;
4655             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4656         }
4657
4658         initialRulePlies = 0; /* 50-move counter start */
4659
4660         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4661         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4662     }
4663
4664     
4665     /* [HGM] logic here is completely changed. In stead of full positions */
4666     /* the initialized data only consist of the two backranks. The switch */
4667     /* selects which one we will use, which is than copied to the Board   */
4668     /* initialPosition, which for the rest is initialized by Pawns and    */
4669     /* empty squares. This initial position is then copied to boards[0],  */
4670     /* possibly after shuffling, so that it remains available.            */
4671
4672     gameInfo.holdingsWidth = 0; /* default board sizes */
4673     gameInfo.boardWidth    = 8;
4674     gameInfo.boardHeight   = 8;
4675     gameInfo.holdingsSize  = 0;
4676     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4677     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4678     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4679
4680     switch (gameInfo.variant) {
4681     case VariantFischeRandom:
4682       shuffleOpenings = TRUE;
4683     default:
4684       pieces = FIDEArray;
4685       break;
4686     case VariantShatranj:
4687       pieces = ShatranjArray;
4688       nrCastlingRights = 0;
4689       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4690       break;
4691     case VariantMakruk:
4692       pieces = makrukArray;
4693       nrCastlingRights = 0;
4694       startedFromSetupPosition = TRUE;
4695       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
4696       break;
4697     case VariantTwoKings:
4698       pieces = twoKingsArray;
4699       break;
4700     case VariantCapaRandom:
4701       shuffleOpenings = TRUE;
4702     case VariantCapablanca:
4703       pieces = CapablancaArray;
4704       gameInfo.boardWidth = 10;
4705       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4706       break;
4707     case VariantGothic:
4708       pieces = GothicArray;
4709       gameInfo.boardWidth = 10;
4710       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4711       break;
4712     case VariantJanus:
4713       pieces = JanusArray;
4714       gameInfo.boardWidth = 10;
4715       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4716       nrCastlingRights = 6;
4717         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4718         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4719         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4720         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4721         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4722         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4723       break;
4724     case VariantFalcon:
4725       pieces = FalconArray;
4726       gameInfo.boardWidth = 10;
4727       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4728       break;
4729     case VariantXiangqi:
4730       pieces = XiangqiArray;
4731       gameInfo.boardWidth  = 9;
4732       gameInfo.boardHeight = 10;
4733       nrCastlingRights = 0;
4734       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4735       break;
4736     case VariantShogi:
4737       pieces = ShogiArray;
4738       gameInfo.boardWidth  = 9;
4739       gameInfo.boardHeight = 9;
4740       gameInfo.holdingsSize = 7;
4741       nrCastlingRights = 0;
4742       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4743       break;
4744     case VariantCourier:
4745       pieces = CourierArray;
4746       gameInfo.boardWidth  = 12;
4747       nrCastlingRights = 0;
4748       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4749       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4750       break;
4751     case VariantKnightmate:
4752       pieces = KnightmateArray;
4753       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4754       break;
4755     case VariantFairy:
4756       pieces = fairyArray;
4757       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4758       break;
4759     case VariantGreat:
4760       pieces = GreatArray;
4761       gameInfo.boardWidth = 10;
4762       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4763       gameInfo.holdingsSize = 8;
4764       break;
4765     case VariantSuper:
4766       pieces = FIDEArray;
4767       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4768       gameInfo.holdingsSize = 8;
4769       startedFromSetupPosition = TRUE;
4770       break;
4771     case VariantCrazyhouse:
4772     case VariantBughouse:
4773       pieces = FIDEArray;
4774       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4775       gameInfo.holdingsSize = 5;
4776       break;
4777     case VariantWildCastle:
4778       pieces = FIDEArray;
4779       /* !!?shuffle with kings guaranteed to be on d or e file */
4780       shuffleOpenings = 1;
4781       break;
4782     case VariantNoCastle:
4783       pieces = FIDEArray;
4784       nrCastlingRights = 0;
4785       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4786       /* !!?unconstrained back-rank shuffle */
4787       shuffleOpenings = 1;
4788       break;
4789     }
4790
4791     overrule = 0;
4792     if(appData.NrFiles >= 0) {
4793         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4794         gameInfo.boardWidth = appData.NrFiles;
4795     }
4796     if(appData.NrRanks >= 0) {
4797         gameInfo.boardHeight = appData.NrRanks;
4798     }
4799     if(appData.holdingsSize >= 0) {
4800         i = appData.holdingsSize;
4801         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4802         gameInfo.holdingsSize = i;
4803     }
4804     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4805     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4806         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4807
4808     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4809     if(pawnRow < 1) pawnRow = 1;
4810     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4811
4812     /* User pieceToChar list overrules defaults */
4813     if(appData.pieceToCharTable != NULL)
4814         SetCharTable(pieceToChar, appData.pieceToCharTable);
4815
4816     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4817
4818         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4819             s = (ChessSquare) 0; /* account holding counts in guard band */
4820         for( i=0; i<BOARD_HEIGHT; i++ )
4821             initialPosition[i][j] = s;
4822
4823         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4824         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4825         initialPosition[pawnRow][j] = WhitePawn;
4826         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4827         if(gameInfo.variant == VariantXiangqi) {
4828             if(j&1) {
4829                 initialPosition[pawnRow][j] = 
4830                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4831                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4832                    initialPosition[2][j] = WhiteCannon;
4833                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4834                 }
4835             }
4836         }
4837         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4838     }
4839     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4840
4841             j=BOARD_LEFT+1;
4842             initialPosition[1][j] = WhiteBishop;
4843             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4844             j=BOARD_RGHT-2;
4845             initialPosition[1][j] = WhiteRook;
4846             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4847     }
4848
4849     if( nrCastlingRights == -1) {
4850         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4851         /*       This sets default castling rights from none to normal corners   */
4852         /* Variants with other castling rights must set them themselves above    */
4853         nrCastlingRights = 6;
4854        
4855         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4856         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4857         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4858         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4859         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4860         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4861      }
4862
4863      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4864      if(gameInfo.variant == VariantGreat) { // promotion commoners
4865         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4866         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4867         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4868         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4869      }
4870   if (appData.debugMode) {
4871     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4872   }
4873     if(shuffleOpenings) {
4874         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4875         startedFromSetupPosition = TRUE;
4876     }
4877     if(startedFromPositionFile) {
4878       /* [HGM] loadPos: use PositionFile for every new game */
4879       CopyBoard(initialPosition, filePosition);
4880       for(i=0; i<nrCastlingRights; i++)
4881           castlingRights[0][i] = initialRights[i] = fileRights[i];
4882       startedFromSetupPosition = TRUE;
4883     }
4884
4885     CopyBoard(boards[0], initialPosition);
4886
4887     if(oldx != gameInfo.boardWidth ||
4888        oldy != gameInfo.boardHeight ||
4889        oldh != gameInfo.holdingsWidth
4890 #ifdef GOTHIC
4891        || oldv == VariantGothic ||        // For licensing popups
4892        gameInfo.variant == VariantGothic
4893 #endif
4894 #ifdef FALCON
4895        || oldv == VariantFalcon ||
4896        gameInfo.variant == VariantFalcon
4897 #endif
4898                                          )
4899             InitDrawingSizes(-2 ,0);
4900
4901     if (redraw)
4902       DrawPosition(TRUE, boards[currentMove]);
4903 }
4904
4905 void
4906 SendBoard(cps, moveNum)
4907      ChessProgramState *cps;
4908      int moveNum;
4909 {
4910     char message[MSG_SIZ];
4911     
4912     if (cps->useSetboard) {
4913       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4914       sprintf(message, "setboard %s\n", fen);
4915       SendToProgram(message, cps);
4916       free(fen);
4917
4918     } else {
4919       ChessSquare *bp;
4920       int i, j;
4921       /* Kludge to set black to move, avoiding the troublesome and now
4922        * deprecated "black" command.
4923        */
4924       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4925
4926       SendToProgram("edit\n", cps);
4927       SendToProgram("#\n", cps);
4928       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4929         bp = &boards[moveNum][i][BOARD_LEFT];
4930         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4931           if ((int) *bp < (int) BlackPawn) {
4932             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4933                     AAA + j, ONE + i);
4934             if(message[0] == '+' || message[0] == '~') {
4935                 sprintf(message, "%c%c%c+\n",
4936                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4937                         AAA + j, ONE + i);
4938             }
4939             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4940                 message[1] = BOARD_RGHT   - 1 - j + '1';
4941                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4942             }
4943             SendToProgram(message, cps);
4944           }
4945         }
4946       }
4947     
4948       SendToProgram("c\n", cps);
4949       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4950         bp = &boards[moveNum][i][BOARD_LEFT];
4951         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4952           if (((int) *bp != (int) EmptySquare)
4953               && ((int) *bp >= (int) BlackPawn)) {
4954             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4955                     AAA + j, ONE + i);
4956             if(message[0] == '+' || message[0] == '~') {
4957                 sprintf(message, "%c%c%c+\n",
4958                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4959                         AAA + j, ONE + i);
4960             }
4961             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4962                 message[1] = BOARD_RGHT   - 1 - j + '1';
4963                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4964             }
4965             SendToProgram(message, cps);
4966           }
4967         }
4968       }
4969     
4970       SendToProgram(".\n", cps);
4971     }
4972     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4973 }
4974
4975 int
4976 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4977 {
4978     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4979     /* [HGM] add Shogi promotions */
4980     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4981     ChessSquare piece;
4982     ChessMove moveType;
4983     Boolean premove;
4984
4985     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4986     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4987
4988     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4989       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4990         return FALSE;
4991
4992     piece = boards[currentMove][fromY][fromX];
4993     if(gameInfo.variant == VariantShogi) {
4994         promotionZoneSize = 3;
4995         highestPromotingPiece = (int)WhiteFerz;
4996     } else if(gameInfo.variant == VariantMakruk) {
4997         promotionZoneSize = 3;
4998     }
4999
5000     // next weed out all moves that do not touch the promotion zone at all
5001     if((int)piece >= BlackPawn) {
5002         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5003              return FALSE;
5004         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5005     } else {
5006         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5007            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5008     }
5009
5010     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5011
5012     // weed out mandatory Shogi promotions
5013     if(gameInfo.variant == VariantShogi) {
5014         if(piece >= BlackPawn) {
5015             if(toY == 0 && piece == BlackPawn ||
5016                toY == 0 && piece == BlackQueen ||
5017                toY <= 1 && piece == BlackKnight) {
5018                 *promoChoice = '+';
5019                 return FALSE;
5020             }
5021         } else {
5022             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5023                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5024                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5025                 *promoChoice = '+';
5026                 return FALSE;
5027             }
5028         }
5029     }
5030
5031     // weed out obviously illegal Pawn moves
5032     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5033         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5034         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5035         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5036         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5037         // note we are not allowed to test for valid (non-)capture, due to premove
5038     }
5039
5040     // we either have a choice what to promote to, or (in Shogi) whether to promote
5041     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5042         *promoChoice = PieceToChar(BlackFerz);  // no choice
5043         return FALSE;
5044     }
5045     if(appData.alwaysPromoteToQueen) { // predetermined
5046         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5047              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5048         else *promoChoice = PieceToChar(BlackQueen);
5049         return FALSE;
5050     }
5051
5052     // suppress promotion popup on illegal moves that are not premoves
5053     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5054               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5055     if(appData.testLegality && !premove) {
5056         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5057                         epStatus[currentMove], castlingRights[currentMove],
5058                         fromY, fromX, toY, toX, NULLCHAR);
5059         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5060            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5061             return FALSE;
5062     }
5063
5064     return TRUE;
5065 }
5066
5067 int
5068 InPalace(row, column)
5069      int row, column;
5070 {   /* [HGM] for Xiangqi */
5071     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5072          column < (BOARD_WIDTH + 4)/2 &&
5073          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5074     return FALSE;
5075 }
5076
5077 int
5078 PieceForSquare (x, y)
5079      int x;
5080      int y;
5081 {
5082   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5083      return -1;
5084   else
5085      return boards[currentMove][y][x];
5086 }
5087
5088 int
5089 OKToStartUserMove(x, y)
5090      int x, y;
5091 {
5092     ChessSquare from_piece;
5093     int white_piece;
5094
5095     if (matchMode) return FALSE;
5096     if (gameMode == EditPosition) return TRUE;
5097
5098     if (x >= 0 && y >= 0)
5099       from_piece = boards[currentMove][y][x];
5100     else
5101       from_piece = EmptySquare;
5102
5103     if (from_piece == EmptySquare) return FALSE;
5104
5105     white_piece = (int)from_piece >= (int)WhitePawn &&
5106       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5107
5108     switch (gameMode) {
5109       case PlayFromGameFile:
5110       case AnalyzeFile:
5111       case TwoMachinesPlay:
5112       case EndOfGame:
5113         return FALSE;
5114
5115       case IcsObserving:
5116       case IcsIdle:
5117         return FALSE;
5118
5119       case MachinePlaysWhite:
5120       case IcsPlayingBlack:
5121         if (appData.zippyPlay) return FALSE;
5122         if (white_piece) {
5123             DisplayMoveError(_("You are playing Black"));
5124             return FALSE;
5125         }
5126         break;
5127
5128       case MachinePlaysBlack:
5129       case IcsPlayingWhite:
5130         if (appData.zippyPlay) return FALSE;
5131         if (!white_piece) {
5132             DisplayMoveError(_("You are playing White"));
5133             return FALSE;
5134         }
5135         break;
5136
5137       case EditGame:
5138         if (!white_piece && WhiteOnMove(currentMove)) {
5139             DisplayMoveError(_("It is White's turn"));
5140             return FALSE;
5141         }           
5142         if (white_piece && !WhiteOnMove(currentMove)) {
5143             DisplayMoveError(_("It is Black's turn"));
5144             return FALSE;
5145         }           
5146         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5147             /* Editing correspondence game history */
5148             /* Could disallow this or prompt for confirmation */
5149             cmailOldMove = -1;
5150         }
5151         if (currentMove < forwardMostMove) {
5152             /* Discarding moves */
5153             /* Could prompt for confirmation here,
5154                but I don't think that's such a good idea */
5155             forwardMostMove = currentMove;
5156         }
5157         break;
5158
5159       case BeginningOfGame:
5160         if (appData.icsActive) return FALSE;
5161         if (!appData.noChessProgram) {
5162             if (!white_piece) {
5163                 DisplayMoveError(_("You are playing White"));
5164                 return FALSE;
5165             }
5166         }
5167         break;
5168         
5169       case Training:
5170         if (!white_piece && WhiteOnMove(currentMove)) {
5171             DisplayMoveError(_("It is White's turn"));
5172             return FALSE;
5173         }           
5174         if (white_piece && !WhiteOnMove(currentMove)) {
5175             DisplayMoveError(_("It is Black's turn"));
5176             return FALSE;
5177         }           
5178         break;
5179
5180       default:
5181       case IcsExamining:
5182         break;
5183     }
5184     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5185         && gameMode != AnalyzeFile && gameMode != Training) {
5186         DisplayMoveError(_("Displayed position is not current"));
5187         return FALSE;
5188     }
5189     return TRUE;
5190 }
5191
5192 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5193 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5194 int lastLoadGameUseList = FALSE;
5195 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5196 ChessMove lastLoadGameStart = (ChessMove) 0;
5197
5198 ChessMove
5199 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5200      int fromX, fromY, toX, toY;
5201      int promoChar;
5202      Boolean captureOwn;
5203 {
5204     ChessMove moveType;
5205     ChessSquare pdown, pup;
5206
5207     /* Check if the user is playing in turn.  This is complicated because we
5208        let the user "pick up" a piece before it is his turn.  So the piece he
5209        tried to pick up may have been captured by the time he puts it down!
5210        Therefore we use the color the user is supposed to be playing in this
5211        test, not the color of the piece that is currently on the starting
5212        square---except in EditGame mode, where the user is playing both
5213        sides; fortunately there the capture race can't happen.  (It can
5214        now happen in IcsExamining mode, but that's just too bad.  The user
5215        will get a somewhat confusing message in that case.)
5216        */
5217
5218     switch (gameMode) {
5219       case PlayFromGameFile:
5220       case AnalyzeFile:
5221       case TwoMachinesPlay:
5222       case EndOfGame:
5223       case IcsObserving:
5224       case IcsIdle:
5225         /* We switched into a game mode where moves are not accepted,
5226            perhaps while the mouse button was down. */
5227         return ImpossibleMove;
5228
5229       case MachinePlaysWhite:
5230         /* User is moving for Black */
5231         if (WhiteOnMove(currentMove)) {
5232             DisplayMoveError(_("It is White's turn"));
5233             return ImpossibleMove;
5234         }
5235         break;
5236
5237       case MachinePlaysBlack:
5238         /* User is moving for White */
5239         if (!WhiteOnMove(currentMove)) {
5240             DisplayMoveError(_("It is Black's turn"));
5241             return ImpossibleMove;
5242         }
5243         break;
5244
5245       case EditGame:
5246       case IcsExamining:
5247       case BeginningOfGame:
5248       case AnalyzeMode:
5249       case Training:
5250         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5251             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5252             /* User is moving for Black */
5253             if (WhiteOnMove(currentMove)) {
5254                 DisplayMoveError(_("It is White's turn"));
5255                 return ImpossibleMove;
5256             }
5257         } else {
5258             /* User is moving for White */
5259             if (!WhiteOnMove(currentMove)) {
5260                 DisplayMoveError(_("It is Black's turn"));
5261                 return ImpossibleMove;
5262             }
5263         }
5264         break;
5265
5266       case IcsPlayingBlack:
5267         /* User is moving for Black */
5268         if (WhiteOnMove(currentMove)) {
5269             if (!appData.premove) {
5270                 DisplayMoveError(_("It is White's turn"));
5271             } else if (toX >= 0 && toY >= 0) {
5272                 premoveToX = toX;
5273                 premoveToY = toY;
5274                 premoveFromX = fromX;
5275                 premoveFromY = fromY;
5276                 premovePromoChar = promoChar;
5277                 gotPremove = 1;
5278                 if (appData.debugMode) 
5279                     fprintf(debugFP, "Got premove: fromX %d,"
5280                             "fromY %d, toX %d, toY %d\n",
5281                             fromX, fromY, toX, toY);
5282             }
5283             return ImpossibleMove;
5284         }
5285         break;
5286
5287       case IcsPlayingWhite:
5288         /* User is moving for White */
5289         if (!WhiteOnMove(currentMove)) {
5290             if (!appData.premove) {
5291                 DisplayMoveError(_("It is Black's turn"));
5292             } else if (toX >= 0 && toY >= 0) {
5293                 premoveToX = toX;
5294                 premoveToY = toY;
5295                 premoveFromX = fromX;
5296                 premoveFromY = fromY;
5297                 premovePromoChar = promoChar;
5298                 gotPremove = 1;
5299                 if (appData.debugMode) 
5300                     fprintf(debugFP, "Got premove: fromX %d,"
5301                             "fromY %d, toX %d, toY %d\n",
5302                             fromX, fromY, toX, toY);
5303             }
5304             return ImpossibleMove;
5305         }
5306         break;
5307
5308       default:
5309         break;
5310
5311       case EditPosition:
5312         /* EditPosition, empty square, or different color piece;
5313            click-click move is possible */
5314         if (toX == -2 || toY == -2) {
5315             boards[0][fromY][fromX] = EmptySquare;
5316             return AmbiguousMove;
5317         } else if (toX >= 0 && toY >= 0) {
5318             boards[0][toY][toX] = boards[0][fromY][fromX];
5319             boards[0][fromY][fromX] = EmptySquare;
5320             return AmbiguousMove;
5321         }
5322         return ImpossibleMove;
5323     }
5324
5325     if(toX < 0 || toY < 0) return ImpossibleMove;
5326     pdown = boards[currentMove][fromY][fromX];
5327     pup = boards[currentMove][toY][toX];
5328
5329     /* [HGM] If move started in holdings, it means a drop */
5330     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5331          if( pup != EmptySquare ) return ImpossibleMove;
5332          if(appData.testLegality) {
5333              /* it would be more logical if LegalityTest() also figured out
5334               * which drops are legal. For now we forbid pawns on back rank.
5335               * Shogi is on its own here...
5336               */
5337              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5338                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5339                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5340          }
5341          return WhiteDrop; /* Not needed to specify white or black yet */
5342     }
5343
5344     userOfferedDraw = FALSE;
5345         
5346     /* [HGM] always test for legality, to get promotion info */
5347     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5348                           epStatus[currentMove], castlingRights[currentMove],
5349                                          fromY, fromX, toY, toX, promoChar);
5350     /* [HGM] but possibly ignore an IllegalMove result */
5351     if (appData.testLegality) {
5352         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5353             DisplayMoveError(_("Illegal move"));
5354             return ImpossibleMove;
5355         }
5356     }
5357
5358     return moveType;
5359     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5360        function is made into one that returns an OK move type if FinishMove
5361        should be called. This to give the calling driver routine the
5362        opportunity to finish the userMove input with a promotion popup,
5363        without bothering the user with this for invalid or illegal moves */
5364
5365 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5366 }
5367
5368 /* Common tail of UserMoveEvent and DropMenuEvent */
5369 int
5370 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5371      ChessMove moveType;
5372      int fromX, fromY, toX, toY;
5373      /*char*/int promoChar;
5374 {
5375     char *bookHit = 0;
5376
5377     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5378         // [HGM] superchess: suppress promotions to non-available piece
5379         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5380         if(WhiteOnMove(currentMove)) {
5381             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5382         } else {
5383             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5384         }
5385     }
5386
5387     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5388        move type in caller when we know the move is a legal promotion */
5389     if(moveType == NormalMove && promoChar)
5390         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5391
5392     /* [HGM] convert drag-and-drop piece drops to standard form */
5393     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5394          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5395            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5396                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5397            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5398            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5399            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5400            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5401          fromY = DROP_RANK;
5402     }
5403
5404     /* [HGM] <popupFix> The following if has been moved here from
5405        UserMoveEvent(). Because it seemed to belong here (why not allow
5406        piece drops in training games?), and because it can only be
5407        performed after it is known to what we promote. */
5408     if (gameMode == Training) {
5409       /* compare the move played on the board to the next move in the
5410        * game. If they match, display the move and the opponent's response. 
5411        * If they don't match, display an error message.
5412        */
5413       int saveAnimate;
5414       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5415       CopyBoard(testBoard, boards[currentMove]);
5416       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5417
5418       if (CompareBoards(testBoard, boards[currentMove+1])) {
5419         ForwardInner(currentMove+1);
5420
5421         /* Autoplay the opponent's response.
5422          * if appData.animate was TRUE when Training mode was entered,
5423          * the response will be animated.
5424          */
5425         saveAnimate = appData.animate;
5426         appData.animate = animateTraining;
5427         ForwardInner(currentMove+1);
5428         appData.animate = saveAnimate;
5429
5430         /* check for the end of the game */
5431         if (currentMove >= forwardMostMove) {
5432           gameMode = PlayFromGameFile;
5433           ModeHighlight();
5434           SetTrainingModeOff();
5435           DisplayInformation(_("End of game"));
5436         }
5437       } else {
5438         DisplayError(_("Incorrect move"), 0);
5439       }
5440       return 1;
5441     }
5442
5443   /* Ok, now we know that the move is good, so we can kill
5444      the previous line in Analysis Mode */
5445   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5446     forwardMostMove = currentMove;
5447   }
5448
5449   /* If we need the chess program but it's dead, restart it */
5450   ResurrectChessProgram();
5451
5452   /* A user move restarts a paused game*/
5453   if (pausing)
5454     PauseEvent();
5455
5456   thinkOutput[0] = NULLCHAR;
5457
5458   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5459
5460   if (gameMode == BeginningOfGame) {
5461     if (appData.noChessProgram) {
5462       gameMode = EditGame;
5463       SetGameInfo();
5464     } else {
5465       char buf[MSG_SIZ];
5466       gameMode = MachinePlaysBlack;
5467       StartClocks();
5468       SetGameInfo();
5469       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5470       DisplayTitle(buf);
5471       if (first.sendName) {
5472         sprintf(buf, "name %s\n", gameInfo.white);
5473         SendToProgram(buf, &first);
5474       }
5475       StartClocks();
5476     }
5477     ModeHighlight();
5478   }
5479
5480   /* Relay move to ICS or chess engine */
5481   if (appData.icsActive) {
5482     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5483         gameMode == IcsExamining) {
5484       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5485       ics_user_moved = 1;
5486     }
5487   } else {
5488     if (first.sendTime && (gameMode == BeginningOfGame ||
5489                            gameMode == MachinePlaysWhite ||
5490                            gameMode == MachinePlaysBlack)) {
5491       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5492     }
5493     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5494          // [HGM] book: if program might be playing, let it use book
5495         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5496         first.maybeThinking = TRUE;
5497     } else SendMoveToProgram(forwardMostMove-1, &first);
5498     if (currentMove == cmailOldMove + 1) {
5499       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5500     }
5501   }
5502
5503   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5504
5505   switch (gameMode) {
5506   case EditGame:
5507     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5508                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5509     case MT_NONE:
5510     case MT_CHECK:
5511       break;
5512     case MT_CHECKMATE:
5513     case MT_STAINMATE:
5514       if (WhiteOnMove(currentMove)) {
5515         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5516       } else {
5517         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5518       }
5519       break;
5520     case MT_STALEMATE:
5521       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5522       break;
5523     }
5524     break;
5525     
5526   case MachinePlaysBlack:
5527   case MachinePlaysWhite:
5528     /* disable certain menu options while machine is thinking */
5529     SetMachineThinkingEnables();
5530     break;
5531
5532   default:
5533     break;
5534   }
5535
5536   if(bookHit) { // [HGM] book: simulate book reply
5537         static char bookMove[MSG_SIZ]; // a bit generous?
5538
5539         programStats.nodes = programStats.depth = programStats.time = 
5540         programStats.score = programStats.got_only_move = 0;
5541         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5542
5543         strcpy(bookMove, "move ");
5544         strcat(bookMove, bookHit);
5545         HandleMachineMove(bookMove, &first);
5546   }
5547   return 1;
5548 }
5549
5550 void
5551 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5552      int fromX, fromY, toX, toY;
5553      int promoChar;
5554 {
5555     /* [HGM] This routine was added to allow calling of its two logical
5556        parts from other modules in the old way. Before, UserMoveEvent()
5557        automatically called FinishMove() if the move was OK, and returned
5558        otherwise. I separated the two, in order to make it possible to
5559        slip a promotion popup in between. But that it always needs two
5560        calls, to the first part, (now called UserMoveTest() ), and to
5561        FinishMove if the first part succeeded. Calls that do not need
5562        to do anything in between, can call this routine the old way. 
5563     */
5564     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5565 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5566     if(moveType == AmbiguousMove)
5567         DrawPosition(FALSE, boards[currentMove]);
5568     else if(moveType != ImpossibleMove && moveType != Comment)
5569         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5570 }
5571
5572 void LeftClick(ClickType clickType, int xPix, int yPix)
5573 {
5574     int x, y;
5575     Boolean saveAnimate;
5576     static int second = 0, promotionChoice = 0;
5577     char promoChoice = NULLCHAR;
5578
5579     if (clickType == Press) ErrorPopDown();
5580
5581     x = EventToSquare(xPix, BOARD_WIDTH);
5582     y = EventToSquare(yPix, BOARD_HEIGHT);
5583     if (!flipView && y >= 0) {
5584         y = BOARD_HEIGHT - 1 - y;
5585     }
5586     if (flipView && x >= 0) {
5587         x = BOARD_WIDTH - 1 - x;
5588     }
5589
5590     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5591         if(clickType == Release) return; // ignore upclick of click-click destination
5592         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5593         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5594         if(gameInfo.holdingsWidth && 
5595                 (WhiteOnMove(currentMove) 
5596                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5597                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5598             // click in right holdings, for determining promotion piece
5599             ChessSquare p = boards[currentMove][y][x];
5600             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5601             if(p != EmptySquare) {
5602                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5603                 fromX = fromY = -1;
5604                 return;
5605             }
5606         }
5607         DrawPosition(FALSE, boards[currentMove]);
5608         return;
5609     }
5610
5611     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5612     if(clickType == Press
5613             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5614               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5615               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5616         return;
5617
5618     if (fromX == -1) {
5619         if (clickType == Press) {
5620             /* First square */
5621             if (OKToStartUserMove(x, y)) {
5622                 fromX = x;
5623                 fromY = y;
5624                 second = 0;
5625                 DragPieceBegin(xPix, yPix);
5626                 if (appData.highlightDragging) {
5627                     SetHighlights(x, y, -1, -1);
5628                 }
5629             }
5630         }
5631         return;
5632     }
5633
5634     /* fromX != -1 */
5635     if (clickType == Press && gameMode != EditPosition) {
5636         ChessSquare fromP;
5637         ChessSquare toP;
5638         int frc;
5639
5640         // ignore off-board to clicks
5641         if(y < 0 || x < 0) return;
5642
5643         /* Check if clicking again on the same color piece */
5644         fromP = boards[currentMove][fromY][fromX];
5645         toP = boards[currentMove][y][x];
5646         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5647         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5648              WhitePawn <= toP && toP <= WhiteKing &&
5649              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5650              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5651             (BlackPawn <= fromP && fromP <= BlackKing && 
5652              BlackPawn <= toP && toP <= BlackKing &&
5653              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5654              !(fromP == BlackKing && toP == BlackRook && frc))) {
5655             /* Clicked again on same color piece -- changed his mind */
5656             second = (x == fromX && y == fromY);
5657             if (appData.highlightDragging) {
5658                 SetHighlights(x, y, -1, -1);
5659             } else {
5660                 ClearHighlights();
5661             }
5662             if (OKToStartUserMove(x, y)) {
5663                 fromX = x;
5664                 fromY = y;
5665                 DragPieceBegin(xPix, yPix);
5666             }
5667             return;
5668         }
5669         // ignore clicks on holdings
5670         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5671     }
5672
5673     if (clickType == Release && x == fromX && y == fromY) {
5674         DragPieceEnd(xPix, yPix);
5675         if (appData.animateDragging) {
5676             /* Undo animation damage if any */
5677             DrawPosition(FALSE, NULL);
5678         }
5679         if (second) {
5680             /* Second up/down in same square; just abort move */
5681             second = 0;
5682             fromX = fromY = -1;
5683             ClearHighlights();
5684             gotPremove = 0;
5685             ClearPremoveHighlights();
5686         } else {
5687             /* First upclick in same square; start click-click mode */
5688             SetHighlights(x, y, -1, -1);
5689         }
5690         return;
5691     }
5692
5693     /* we now have a different from- and (possibly off-board) to-square */
5694     /* Completed move */
5695     toX = x;
5696     toY = y;
5697     saveAnimate = appData.animate;
5698     if (clickType == Press) {
5699         /* Finish clickclick move */
5700         if (appData.animate || appData.highlightLastMove) {
5701             SetHighlights(fromX, fromY, toX, toY);
5702         } else {
5703             ClearHighlights();
5704         }
5705     } else {
5706         /* Finish drag move */
5707         if (appData.highlightLastMove) {
5708             SetHighlights(fromX, fromY, toX, toY);
5709         } else {
5710             ClearHighlights();
5711         }
5712         DragPieceEnd(xPix, yPix);
5713         /* Don't animate move and drag both */
5714         appData.animate = FALSE;
5715     }
5716
5717     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5718     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5719         ClearHighlights();
5720         fromX = fromY = -1;
5721         DrawPosition(TRUE, NULL);
5722         return;
5723     }
5724
5725     // off-board moves should not be highlighted
5726     if(x < 0 || x < 0) ClearHighlights();
5727
5728     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5729         SetHighlights(fromX, fromY, toX, toY);
5730         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5731             // [HGM] super: promotion to captured piece selected from holdings
5732             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5733             promotionChoice = TRUE;
5734             // kludge follows to temporarily execute move on display, without promoting yet
5735             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5736             boards[currentMove][toY][toX] = p;
5737             DrawPosition(FALSE, boards[currentMove]);
5738             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5739             boards[currentMove][toY][toX] = q;
5740             DisplayMessage("Click in holdings to choose piece", "");
5741             return;
5742         }
5743         PromotionPopUp();
5744     } else {
5745         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5746         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5747         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5748         fromX = fromY = -1;
5749     }
5750     appData.animate = saveAnimate;
5751     if (appData.animate || appData.animateDragging) {
5752         /* Undo animation damage if needed */
5753         DrawPosition(FALSE, NULL);
5754     }
5755 }
5756
5757 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5758 {
5759 //    char * hint = lastHint;
5760     FrontEndProgramStats stats;
5761
5762     stats.which = cps == &first ? 0 : 1;
5763     stats.depth = cpstats->depth;
5764     stats.nodes = cpstats->nodes;
5765     stats.score = cpstats->score;
5766     stats.time = cpstats->time;
5767     stats.pv = cpstats->movelist;
5768     stats.hint = lastHint;
5769     stats.an_move_index = 0;
5770     stats.an_move_count = 0;
5771
5772     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5773         stats.hint = cpstats->move_name;
5774         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5775         stats.an_move_count = cpstats->nr_moves;
5776     }
5777
5778     SetProgramStats( &stats );
5779 }
5780
5781 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5782 {   // [HGM] book: this routine intercepts moves to simulate book replies
5783     char *bookHit = NULL;
5784
5785     //first determine if the incoming move brings opponent into his book
5786     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5787         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5788     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5789     if(bookHit != NULL && !cps->bookSuspend) {
5790         // make sure opponent is not going to reply after receiving move to book position
5791         SendToProgram("force\n", cps);
5792         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5793     }
5794     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5795     // now arrange restart after book miss
5796     if(bookHit) {
5797         // after a book hit we never send 'go', and the code after the call to this routine
5798         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5799         char buf[MSG_SIZ];
5800         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5801         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5802         SendToProgram(buf, cps);
5803         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5804     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5805         SendToProgram("go\n", cps);
5806         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5807     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5808         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5809             SendToProgram("go\n", cps); 
5810         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5811     }
5812     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5813 }
5814
5815 char *savedMessage;
5816 ChessProgramState *savedState;
5817 void DeferredBookMove(void)
5818 {
5819         if(savedState->lastPing != savedState->lastPong)
5820                     ScheduleDelayedEvent(DeferredBookMove, 10);
5821         else
5822         HandleMachineMove(savedMessage, savedState);
5823 }
5824
5825 void
5826 HandleMachineMove(message, cps)
5827      char *message;
5828      ChessProgramState *cps;
5829 {
5830     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5831     char realname[MSG_SIZ];
5832     int fromX, fromY, toX, toY;
5833     ChessMove moveType;
5834     char promoChar;
5835     char *p;
5836     int machineWhite;
5837     char *bookHit;
5838
5839 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5840     /*
5841      * Kludge to ignore BEL characters
5842      */
5843     while (*message == '\007') message++;
5844
5845     /*
5846      * [HGM] engine debug message: ignore lines starting with '#' character
5847      */
5848     if(cps->debug && *message == '#') return;
5849
5850     /*
5851      * Look for book output
5852      */
5853     if (cps == &first && bookRequested) {
5854         if (message[0] == '\t' || message[0] == ' ') {
5855             /* Part of the book output is here; append it */
5856             strcat(bookOutput, message);
5857             strcat(bookOutput, "  \n");
5858             return;
5859         } else if (bookOutput[0] != NULLCHAR) {
5860             /* All of book output has arrived; display it */
5861             char *p = bookOutput;
5862             while (*p != NULLCHAR) {
5863                 if (*p == '\t') *p = ' ';
5864                 p++;
5865             }
5866             DisplayInformation(bookOutput);
5867             bookRequested = FALSE;
5868             /* Fall through to parse the current output */
5869         }
5870     }
5871
5872     /*
5873      * Look for machine move.
5874      */
5875     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5876         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5877     {
5878         /* This method is only useful on engines that support ping */
5879         if (cps->lastPing != cps->lastPong) {
5880           if (gameMode == BeginningOfGame) {
5881             /* Extra move from before last new; ignore */
5882             if (appData.debugMode) {
5883                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5884             }
5885           } else {
5886             if (appData.debugMode) {
5887                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5888                         cps->which, gameMode);
5889             }
5890
5891             SendToProgram("undo\n", cps);
5892           }
5893           return;
5894         }
5895
5896         switch (gameMode) {
5897           case BeginningOfGame:
5898             /* Extra move from before last reset; ignore */
5899             if (appData.debugMode) {
5900                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5901             }
5902             return;
5903
5904           case EndOfGame:
5905           case IcsIdle:
5906           default:
5907             /* Extra move after we tried to stop.  The mode test is
5908                not a reliable way of detecting this problem, but it's
5909                the best we can do on engines that don't support ping.
5910             */
5911             if (appData.debugMode) {
5912                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5913                         cps->which, gameMode);
5914             }
5915             SendToProgram("undo\n", cps);
5916             return;
5917
5918           case MachinePlaysWhite:
5919           case IcsPlayingWhite:
5920             machineWhite = TRUE;
5921             break;
5922
5923           case MachinePlaysBlack:
5924           case IcsPlayingBlack:
5925             machineWhite = FALSE;
5926             break;
5927
5928           case TwoMachinesPlay:
5929             machineWhite = (cps->twoMachinesColor[0] == 'w');
5930             break;
5931         }
5932         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5933             if (appData.debugMode) {
5934                 fprintf(debugFP,
5935                         "Ignoring move out of turn by %s, gameMode %d"
5936                         ", forwardMost %d\n",
5937                         cps->which, gameMode, forwardMostMove);
5938             }
5939             return;
5940         }
5941
5942     if (appData.debugMode) { int f = forwardMostMove;
5943         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5944                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5945     }
5946         if(cps->alphaRank) AlphaRank(machineMove, 4);
5947         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5948                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5949             /* Machine move could not be parsed; ignore it. */
5950             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5951                     machineMove, cps->which);
5952             DisplayError(buf1, 0);
5953             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5954                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5955             if (gameMode == TwoMachinesPlay) {
5956               GameEnds(machineWhite ? BlackWins : WhiteWins,
5957                        buf1, GE_XBOARD);
5958             }
5959             return;
5960         }
5961
5962         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5963         /* So we have to redo legality test with true e.p. status here,  */
5964         /* to make sure an illegal e.p. capture does not slip through,   */
5965         /* to cause a forfeit on a justified illegal-move complaint      */
5966         /* of the opponent.                                              */
5967         if( gameMode==TwoMachinesPlay && appData.testLegality
5968             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5969                                                               ) {
5970            ChessMove moveType;
5971            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5972                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5973                              fromY, fromX, toY, toX, promoChar);
5974             if (appData.debugMode) {
5975                 int i;
5976                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5977                     castlingRights[forwardMostMove][i], castlingRank[i]);
5978                 fprintf(debugFP, "castling rights\n");
5979             }
5980             if(moveType == IllegalMove) {
5981                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5982                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5983                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5984                            buf1, GE_XBOARD);
5985                 return;
5986            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5987            /* [HGM] Kludge to handle engines that send FRC-style castling
5988               when they shouldn't (like TSCP-Gothic) */
5989            switch(moveType) {
5990              case WhiteASideCastleFR:
5991              case BlackASideCastleFR:
5992                toX+=2;
5993                currentMoveString[2]++;
5994                break;
5995              case WhiteHSideCastleFR:
5996              case BlackHSideCastleFR:
5997                toX--;
5998                currentMoveString[2]--;
5999                break;
6000              default: ; // nothing to do, but suppresses warning of pedantic compilers
6001            }
6002         }
6003         hintRequested = FALSE;
6004         lastHint[0] = NULLCHAR;
6005         bookRequested = FALSE;
6006         /* Program may be pondering now */
6007         cps->maybeThinking = TRUE;
6008         if (cps->sendTime == 2) cps->sendTime = 1;
6009         if (cps->offeredDraw) cps->offeredDraw--;
6010
6011 #if ZIPPY
6012         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6013             first.initDone) {
6014           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6015           ics_user_moved = 1;
6016           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6017                 char buf[3*MSG_SIZ];
6018
6019                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6020                         programStats.score / 100.,
6021                         programStats.depth,
6022                         programStats.time / 100.,
6023                         (unsigned int)programStats.nodes,
6024                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6025                         programStats.movelist);
6026                 SendToICS(buf);
6027 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6028           }
6029         }
6030 #endif
6031         /* currentMoveString is set as a side-effect of ParseOneMove */
6032         strcpy(machineMove, currentMoveString);
6033         strcat(machineMove, "\n");
6034         strcpy(moveList[forwardMostMove], machineMove);
6035
6036         /* [AS] Save move info and clear stats for next move */
6037         pvInfoList[ forwardMostMove ].score = programStats.score;
6038         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6039         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6040         ClearProgramStats();
6041         thinkOutput[0] = NULLCHAR;
6042         hiddenThinkOutputState = 0;
6043
6044         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6045
6046         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6047         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6048             int count = 0;
6049
6050             while( count < adjudicateLossPlies ) {
6051                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6052
6053                 if( count & 1 ) {
6054                     score = -score; /* Flip score for winning side */
6055                 }
6056
6057                 if( score > adjudicateLossThreshold ) {
6058                     break;
6059                 }
6060
6061                 count++;
6062             }
6063
6064             if( count >= adjudicateLossPlies ) {
6065                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6066
6067                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6068                     "Xboard adjudication", 
6069                     GE_XBOARD );
6070
6071                 return;
6072             }
6073         }
6074
6075         if( gameMode == TwoMachinesPlay ) {
6076           // [HGM] some adjudications useful with buggy engines
6077             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6078           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6079
6080
6081             if( appData.testLegality )
6082             {   /* [HGM] Some more adjudications for obstinate engines */
6083                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6084                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6085                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6086                 static int moveCount = 6;
6087                 ChessMove result;
6088                 char *reason = NULL;
6089
6090                 /* Count what is on board. */
6091                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6092                 {   ChessSquare p = boards[forwardMostMove][i][j];
6093                     int m=i;
6094
6095                     switch((int) p)
6096                     {   /* count B,N,R and other of each side */
6097                         case WhiteKing:
6098                         case BlackKing:
6099                              NrK++; break; // [HGM] atomic: count Kings
6100                         case WhiteKnight:
6101                              NrWN++; break;
6102                         case WhiteBishop:
6103                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6104                              bishopsColor |= 1 << ((i^j)&1);
6105                              NrWB++; break;
6106                         case BlackKnight:
6107                              NrBN++; break;
6108                         case BlackBishop:
6109                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6110                              bishopsColor |= 1 << ((i^j)&1);
6111                              NrBB++; break;
6112                         case WhiteRook:
6113                              NrWR++; break;
6114                         case BlackRook:
6115                              NrBR++; break;
6116                         case WhiteQueen:
6117                              NrWQ++; break;
6118                         case BlackQueen:
6119                              NrBQ++; break;
6120                         case EmptySquare: 
6121                              break;
6122                         case BlackPawn:
6123                              m = 7-i;
6124                         case WhitePawn:
6125                              PawnAdvance += m; NrPawns++;
6126                     }
6127                     NrPieces += (p != EmptySquare);
6128                     NrW += ((int)p < (int)BlackPawn);
6129                     if(gameInfo.variant == VariantXiangqi && 
6130                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6131                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6132                         NrW -= ((int)p < (int)BlackPawn);
6133                     }
6134                 }
6135
6136                 /* Some material-based adjudications that have to be made before stalemate test */
6137                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6138                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6139                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6140                      if(appData.checkMates) {
6141                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6142                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6143                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6144                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6145                          return;
6146                      }
6147                 }
6148
6149                 /* Bare King in Shatranj (loses) or Losers (wins) */
6150                 if( NrW == 1 || NrPieces - NrW == 1) {
6151                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6152                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6153                      if(appData.checkMates) {
6154                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6155                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6156                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6157                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6158                          return;
6159                      }
6160                   } else
6161                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6162                   {    /* bare King */
6163                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6164                         if(appData.checkMates) {
6165                             /* but only adjudicate if adjudication enabled */
6166                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6167                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6168                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6169                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6170                             return;
6171                         }
6172                   }
6173                 } else bare = 1;
6174
6175
6176             // don't wait for engine to announce game end if we can judge ourselves
6177             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6178                                        castlingRights[forwardMostMove]) ) {
6179               case MT_CHECK:
6180                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6181                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6182                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6183                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6184                             checkCnt++;
6185                         if(checkCnt >= 2) {
6186                             reason = "Xboard adjudication: 3rd check";
6187                             epStatus[forwardMostMove] = EP_CHECKMATE;
6188                             break;
6189                         }
6190                     }
6191                 }
6192               case MT_NONE:
6193               default:
6194                 break;
6195               case MT_STALEMATE:
6196               case MT_STAINMATE:
6197                 reason = "Xboard adjudication: Stalemate";
6198                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6199                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6200                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6201                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6202                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6203                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6204                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6205                                                                         EP_CHECKMATE : EP_WINS);
6206                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6207                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6208                 }
6209                 break;
6210               case MT_CHECKMATE:
6211                 reason = "Xboard adjudication: Checkmate";
6212                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6213                 break;
6214             }
6215
6216                 switch(i = epStatus[forwardMostMove]) {
6217                     case EP_STALEMATE:
6218                         result = GameIsDrawn; break;
6219                     case EP_CHECKMATE:
6220                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6221                     case EP_WINS:
6222                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6223                     default:
6224                         result = (ChessMove) 0;
6225                 }
6226                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6227                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6228                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6229                     GameEnds( result, reason, GE_XBOARD );
6230                     return;
6231                 }
6232
6233                 /* Next absolutely insufficient mating material. */
6234                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6235                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6236                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6237                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6238                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6239
6240                      /* always flag draws, for judging claims */
6241                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6242
6243                      if(appData.materialDraws) {
6244                          /* but only adjudicate them if adjudication enabled */
6245                          SendToProgram("force\n", cps->other); // suppress reply
6246                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6247                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6248                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6249                          return;
6250                      }
6251                 }
6252
6253                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6254                 if(NrPieces == 4 && 
6255                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6256                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6257                    || NrWN==2 || NrBN==2     /* KNNK */
6258                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6259                   ) ) {
6260                      if(--moveCount < 0 && appData.trivialDraws)
6261                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6262                           SendToProgram("force\n", cps->other); // suppress reply
6263                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6264                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6265                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6266                           return;
6267                      }
6268                 } else moveCount = 6;
6269             }
6270           }
6271
6272                 /* Check for rep-draws */
6273                 count = 0;
6274                 for(k = forwardMostMove-2;
6275                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6276                         epStatus[k] < EP_UNKNOWN &&
6277                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6278                     k-=2)
6279                 {   int rights=0;
6280                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6281                         /* compare castling rights */
6282                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6283                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6284                                 rights++; /* King lost rights, while rook still had them */
6285                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6286                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6287                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6288                                    rights++; /* but at least one rook lost them */
6289                         }
6290                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6291                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6292                                 rights++; 
6293                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6294                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6295                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6296                                    rights++;
6297                         }
6298                         if( rights == 0 && ++count > appData.drawRepeats-2
6299                             && appData.drawRepeats > 1) {
6300                              /* adjudicate after user-specified nr of repeats */
6301                              SendToProgram("force\n", cps->other); // suppress reply
6302                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6303                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6304                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6305                                 // [HGM] xiangqi: check for forbidden perpetuals
6306                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6307                                 for(m=forwardMostMove; m>k; m-=2) {
6308                                     if(MateTest(boards[m], PosFlags(m), 
6309                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6310                                         ourPerpetual = 0; // the current mover did not always check
6311                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6312                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6313                                         hisPerpetual = 0; // the opponent did not always check
6314                                 }
6315                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6316                                                                         ourPerpetual, hisPerpetual);
6317                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6318                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6319                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6320                                     return;
6321                                 }
6322                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6323                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6324                                 // Now check for perpetual chases
6325                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6326                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6327                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6328                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6329                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6330                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6331                                         return;
6332                                     }
6333                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6334                                         break; // Abort repetition-checking loop.
6335                                 }
6336                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6337                              }
6338                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6339                              return;
6340                         }
6341                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6342                              epStatus[forwardMostMove] = EP_REP_DRAW;
6343                     }
6344                 }
6345
6346                 /* Now we test for 50-move draws. Determine ply count */
6347                 count = forwardMostMove;
6348                 /* look for last irreversble move */
6349                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6350                     count--;
6351                 /* if we hit starting position, add initial plies */
6352                 if( count == backwardMostMove )
6353                     count -= initialRulePlies;
6354                 count = forwardMostMove - count; 
6355                 if( count >= 100)
6356                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6357                          /* this is used to judge if draw claims are legal */
6358                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6359                          SendToProgram("force\n", cps->other); // suppress reply
6360                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6361                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6362                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6363                          return;
6364                 }
6365
6366                 /* if draw offer is pending, treat it as a draw claim
6367                  * when draw condition present, to allow engines a way to
6368                  * claim draws before making their move to avoid a race
6369                  * condition occurring after their move
6370                  */
6371                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6372                          char *p = NULL;
6373                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6374                              p = "Draw claim: 50-move rule";
6375                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6376                              p = "Draw claim: 3-fold repetition";
6377                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6378                              p = "Draw claim: insufficient mating material";
6379                          if( p != NULL ) {
6380                              SendToProgram("force\n", cps->other); // suppress reply
6381                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6382                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6383                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6384                              return;
6385                          }
6386                 }
6387
6388
6389                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6390                     SendToProgram("force\n", cps->other); // suppress reply
6391                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6392                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6393
6394                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6395
6396                     return;
6397                 }
6398         }
6399
6400         bookHit = NULL;
6401         if (gameMode == TwoMachinesPlay) {
6402             /* [HGM] relaying draw offers moved to after reception of move */
6403             /* and interpreting offer as claim if it brings draw condition */
6404             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6405                 SendToProgram("draw\n", cps->other);
6406             }
6407             if (cps->other->sendTime) {
6408                 SendTimeRemaining(cps->other,
6409                                   cps->other->twoMachinesColor[0] == 'w');
6410             }
6411             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6412             if (firstMove && !bookHit) {
6413                 firstMove = FALSE;
6414                 if (cps->other->useColors) {
6415                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6416                 }
6417                 SendToProgram("go\n", cps->other);
6418             }
6419             cps->other->maybeThinking = TRUE;
6420         }
6421
6422         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6423         
6424         if (!pausing && appData.ringBellAfterMoves) {
6425             RingBell();
6426         }
6427
6428         /* 
6429          * Reenable menu items that were disabled while
6430          * machine was thinking
6431          */
6432         if (gameMode != TwoMachinesPlay)
6433             SetUserThinkingEnables();
6434
6435         // [HGM] book: after book hit opponent has received move and is now in force mode
6436         // force the book reply into it, and then fake that it outputted this move by jumping
6437         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6438         if(bookHit) {
6439                 static char bookMove[MSG_SIZ]; // a bit generous?
6440
6441                 strcpy(bookMove, "move ");
6442                 strcat(bookMove, bookHit);
6443                 message = bookMove;
6444                 cps = cps->other;
6445                 programStats.nodes = programStats.depth = programStats.time = 
6446                 programStats.score = programStats.got_only_move = 0;
6447                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6448
6449                 if(cps->lastPing != cps->lastPong) {
6450                     savedMessage = message; // args for deferred call
6451                     savedState = cps;
6452                     ScheduleDelayedEvent(DeferredBookMove, 10);
6453                     return;
6454                 }
6455                 goto FakeBookMove;
6456         }
6457
6458         return;
6459     }
6460
6461     /* Set special modes for chess engines.  Later something general
6462      *  could be added here; for now there is just one kludge feature,
6463      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6464      *  when "xboard" is given as an interactive command.
6465      */
6466     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6467         cps->useSigint = FALSE;
6468         cps->useSigterm = FALSE;
6469     }
6470     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6471       ParseFeatures(message+8, cps);
6472       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6473     }
6474
6475     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6476      * want this, I was asked to put it in, and obliged.
6477      */
6478     if (!strncmp(message, "setboard ", 9)) {
6479         Board initial_position; int i;
6480
6481         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6482
6483         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6484             DisplayError(_("Bad FEN received from engine"), 0);
6485             return ;
6486         } else {
6487            Reset(TRUE, FALSE);
6488            CopyBoard(boards[0], initial_position);
6489            initialRulePlies = FENrulePlies;
6490            epStatus[0] = FENepStatus;
6491            for( i=0; i<nrCastlingRights; i++ )
6492                 castlingRights[0][i] = FENcastlingRights[i];
6493            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6494            else gameMode = MachinePlaysBlack;                 
6495            DrawPosition(FALSE, boards[currentMove]);
6496         }
6497         return;
6498     }
6499
6500     /*
6501      * Look for communication commands
6502      */
6503     if (!strncmp(message, "telluser ", 9)) {
6504         DisplayNote(message + 9);
6505         return;
6506     }
6507     if (!strncmp(message, "tellusererror ", 14)) {
6508         DisplayError(message + 14, 0);
6509         return;
6510     }
6511     if (!strncmp(message, "tellopponent ", 13)) {
6512       if (appData.icsActive) {
6513         if (loggedOn) {
6514           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6515           SendToICS(buf1);
6516         }
6517       } else {
6518         DisplayNote(message + 13);
6519       }
6520       return;
6521     }
6522     if (!strncmp(message, "tellothers ", 11)) {
6523       if (appData.icsActive) {
6524         if (loggedOn) {
6525           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6526           SendToICS(buf1);
6527         }
6528       }
6529       return;
6530     }
6531     if (!strncmp(message, "tellall ", 8)) {
6532       if (appData.icsActive) {
6533         if (loggedOn) {
6534           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6535           SendToICS(buf1);
6536         }
6537       } else {
6538         DisplayNote(message + 8);
6539       }
6540       return;
6541     }
6542     if (strncmp(message, "warning", 7) == 0) {
6543         /* Undocumented feature, use tellusererror in new code */
6544         DisplayError(message, 0);
6545         return;
6546     }
6547     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6548         strcpy(realname, cps->tidy);
6549         strcat(realname, " query");
6550         AskQuestion(realname, buf2, buf1, cps->pr);
6551         return;
6552     }
6553     /* Commands from the engine directly to ICS.  We don't allow these to be 
6554      *  sent until we are logged on. Crafty kibitzes have been known to 
6555      *  interfere with the login process.
6556      */
6557     if (loggedOn) {
6558         if (!strncmp(message, "tellics ", 8)) {
6559             SendToICS(message + 8);
6560             SendToICS("\n");
6561             return;
6562         }
6563         if (!strncmp(message, "tellicsnoalias ", 15)) {
6564             SendToICS(ics_prefix);
6565             SendToICS(message + 15);
6566             SendToICS("\n");
6567             return;
6568         }
6569         /* The following are for backward compatibility only */
6570         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6571             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6572             SendToICS(ics_prefix);
6573             SendToICS(message);
6574             SendToICS("\n");
6575             return;
6576         }
6577     }
6578     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6579         return;
6580     }
6581     /*
6582      * If the move is illegal, cancel it and redraw the board.
6583      * Also deal with other error cases.  Matching is rather loose
6584      * here to accommodate engines written before the spec.
6585      */
6586     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6587         strncmp(message, "Error", 5) == 0) {
6588         if (StrStr(message, "name") || 
6589             StrStr(message, "rating") || StrStr(message, "?") ||
6590             StrStr(message, "result") || StrStr(message, "board") ||
6591             StrStr(message, "bk") || StrStr(message, "computer") ||
6592             StrStr(message, "variant") || StrStr(message, "hint") ||
6593             StrStr(message, "random") || StrStr(message, "depth") ||
6594             StrStr(message, "accepted")) {
6595             return;
6596         }
6597         if (StrStr(message, "protover")) {
6598           /* Program is responding to input, so it's apparently done
6599              initializing, and this error message indicates it is
6600              protocol version 1.  So we don't need to wait any longer
6601              for it to initialize and send feature commands. */
6602           FeatureDone(cps, 1);
6603           cps->protocolVersion = 1;
6604           return;
6605         }
6606         cps->maybeThinking = FALSE;
6607
6608         if (StrStr(message, "draw")) {
6609             /* Program doesn't have "draw" command */
6610             cps->sendDrawOffers = 0;
6611             return;
6612         }
6613         if (cps->sendTime != 1 &&
6614             (StrStr(message, "time") || StrStr(message, "otim"))) {
6615           /* Program apparently doesn't have "time" or "otim" command */
6616           cps->sendTime = 0;
6617           return;
6618         }
6619         if (StrStr(message, "analyze")) {
6620             cps->analysisSupport = FALSE;
6621             cps->analyzing = FALSE;
6622             Reset(FALSE, TRUE);
6623             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6624             DisplayError(buf2, 0);
6625             return;
6626         }
6627         if (StrStr(message, "(no matching move)st")) {
6628           /* Special kludge for GNU Chess 4 only */
6629           cps->stKludge = TRUE;
6630           SendTimeControl(cps, movesPerSession, timeControl,
6631                           timeIncrement, appData.searchDepth,
6632                           searchTime);
6633           return;
6634         }
6635         if (StrStr(message, "(no matching move)sd")) {
6636           /* Special kludge for GNU Chess 4 only */
6637           cps->sdKludge = TRUE;
6638           SendTimeControl(cps, movesPerSession, timeControl,
6639                           timeIncrement, appData.searchDepth,
6640                           searchTime);
6641           return;
6642         }
6643         if (!StrStr(message, "llegal")) {
6644             return;
6645         }
6646         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6647             gameMode == IcsIdle) return;
6648         if (forwardMostMove <= backwardMostMove) return;
6649         if (pausing) PauseEvent();
6650       if(appData.forceIllegal) {
6651             // [HGM] illegal: machine refused move; force position after move into it
6652           SendToProgram("force\n", cps);
6653           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6654                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6655                 // when black is to move, while there might be nothing on a2 or black
6656                 // might already have the move. So send the board as if white has the move.
6657                 // But first we must change the stm of the engine, as it refused the last move
6658                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6659                 if(WhiteOnMove(forwardMostMove)) {
6660                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6661                     SendBoard(cps, forwardMostMove); // kludgeless board
6662                 } else {
6663                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6664                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6665                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6666                 }
6667           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6668             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6669                  gameMode == TwoMachinesPlay)
6670               SendToProgram("go\n", cps);
6671             return;
6672       } else
6673         if (gameMode == PlayFromGameFile) {
6674             /* Stop reading this game file */
6675             gameMode = EditGame;
6676             ModeHighlight();
6677         }
6678         currentMove = forwardMostMove-1;
6679         DisplayMove(currentMove-1); /* before DisplayMoveError */
6680         SwitchClocks(forwardMostMove-1); // [HGM] race
6681         DisplayBothClocks();
6682         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6683                 parseList[currentMove], cps->which);
6684         DisplayMoveError(buf1);
6685         DrawPosition(FALSE, boards[currentMove]);
6686
6687         /* [HGM] illegal-move claim should forfeit game when Xboard */
6688         /* only passes fully legal moves                            */
6689         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6690             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6691                                 "False illegal-move claim", GE_XBOARD );
6692         }
6693         return;
6694     }
6695     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6696         /* Program has a broken "time" command that
6697            outputs a string not ending in newline.
6698            Don't use it. */
6699         cps->sendTime = 0;
6700     }
6701     
6702     /*
6703      * If chess program startup fails, exit with an error message.
6704      * Attempts to recover here are futile.
6705      */
6706     if ((StrStr(message, "unknown host") != NULL)
6707         || (StrStr(message, "No remote directory") != NULL)
6708         || (StrStr(message, "not found") != NULL)
6709         || (StrStr(message, "No such file") != NULL)
6710         || (StrStr(message, "can't alloc") != NULL)
6711         || (StrStr(message, "Permission denied") != NULL)) {
6712
6713         cps->maybeThinking = FALSE;
6714         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6715                 cps->which, cps->program, cps->host, message);
6716         RemoveInputSource(cps->isr);
6717         DisplayFatalError(buf1, 0, 1);
6718         return;
6719     }
6720     
6721     /* 
6722      * Look for hint output
6723      */
6724     if (sscanf(message, "Hint: %s", buf1) == 1) {
6725         if (cps == &first && hintRequested) {
6726             hintRequested = FALSE;
6727             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6728                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6729                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6730                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6731                                     fromY, fromX, toY, toX, promoChar, buf1);
6732                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6733                 DisplayInformation(buf2);
6734             } else {
6735                 /* Hint move could not be parsed!? */
6736               snprintf(buf2, sizeof(buf2),
6737                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6738                         buf1, cps->which);
6739                 DisplayError(buf2, 0);
6740             }
6741         } else {
6742             strcpy(lastHint, buf1);
6743         }
6744         return;
6745     }
6746
6747     /*
6748      * Ignore other messages if game is not in progress
6749      */
6750     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6751         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6752
6753     /*
6754      * look for win, lose, draw, or draw offer
6755      */
6756     if (strncmp(message, "1-0", 3) == 0) {
6757         char *p, *q, *r = "";
6758         p = strchr(message, '{');
6759         if (p) {
6760             q = strchr(p, '}');
6761             if (q) {
6762                 *q = NULLCHAR;
6763                 r = p + 1;
6764             }
6765         }
6766         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6767         return;
6768     } else if (strncmp(message, "0-1", 3) == 0) {
6769         char *p, *q, *r = "";
6770         p = strchr(message, '{');
6771         if (p) {
6772             q = strchr(p, '}');
6773             if (q) {
6774                 *q = NULLCHAR;
6775                 r = p + 1;
6776             }
6777         }
6778         /* Kludge for Arasan 4.1 bug */
6779         if (strcmp(r, "Black resigns") == 0) {
6780             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6781             return;
6782         }
6783         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6784         return;
6785     } else if (strncmp(message, "1/2", 3) == 0) {
6786         char *p, *q, *r = "";
6787         p = strchr(message, '{');
6788         if (p) {
6789             q = strchr(p, '}');
6790             if (q) {
6791                 *q = NULLCHAR;
6792                 r = p + 1;
6793             }
6794         }
6795             
6796         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6797         return;
6798
6799     } else if (strncmp(message, "White resign", 12) == 0) {
6800         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6801         return;
6802     } else if (strncmp(message, "Black resign", 12) == 0) {
6803         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6804         return;
6805     } else if (strncmp(message, "White matches", 13) == 0 ||
6806                strncmp(message, "Black matches", 13) == 0   ) {
6807         /* [HGM] ignore GNUShogi noises */
6808         return;
6809     } else if (strncmp(message, "White", 5) == 0 &&
6810                message[5] != '(' &&
6811                StrStr(message, "Black") == NULL) {
6812         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6813         return;
6814     } else if (strncmp(message, "Black", 5) == 0 &&
6815                message[5] != '(') {
6816         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6817         return;
6818     } else if (strcmp(message, "resign") == 0 ||
6819                strcmp(message, "computer resigns") == 0) {
6820         switch (gameMode) {
6821           case MachinePlaysBlack:
6822           case IcsPlayingBlack:
6823             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6824             break;
6825           case MachinePlaysWhite:
6826           case IcsPlayingWhite:
6827             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6828             break;
6829           case TwoMachinesPlay:
6830             if (cps->twoMachinesColor[0] == 'w')
6831               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6832             else
6833               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6834             break;
6835           default:
6836             /* can't happen */
6837             break;
6838         }
6839         return;
6840     } else if (strncmp(message, "opponent mates", 14) == 0) {
6841         switch (gameMode) {
6842           case MachinePlaysBlack:
6843           case IcsPlayingBlack:
6844             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6845             break;
6846           case MachinePlaysWhite:
6847           case IcsPlayingWhite:
6848             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6849             break;
6850           case TwoMachinesPlay:
6851             if (cps->twoMachinesColor[0] == 'w')
6852               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6853             else
6854               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6855             break;
6856           default:
6857             /* can't happen */
6858             break;
6859         }
6860         return;
6861     } else if (strncmp(message, "computer mates", 14) == 0) {
6862         switch (gameMode) {
6863           case MachinePlaysBlack:
6864           case IcsPlayingBlack:
6865             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6866             break;
6867           case MachinePlaysWhite:
6868           case IcsPlayingWhite:
6869             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6870             break;
6871           case TwoMachinesPlay:
6872             if (cps->twoMachinesColor[0] == 'w')
6873               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6874             else
6875               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6876             break;
6877           default:
6878             /* can't happen */
6879             break;
6880         }
6881         return;
6882     } else if (strncmp(message, "checkmate", 9) == 0) {
6883         if (WhiteOnMove(forwardMostMove)) {
6884             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6885         } else {
6886             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6887         }
6888         return;
6889     } else if (strstr(message, "Draw") != NULL ||
6890                strstr(message, "game is a draw") != NULL) {
6891         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6892         return;
6893     } else if (strstr(message, "offer") != NULL &&
6894                strstr(message, "draw") != NULL) {
6895 #if ZIPPY
6896         if (appData.zippyPlay && first.initDone) {
6897             /* Relay offer to ICS */
6898             SendToICS(ics_prefix);
6899             SendToICS("draw\n");
6900         }
6901 #endif
6902         cps->offeredDraw = 2; /* valid until this engine moves twice */
6903         if (gameMode == TwoMachinesPlay) {
6904             if (cps->other->offeredDraw) {
6905                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6906             /* [HGM] in two-machine mode we delay relaying draw offer      */
6907             /* until after we also have move, to see if it is really claim */
6908             }
6909         } else if (gameMode == MachinePlaysWhite ||
6910                    gameMode == MachinePlaysBlack) {
6911           if (userOfferedDraw) {
6912             DisplayInformation(_("Machine accepts your draw offer"));
6913             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6914           } else {
6915             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6916           }
6917         }
6918     }
6919
6920     
6921     /*
6922      * Look for thinking output
6923      */
6924     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6925           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6926                                 ) {
6927         int plylev, mvleft, mvtot, curscore, time;
6928         char mvname[MOVE_LEN];
6929         u64 nodes; // [DM]
6930         char plyext;
6931         int ignore = FALSE;
6932         int prefixHint = FALSE;
6933         mvname[0] = NULLCHAR;
6934
6935         switch (gameMode) {
6936           case MachinePlaysBlack:
6937           case IcsPlayingBlack:
6938             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6939             break;
6940           case MachinePlaysWhite:
6941           case IcsPlayingWhite:
6942             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6943             break;
6944           case AnalyzeMode:
6945           case AnalyzeFile:
6946             break;
6947           case IcsObserving: /* [DM] icsEngineAnalyze */
6948             if (!appData.icsEngineAnalyze) ignore = TRUE;
6949             break;
6950           case TwoMachinesPlay:
6951             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6952                 ignore = TRUE;
6953             }
6954             break;
6955           default:
6956             ignore = TRUE;
6957             break;
6958         }
6959
6960         if (!ignore) {
6961             buf1[0] = NULLCHAR;
6962             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6963                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6964
6965                 if (plyext != ' ' && plyext != '\t') {
6966                     time *= 100;
6967                 }
6968
6969                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6970                 if( cps->scoreIsAbsolute && 
6971                     ( gameMode == MachinePlaysBlack ||
6972                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6973                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
6974                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6975                      !WhiteOnMove(currentMove)
6976                     ) )
6977                 {
6978                     curscore = -curscore;
6979                 }
6980
6981
6982                 programStats.depth = plylev;
6983                 programStats.nodes = nodes;
6984                 programStats.time = time;
6985                 programStats.score = curscore;
6986                 programStats.got_only_move = 0;
6987
6988                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6989                         int ticklen;
6990
6991                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6992                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6993                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6994                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6995                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6996                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6997                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6998                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6999                 }
7000
7001                 /* Buffer overflow protection */
7002                 if (buf1[0] != NULLCHAR) {
7003                     if (strlen(buf1) >= sizeof(programStats.movelist)
7004                         && appData.debugMode) {
7005                         fprintf(debugFP,
7006                                 "PV is too long; using the first %u bytes.\n",
7007                                 (unsigned) sizeof(programStats.movelist) - 1);
7008                     }
7009
7010                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7011                 } else {
7012                     sprintf(programStats.movelist, " no PV\n");
7013                 }
7014
7015                 if (programStats.seen_stat) {
7016                     programStats.ok_to_send = 1;
7017                 }
7018
7019                 if (strchr(programStats.movelist, '(') != NULL) {
7020                     programStats.line_is_book = 1;
7021                     programStats.nr_moves = 0;
7022                     programStats.moves_left = 0;
7023                 } else {
7024                     programStats.line_is_book = 0;
7025                 }
7026
7027                 SendProgramStatsToFrontend( cps, &programStats );
7028
7029                 /* 
7030                     [AS] Protect the thinkOutput buffer from overflow... this
7031                     is only useful if buf1 hasn't overflowed first!
7032                 */
7033                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7034                         plylev, 
7035                         (gameMode == TwoMachinesPlay ?
7036                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7037                         ((double) curscore) / 100.0,
7038                         prefixHint ? lastHint : "",
7039                         prefixHint ? " " : "" );
7040
7041                 if( buf1[0] != NULLCHAR ) {
7042                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7043
7044                     if( strlen(buf1) > max_len ) {
7045                         if( appData.debugMode) {
7046                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7047                         }
7048                         buf1[max_len+1] = '\0';
7049                     }
7050
7051                     strcat( thinkOutput, buf1 );
7052                 }
7053
7054                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7055                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7056                     DisplayMove(currentMove - 1);
7057                 }
7058                 return;
7059
7060             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7061                 /* crafty (9.25+) says "(only move) <move>"
7062                  * if there is only 1 legal move
7063                  */
7064                 sscanf(p, "(only move) %s", buf1);
7065                 sprintf(thinkOutput, "%s (only move)", buf1);
7066                 sprintf(programStats.movelist, "%s (only move)", buf1);
7067                 programStats.depth = 1;
7068                 programStats.nr_moves = 1;
7069                 programStats.moves_left = 1;
7070                 programStats.nodes = 1;
7071                 programStats.time = 1;
7072                 programStats.got_only_move = 1;
7073
7074                 /* Not really, but we also use this member to
7075                    mean "line isn't going to change" (Crafty
7076                    isn't searching, so stats won't change) */
7077                 programStats.line_is_book = 1;
7078
7079                 SendProgramStatsToFrontend( cps, &programStats );
7080                 
7081                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7082                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7083                     DisplayMove(currentMove - 1);
7084                 }
7085                 return;
7086             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7087                               &time, &nodes, &plylev, &mvleft,
7088                               &mvtot, mvname) >= 5) {
7089                 /* The stat01: line is from Crafty (9.29+) in response
7090                    to the "." command */
7091                 programStats.seen_stat = 1;
7092                 cps->maybeThinking = TRUE;
7093
7094                 if (programStats.got_only_move || !appData.periodicUpdates)
7095                   return;
7096
7097                 programStats.depth = plylev;
7098                 programStats.time = time;
7099                 programStats.nodes = nodes;
7100                 programStats.moves_left = mvleft;
7101                 programStats.nr_moves = mvtot;
7102                 strcpy(programStats.move_name, mvname);
7103                 programStats.ok_to_send = 1;
7104                 programStats.movelist[0] = '\0';
7105
7106                 SendProgramStatsToFrontend( cps, &programStats );
7107
7108                 return;
7109
7110             } else if (strncmp(message,"++",2) == 0) {
7111                 /* Crafty 9.29+ outputs this */
7112                 programStats.got_fail = 2;
7113                 return;
7114
7115             } else if (strncmp(message,"--",2) == 0) {
7116                 /* Crafty 9.29+ outputs this */
7117                 programStats.got_fail = 1;
7118                 return;
7119
7120             } else if (thinkOutput[0] != NULLCHAR &&
7121                        strncmp(message, "    ", 4) == 0) {
7122                 unsigned message_len;
7123
7124                 p = message;
7125                 while (*p && *p == ' ') p++;
7126
7127                 message_len = strlen( p );
7128
7129                 /* [AS] Avoid buffer overflow */
7130                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7131                     strcat(thinkOutput, " ");
7132                     strcat(thinkOutput, p);
7133                 }
7134
7135                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7136                     strcat(programStats.movelist, " ");
7137                     strcat(programStats.movelist, p);
7138                 }
7139
7140                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7141                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7142                     DisplayMove(currentMove - 1);
7143                 }
7144                 return;
7145             }
7146         }
7147         else {
7148             buf1[0] = NULLCHAR;
7149
7150             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7151                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7152             {
7153                 ChessProgramStats cpstats;
7154
7155                 if (plyext != ' ' && plyext != '\t') {
7156                     time *= 100;
7157                 }
7158
7159                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7160                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7161                     curscore = -curscore;
7162                 }
7163
7164                 cpstats.depth = plylev;
7165                 cpstats.nodes = nodes;
7166                 cpstats.time = time;
7167                 cpstats.score = curscore;
7168                 cpstats.got_only_move = 0;
7169                 cpstats.movelist[0] = '\0';
7170
7171                 if (buf1[0] != NULLCHAR) {
7172                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7173                 }
7174
7175                 cpstats.ok_to_send = 0;
7176                 cpstats.line_is_book = 0;
7177                 cpstats.nr_moves = 0;
7178                 cpstats.moves_left = 0;
7179
7180                 SendProgramStatsToFrontend( cps, &cpstats );
7181             }
7182         }
7183     }
7184 }
7185
7186
7187 /* Parse a game score from the character string "game", and
7188    record it as the history of the current game.  The game
7189    score is NOT assumed to start from the standard position. 
7190    The display is not updated in any way.
7191    */
7192 void
7193 ParseGameHistory(game)
7194      char *game;
7195 {
7196     ChessMove moveType;
7197     int fromX, fromY, toX, toY, boardIndex;
7198     char promoChar;
7199     char *p, *q;
7200     char buf[MSG_SIZ];
7201
7202     if (appData.debugMode)
7203       fprintf(debugFP, "Parsing game history: %s\n", game);
7204
7205     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7206     gameInfo.site = StrSave(appData.icsHost);
7207     gameInfo.date = PGNDate();
7208     gameInfo.round = StrSave("-");
7209
7210     /* Parse out names of players */
7211     while (*game == ' ') game++;
7212     p = buf;
7213     while (*game != ' ') *p++ = *game++;
7214     *p = NULLCHAR;
7215     gameInfo.white = StrSave(buf);
7216     while (*game == ' ') game++;
7217     p = buf;
7218     while (*game != ' ' && *game != '\n') *p++ = *game++;
7219     *p = NULLCHAR;
7220     gameInfo.black = StrSave(buf);
7221
7222     /* Parse moves */
7223     boardIndex = blackPlaysFirst ? 1 : 0;
7224     yynewstr(game);
7225     for (;;) {
7226         yyboardindex = boardIndex;
7227         moveType = (ChessMove) yylex();
7228         switch (moveType) {
7229           case IllegalMove:             /* maybe suicide chess, etc. */
7230   if (appData.debugMode) {
7231     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7232     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7233     setbuf(debugFP, NULL);
7234   }
7235           case WhitePromotionChancellor:
7236           case BlackPromotionChancellor:
7237           case WhitePromotionArchbishop:
7238           case BlackPromotionArchbishop:
7239           case WhitePromotionQueen:
7240           case BlackPromotionQueen:
7241           case WhitePromotionRook:
7242           case BlackPromotionRook:
7243           case WhitePromotionBishop:
7244           case BlackPromotionBishop:
7245           case WhitePromotionKnight:
7246           case BlackPromotionKnight:
7247           case WhitePromotionKing:
7248           case BlackPromotionKing:
7249           case NormalMove:
7250           case WhiteCapturesEnPassant:
7251           case BlackCapturesEnPassant:
7252           case WhiteKingSideCastle:
7253           case WhiteQueenSideCastle:
7254           case BlackKingSideCastle:
7255           case BlackQueenSideCastle:
7256           case WhiteKingSideCastleWild:
7257           case WhiteQueenSideCastleWild:
7258           case BlackKingSideCastleWild:
7259           case BlackQueenSideCastleWild:
7260           /* PUSH Fabien */
7261           case WhiteHSideCastleFR:
7262           case WhiteASideCastleFR:
7263           case BlackHSideCastleFR:
7264           case BlackASideCastleFR:
7265           /* POP Fabien */
7266             fromX = currentMoveString[0] - AAA;
7267             fromY = currentMoveString[1] - ONE;
7268             toX = currentMoveString[2] - AAA;
7269             toY = currentMoveString[3] - ONE;
7270             promoChar = currentMoveString[4];
7271             break;
7272           case WhiteDrop:
7273           case BlackDrop:
7274             fromX = moveType == WhiteDrop ?
7275               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7276             (int) CharToPiece(ToLower(currentMoveString[0]));
7277             fromY = DROP_RANK;
7278             toX = currentMoveString[2] - AAA;
7279             toY = currentMoveString[3] - ONE;
7280             promoChar = NULLCHAR;
7281             break;
7282           case AmbiguousMove:
7283             /* bug? */
7284             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7285   if (appData.debugMode) {
7286     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7287     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7288     setbuf(debugFP, NULL);
7289   }
7290             DisplayError(buf, 0);
7291             return;
7292           case ImpossibleMove:
7293             /* bug? */
7294             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7295   if (appData.debugMode) {
7296     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7297     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7298     setbuf(debugFP, NULL);
7299   }
7300             DisplayError(buf, 0);
7301             return;
7302           case (ChessMove) 0:   /* end of file */
7303             if (boardIndex < backwardMostMove) {
7304                 /* Oops, gap.  How did that happen? */
7305                 DisplayError(_("Gap in move list"), 0);
7306                 return;
7307             }
7308             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7309             if (boardIndex > forwardMostMove) {
7310                 forwardMostMove = boardIndex;
7311             }
7312             return;
7313           case ElapsedTime:
7314             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7315                 strcat(parseList[boardIndex-1], " ");
7316                 strcat(parseList[boardIndex-1], yy_text);
7317             }
7318             continue;
7319           case Comment:
7320           case PGNTag:
7321           case NAG:
7322           default:
7323             /* ignore */
7324             continue;
7325           case WhiteWins:
7326           case BlackWins:
7327           case GameIsDrawn:
7328           case GameUnfinished:
7329             if (gameMode == IcsExamining) {
7330                 if (boardIndex < backwardMostMove) {
7331                     /* Oops, gap.  How did that happen? */
7332                     return;
7333                 }
7334                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7335                 return;
7336             }
7337             gameInfo.result = moveType;
7338             p = strchr(yy_text, '{');
7339             if (p == NULL) p = strchr(yy_text, '(');
7340             if (p == NULL) {
7341                 p = yy_text;
7342                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7343             } else {
7344                 q = strchr(p, *p == '{' ? '}' : ')');
7345                 if (q != NULL) *q = NULLCHAR;
7346                 p++;
7347             }
7348             gameInfo.resultDetails = StrSave(p);
7349             continue;
7350         }
7351         if (boardIndex >= forwardMostMove &&
7352             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7353             backwardMostMove = blackPlaysFirst ? 1 : 0;
7354             return;
7355         }
7356         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7357                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7358                                  parseList[boardIndex]);
7359         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7360         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7361         /* currentMoveString is set as a side-effect of yylex */
7362         strcpy(moveList[boardIndex], currentMoveString);
7363         strcat(moveList[boardIndex], "\n");
7364         boardIndex++;
7365         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7366                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7367         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7368                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7369           case MT_NONE:
7370           case MT_STALEMATE:
7371           default:
7372             break;
7373           case MT_CHECK:
7374             if(gameInfo.variant != VariantShogi)
7375                 strcat(parseList[boardIndex - 1], "+");
7376             break;
7377           case MT_CHECKMATE:
7378           case MT_STAINMATE:
7379             strcat(parseList[boardIndex - 1], "#");
7380             break;
7381         }
7382     }
7383 }
7384
7385
7386 /* Apply a move to the given board  */
7387 void
7388 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7389      int fromX, fromY, toX, toY;
7390      int promoChar;
7391      Board board;
7392      char *castling;
7393      char *ep;
7394 {
7395   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7396   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7397
7398     /* [HGM] compute & store e.p. status and castling rights for new position */
7399     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7400     { int i;
7401
7402       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7403       oldEP = *ep;
7404       *ep = EP_NONE;
7405
7406       if( board[toY][toX] != EmptySquare ) 
7407            *ep = EP_CAPTURE;  
7408
7409       if( board[fromY][fromX] == WhitePawn ) {
7410            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7411                *ep = EP_PAWN_MOVE;
7412            if( toY-fromY==2) {
7413                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7414                         gameInfo.variant != VariantBerolina || toX < fromX)
7415                       *ep = toX | berolina;
7416                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7417                         gameInfo.variant != VariantBerolina || toX > fromX) 
7418                       *ep = toX;
7419            }
7420       } else 
7421       if( board[fromY][fromX] == BlackPawn ) {
7422            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7423                *ep = EP_PAWN_MOVE; 
7424            if( toY-fromY== -2) {
7425                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7426                         gameInfo.variant != VariantBerolina || toX < fromX)
7427                       *ep = toX | berolina;
7428                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7429                         gameInfo.variant != VariantBerolina || toX > fromX) 
7430                       *ep = toX;
7431            }
7432        }
7433
7434        for(i=0; i<nrCastlingRights; i++) {
7435            if(castling[i] == fromX && castlingRank[i] == fromY ||
7436               castling[i] == toX   && castlingRank[i] == toY   
7437              ) castling[i] = -1; // revoke for moved or captured piece
7438        }
7439
7440     }
7441
7442   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7443   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7444        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7445          
7446   if (fromX == toX && fromY == toY) return;
7447
7448   if (fromY == DROP_RANK) {
7449         /* must be first */
7450         piece = board[toY][toX] = (ChessSquare) fromX;
7451   } else {
7452      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7453      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7454      if(gameInfo.variant == VariantKnightmate)
7455          king += (int) WhiteUnicorn - (int) WhiteKing;
7456
7457     /* Code added by Tord: */
7458     /* FRC castling assumed when king captures friendly rook. */
7459     if (board[fromY][fromX] == WhiteKing &&
7460              board[toY][toX] == WhiteRook) {
7461       board[fromY][fromX] = EmptySquare;
7462       board[toY][toX] = EmptySquare;
7463       if(toX > fromX) {
7464         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7465       } else {
7466         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7467       }
7468     } else if (board[fromY][fromX] == BlackKing &&
7469                board[toY][toX] == BlackRook) {
7470       board[fromY][fromX] = EmptySquare;
7471       board[toY][toX] = EmptySquare;
7472       if(toX > fromX) {
7473         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7474       } else {
7475         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7476       }
7477     /* End of code added by Tord */
7478
7479     } else if (board[fromY][fromX] == king
7480         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7481         && toY == fromY && toX > fromX+1) {
7482         board[fromY][fromX] = EmptySquare;
7483         board[toY][toX] = king;
7484         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7485         board[fromY][BOARD_RGHT-1] = EmptySquare;
7486     } else if (board[fromY][fromX] == king
7487         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7488                && toY == fromY && toX < fromX-1) {
7489         board[fromY][fromX] = EmptySquare;
7490         board[toY][toX] = king;
7491         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7492         board[fromY][BOARD_LEFT] = EmptySquare;
7493     } else if (board[fromY][fromX] == WhitePawn
7494                && toY >= BOARD_HEIGHT-promoRank
7495                && gameInfo.variant != VariantXiangqi
7496                ) {
7497         /* white pawn promotion */
7498         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7499         if (board[toY][toX] == EmptySquare) {
7500             board[toY][toX] = WhiteQueen;
7501         }
7502         if(gameInfo.variant==VariantBughouse ||
7503            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7504             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7505         board[fromY][fromX] = EmptySquare;
7506     } else if ((fromY == BOARD_HEIGHT-4)
7507                && (toX != fromX)
7508                && gameInfo.variant != VariantXiangqi
7509                && gameInfo.variant != VariantBerolina
7510                && (board[fromY][fromX] == WhitePawn)
7511                && (board[toY][toX] == EmptySquare)) {
7512         board[fromY][fromX] = EmptySquare;
7513         board[toY][toX] = WhitePawn;
7514         captured = board[toY - 1][toX];
7515         board[toY - 1][toX] = EmptySquare;
7516     } else if ((fromY == BOARD_HEIGHT-4)
7517                && (toX == fromX)
7518                && gameInfo.variant == VariantBerolina
7519                && (board[fromY][fromX] == WhitePawn)
7520                && (board[toY][toX] == EmptySquare)) {
7521         board[fromY][fromX] = EmptySquare;
7522         board[toY][toX] = WhitePawn;
7523         if(oldEP & EP_BEROLIN_A) {
7524                 captured = board[fromY][fromX-1];
7525                 board[fromY][fromX-1] = EmptySquare;
7526         }else{  captured = board[fromY][fromX+1];
7527                 board[fromY][fromX+1] = EmptySquare;
7528         }
7529     } else if (board[fromY][fromX] == king
7530         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7531                && toY == fromY && toX > fromX+1) {
7532         board[fromY][fromX] = EmptySquare;
7533         board[toY][toX] = king;
7534         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7535         board[fromY][BOARD_RGHT-1] = EmptySquare;
7536     } else if (board[fromY][fromX] == king
7537         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7538                && toY == fromY && toX < fromX-1) {
7539         board[fromY][fromX] = EmptySquare;
7540         board[toY][toX] = king;
7541         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7542         board[fromY][BOARD_LEFT] = EmptySquare;
7543     } else if (fromY == 7 && fromX == 3
7544                && board[fromY][fromX] == BlackKing
7545                && toY == 7 && toX == 5) {
7546         board[fromY][fromX] = EmptySquare;
7547         board[toY][toX] = BlackKing;
7548         board[fromY][7] = EmptySquare;
7549         board[toY][4] = BlackRook;
7550     } else if (fromY == 7 && fromX == 3
7551                && board[fromY][fromX] == BlackKing
7552                && toY == 7 && toX == 1) {
7553         board[fromY][fromX] = EmptySquare;
7554         board[toY][toX] = BlackKing;
7555         board[fromY][0] = EmptySquare;
7556         board[toY][2] = BlackRook;
7557     } else if (board[fromY][fromX] == BlackPawn
7558                && toY < promoRank
7559                && gameInfo.variant != VariantXiangqi
7560                ) {
7561         /* black pawn promotion */
7562         board[toY][toX] = CharToPiece(ToLower(promoChar));
7563         if (board[toY][toX] == EmptySquare) {
7564             board[toY][toX] = BlackQueen;
7565         }
7566         if(gameInfo.variant==VariantBughouse ||
7567            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7568             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7569         board[fromY][fromX] = EmptySquare;
7570     } else if ((fromY == 3)
7571                && (toX != fromX)
7572                && gameInfo.variant != VariantXiangqi
7573                && gameInfo.variant != VariantBerolina
7574                && (board[fromY][fromX] == BlackPawn)
7575                && (board[toY][toX] == EmptySquare)) {
7576         board[fromY][fromX] = EmptySquare;
7577         board[toY][toX] = BlackPawn;
7578         captured = board[toY + 1][toX];
7579         board[toY + 1][toX] = EmptySquare;
7580     } else if ((fromY == 3)
7581                && (toX == fromX)
7582                && gameInfo.variant == VariantBerolina
7583                && (board[fromY][fromX] == BlackPawn)
7584                && (board[toY][toX] == EmptySquare)) {
7585         board[fromY][fromX] = EmptySquare;
7586         board[toY][toX] = BlackPawn;
7587         if(oldEP & EP_BEROLIN_A) {
7588                 captured = board[fromY][fromX-1];
7589                 board[fromY][fromX-1] = EmptySquare;
7590         }else{  captured = board[fromY][fromX+1];
7591                 board[fromY][fromX+1] = EmptySquare;
7592         }
7593     } else {
7594         board[toY][toX] = board[fromY][fromX];
7595         board[fromY][fromX] = EmptySquare;
7596     }
7597
7598     /* [HGM] now we promote for Shogi, if needed */
7599     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7600         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7601   }
7602
7603     if (gameInfo.holdingsWidth != 0) {
7604
7605       /* !!A lot more code needs to be written to support holdings  */
7606       /* [HGM] OK, so I have written it. Holdings are stored in the */
7607       /* penultimate board files, so they are automaticlly stored   */
7608       /* in the game history.                                       */
7609       if (fromY == DROP_RANK) {
7610         /* Delete from holdings, by decreasing count */
7611         /* and erasing image if necessary            */
7612         p = (int) fromX;
7613         if(p < (int) BlackPawn) { /* white drop */
7614              p -= (int)WhitePawn;
7615                  p = PieceToNumber((ChessSquare)p);
7616              if(p >= gameInfo.holdingsSize) p = 0;
7617              if(--board[p][BOARD_WIDTH-2] <= 0)
7618                   board[p][BOARD_WIDTH-1] = EmptySquare;
7619              if((int)board[p][BOARD_WIDTH-2] < 0)
7620                         board[p][BOARD_WIDTH-2] = 0;
7621         } else {                  /* black drop */
7622              p -= (int)BlackPawn;
7623                  p = PieceToNumber((ChessSquare)p);
7624              if(p >= gameInfo.holdingsSize) p = 0;
7625              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7626                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7627              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7628                         board[BOARD_HEIGHT-1-p][1] = 0;
7629         }
7630       }
7631       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7632           && gameInfo.variant != VariantBughouse        ) {
7633         /* [HGM] holdings: Add to holdings, if holdings exist */
7634         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7635                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7636                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7637         }
7638         p = (int) captured;
7639         if (p >= (int) BlackPawn) {
7640           p -= (int)BlackPawn;
7641           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7642                   /* in Shogi restore piece to its original  first */
7643                   captured = (ChessSquare) (DEMOTED captured);
7644                   p = DEMOTED p;
7645           }
7646           p = PieceToNumber((ChessSquare)p);
7647           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7648           board[p][BOARD_WIDTH-2]++;
7649           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7650         } else {
7651           p -= (int)WhitePawn;
7652           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7653                   captured = (ChessSquare) (DEMOTED captured);
7654                   p = DEMOTED p;
7655           }
7656           p = PieceToNumber((ChessSquare)p);
7657           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7658           board[BOARD_HEIGHT-1-p][1]++;
7659           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7660         }
7661       }
7662     } else if (gameInfo.variant == VariantAtomic) {
7663       if (captured != EmptySquare) {
7664         int y, x;
7665         for (y = toY-1; y <= toY+1; y++) {
7666           for (x = toX-1; x <= toX+1; x++) {
7667             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7668                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7669               board[y][x] = EmptySquare;
7670             }
7671           }
7672         }
7673         board[toY][toX] = EmptySquare;
7674       }
7675     }
7676     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7677         /* [HGM] Shogi promotions */
7678         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7679     }
7680
7681     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7682                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7683         // [HGM] superchess: take promotion piece out of holdings
7684         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7685         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7686             if(!--board[k][BOARD_WIDTH-2])
7687                 board[k][BOARD_WIDTH-1] = EmptySquare;
7688         } else {
7689             if(!--board[BOARD_HEIGHT-1-k][1])
7690                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7691         }
7692     }
7693
7694 }
7695
7696 /* Updates forwardMostMove */
7697 void
7698 MakeMove(fromX, fromY, toX, toY, promoChar)
7699      int fromX, fromY, toX, toY;
7700      int promoChar;
7701 {
7702 //    forwardMostMove++; // [HGM] bare: moved downstream
7703
7704     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7705         int timeLeft; static int lastLoadFlag=0; int king, piece;
7706         piece = boards[forwardMostMove][fromY][fromX];
7707         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7708         if(gameInfo.variant == VariantKnightmate)
7709             king += (int) WhiteUnicorn - (int) WhiteKing;
7710         if(forwardMostMove == 0) {
7711             if(blackPlaysFirst) 
7712                 fprintf(serverMoves, "%s;", second.tidy);
7713             fprintf(serverMoves, "%s;", first.tidy);
7714             if(!blackPlaysFirst) 
7715                 fprintf(serverMoves, "%s;", second.tidy);
7716         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7717         lastLoadFlag = loadFlag;
7718         // print base move
7719         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7720         // print castling suffix
7721         if( toY == fromY && piece == king ) {
7722             if(toX-fromX > 1)
7723                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7724             if(fromX-toX >1)
7725                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7726         }
7727         // e.p. suffix
7728         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7729              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7730              boards[forwardMostMove][toY][toX] == EmptySquare
7731              && fromX != toX )
7732                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7733         // promotion suffix
7734         if(promoChar != NULLCHAR)
7735                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7736         if(!loadFlag) {
7737             fprintf(serverMoves, "/%d/%d",
7738                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7739             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7740             else                      timeLeft = blackTimeRemaining/1000;
7741             fprintf(serverMoves, "/%d", timeLeft);
7742         }
7743         fflush(serverMoves);
7744     }
7745
7746     if (forwardMostMove+1 >= MAX_MOVES) {
7747       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7748                         0, 1);
7749       return;
7750     }
7751     if (commentList[forwardMostMove+1] != NULL) {
7752         free(commentList[forwardMostMove+1]);
7753         commentList[forwardMostMove+1] = NULL;
7754     }
7755     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7756     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7757     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7758                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7759     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7760     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
7761     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7762     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7763     gameInfo.result = GameUnfinished;
7764     if (gameInfo.resultDetails != NULL) {
7765         free(gameInfo.resultDetails);
7766         gameInfo.resultDetails = NULL;
7767     }
7768     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7769                               moveList[forwardMostMove - 1]);
7770     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7771                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7772                              fromY, fromX, toY, toX, promoChar,
7773                              parseList[forwardMostMove - 1]);
7774     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7775                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7776                             castlingRights[forwardMostMove]) ) {
7777       case MT_NONE:
7778       case MT_STALEMATE:
7779       default:
7780         break;
7781       case MT_CHECK:
7782         if(gameInfo.variant != VariantShogi)
7783             strcat(parseList[forwardMostMove - 1], "+");
7784         break;
7785       case MT_CHECKMATE:
7786       case MT_STAINMATE:
7787         strcat(parseList[forwardMostMove - 1], "#");
7788         break;
7789     }
7790     if (appData.debugMode) {
7791         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7792     }
7793
7794 }
7795
7796 /* Updates currentMove if not pausing */
7797 void
7798 ShowMove(fromX, fromY, toX, toY)
7799 {
7800     int instant = (gameMode == PlayFromGameFile) ?
7801         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7802     if(appData.noGUI) return;
7803     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7804         if (!instant) {
7805             if (forwardMostMove == currentMove + 1) {
7806                 AnimateMove(boards[forwardMostMove - 1],
7807                             fromX, fromY, toX, toY);
7808             }
7809             if (appData.highlightLastMove) {
7810                 SetHighlights(fromX, fromY, toX, toY);
7811             }
7812         }
7813         currentMove = forwardMostMove;
7814     }
7815
7816     if (instant) return;
7817
7818     DisplayMove(currentMove - 1);
7819     DrawPosition(FALSE, boards[currentMove]);
7820     DisplayBothClocks();
7821     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7822 }
7823
7824 void SendEgtPath(ChessProgramState *cps)
7825 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7826         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7827
7828         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7829
7830         while(*p) {
7831             char c, *q = name+1, *r, *s;
7832
7833             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7834             while(*p && *p != ',') *q++ = *p++;
7835             *q++ = ':'; *q = 0;
7836             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7837                 strcmp(name, ",nalimov:") == 0 ) {
7838                 // take nalimov path from the menu-changeable option first, if it is defined
7839                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7840                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7841             } else
7842             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7843                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7844                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7845                 s = r = StrStr(s, ":") + 1; // beginning of path info
7846                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7847                 c = *r; *r = 0;             // temporarily null-terminate path info
7848                     *--q = 0;               // strip of trailig ':' from name
7849                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7850                 *r = c;
7851                 SendToProgram(buf,cps);     // send egtbpath command for this format
7852             }
7853             if(*p == ',') p++; // read away comma to position for next format name
7854         }
7855 }
7856
7857 void
7858 InitChessProgram(cps, setup)
7859      ChessProgramState *cps;
7860      int setup; /* [HGM] needed to setup FRC opening position */
7861 {
7862     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7863     if (appData.noChessProgram) return;
7864     hintRequested = FALSE;
7865     bookRequested = FALSE;
7866
7867     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7868     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7869     if(cps->memSize) { /* [HGM] memory */
7870         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7871         SendToProgram(buf, cps);
7872     }
7873     SendEgtPath(cps); /* [HGM] EGT */
7874     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7875         sprintf(buf, "cores %d\n", appData.smpCores);
7876         SendToProgram(buf, cps);
7877     }
7878
7879     SendToProgram(cps->initString, cps);
7880     if (gameInfo.variant != VariantNormal &&
7881         gameInfo.variant != VariantLoadable
7882         /* [HGM] also send variant if board size non-standard */
7883         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7884                                             ) {
7885       char *v = VariantName(gameInfo.variant);
7886       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7887         /* [HGM] in protocol 1 we have to assume all variants valid */
7888         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7889         DisplayFatalError(buf, 0, 1);
7890         return;
7891       }
7892
7893       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7894       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7895       if( gameInfo.variant == VariantXiangqi )
7896            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7897       if( gameInfo.variant == VariantShogi )
7898            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7899       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7900            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7901       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7902                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7903            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7904       if( gameInfo.variant == VariantCourier )
7905            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7906       if( gameInfo.variant == VariantSuper )
7907            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7908       if( gameInfo.variant == VariantGreat )
7909            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7910
7911       if(overruled) {
7912            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7913                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7914            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7915            if(StrStr(cps->variants, b) == NULL) { 
7916                // specific sized variant not known, check if general sizing allowed
7917                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7918                    if(StrStr(cps->variants, "boardsize") == NULL) {
7919                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7920                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7921                        DisplayFatalError(buf, 0, 1);
7922                        return;
7923                    }
7924                    /* [HGM] here we really should compare with the maximum supported board size */
7925                }
7926            }
7927       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7928       sprintf(buf, "variant %s\n", b);
7929       SendToProgram(buf, cps);
7930     }
7931     currentlyInitializedVariant = gameInfo.variant;
7932
7933     /* [HGM] send opening position in FRC to first engine */
7934     if(setup) {
7935           SendToProgram("force\n", cps);
7936           SendBoard(cps, 0);
7937           /* engine is now in force mode! Set flag to wake it up after first move. */
7938           setboardSpoiledMachineBlack = 1;
7939     }
7940
7941     if (cps->sendICS) {
7942       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7943       SendToProgram(buf, cps);
7944     }
7945     cps->maybeThinking = FALSE;
7946     cps->offeredDraw = 0;
7947     if (!appData.icsActive) {
7948         SendTimeControl(cps, movesPerSession, timeControl,
7949                         timeIncrement, appData.searchDepth,
7950                         searchTime);
7951     }
7952     if (appData.showThinking 
7953         // [HGM] thinking: four options require thinking output to be sent
7954         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7955                                 ) {
7956         SendToProgram("post\n", cps);
7957     }
7958     SendToProgram("hard\n", cps);
7959     if (!appData.ponderNextMove) {
7960         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7961            it without being sure what state we are in first.  "hard"
7962            is not a toggle, so that one is OK.
7963          */
7964         SendToProgram("easy\n", cps);
7965     }
7966     if (cps->usePing) {
7967       sprintf(buf, "ping %d\n", ++cps->lastPing);
7968       SendToProgram(buf, cps);
7969     }
7970     cps->initDone = TRUE;
7971 }   
7972
7973
7974 void
7975 StartChessProgram(cps)
7976      ChessProgramState *cps;
7977 {
7978     char buf[MSG_SIZ];
7979     int err;
7980
7981     if (appData.noChessProgram) return;
7982     cps->initDone = FALSE;
7983
7984     if (strcmp(cps->host, "localhost") == 0) {
7985         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7986     } else if (*appData.remoteShell == NULLCHAR) {
7987         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7988     } else {
7989         if (*appData.remoteUser == NULLCHAR) {
7990           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7991                     cps->program);
7992         } else {
7993           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7994                     cps->host, appData.remoteUser, cps->program);
7995         }
7996         err = StartChildProcess(buf, "", &cps->pr);
7997     }
7998     
7999     if (err != 0) {
8000         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8001         DisplayFatalError(buf, err, 1);
8002         cps->pr = NoProc;
8003         cps->isr = NULL;
8004         return;
8005     }
8006     
8007     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8008     if (cps->protocolVersion > 1) {
8009       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8010       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8011       cps->comboCnt = 0;  //                and values of combo boxes
8012       SendToProgram(buf, cps);
8013     } else {
8014       SendToProgram("xboard\n", cps);
8015     }
8016 }
8017
8018
8019 void
8020 TwoMachinesEventIfReady P((void))
8021 {
8022   if (first.lastPing != first.lastPong) {
8023     DisplayMessage("", _("Waiting for first chess program"));
8024     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8025     return;
8026   }
8027   if (second.lastPing != second.lastPong) {
8028     DisplayMessage("", _("Waiting for second chess program"));
8029     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8030     return;
8031   }
8032   ThawUI();
8033   TwoMachinesEvent();
8034 }
8035
8036 void
8037 NextMatchGame P((void))
8038 {
8039     int index; /* [HGM] autoinc: step load index during match */
8040     Reset(FALSE, TRUE);
8041     if (*appData.loadGameFile != NULLCHAR) {
8042         index = appData.loadGameIndex;
8043         if(index < 0) { // [HGM] autoinc
8044             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8045             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8046         } 
8047         LoadGameFromFile(appData.loadGameFile,
8048                          index,
8049                          appData.loadGameFile, FALSE);
8050     } else if (*appData.loadPositionFile != NULLCHAR) {
8051         index = appData.loadPositionIndex;
8052         if(index < 0) { // [HGM] autoinc
8053             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8054             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8055         } 
8056         LoadPositionFromFile(appData.loadPositionFile,
8057                              index,
8058                              appData.loadPositionFile);
8059     }
8060     TwoMachinesEventIfReady();
8061 }
8062
8063 void UserAdjudicationEvent( int result )
8064 {
8065     ChessMove gameResult = GameIsDrawn;
8066
8067     if( result > 0 ) {
8068         gameResult = WhiteWins;
8069     }
8070     else if( result < 0 ) {
8071         gameResult = BlackWins;
8072     }
8073
8074     if( gameMode == TwoMachinesPlay ) {
8075         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8076     }
8077 }
8078
8079
8080 // [HGM] save: calculate checksum of game to make games easily identifiable
8081 int StringCheckSum(char *s)
8082 {
8083         int i = 0;
8084         if(s==NULL) return 0;
8085         while(*s) i = i*259 + *s++;
8086         return i;
8087 }
8088
8089 int GameCheckSum()
8090 {
8091         int i, sum=0;
8092         for(i=backwardMostMove; i<forwardMostMove; i++) {
8093                 sum += pvInfoList[i].depth;
8094                 sum += StringCheckSum(parseList[i]);
8095                 sum += StringCheckSum(commentList[i]);
8096                 sum *= 261;
8097         }
8098         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8099         return sum + StringCheckSum(commentList[i]);
8100 } // end of save patch
8101
8102 void
8103 GameEnds(result, resultDetails, whosays)
8104      ChessMove result;
8105      char *resultDetails;
8106      int whosays;
8107 {
8108     GameMode nextGameMode;
8109     int isIcsGame;
8110     char buf[MSG_SIZ];
8111
8112     if(endingGame) return; /* [HGM] crash: forbid recursion */
8113     endingGame = 1;
8114
8115     if (appData.debugMode) {
8116       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8117               result, resultDetails ? resultDetails : "(null)", whosays);
8118     }
8119
8120     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8121         /* If we are playing on ICS, the server decides when the
8122            game is over, but the engine can offer to draw, claim 
8123            a draw, or resign. 
8124          */
8125 #if ZIPPY
8126         if (appData.zippyPlay && first.initDone) {
8127             if (result == GameIsDrawn) {
8128                 /* In case draw still needs to be claimed */
8129                 SendToICS(ics_prefix);
8130                 SendToICS("draw\n");
8131             } else if (StrCaseStr(resultDetails, "resign")) {
8132                 SendToICS(ics_prefix);
8133                 SendToICS("resign\n");
8134             }
8135         }
8136 #endif
8137         endingGame = 0; /* [HGM] crash */
8138         return;
8139     }
8140
8141     /* If we're loading the game from a file, stop */
8142     if (whosays == GE_FILE) {
8143       (void) StopLoadGameTimer();
8144       gameFileFP = NULL;
8145     }
8146
8147     /* Cancel draw offers */
8148     first.offeredDraw = second.offeredDraw = 0;
8149
8150     /* If this is an ICS game, only ICS can really say it's done;
8151        if not, anyone can. */
8152     isIcsGame = (gameMode == IcsPlayingWhite || 
8153                  gameMode == IcsPlayingBlack || 
8154                  gameMode == IcsObserving    || 
8155                  gameMode == IcsExamining);
8156
8157     if (!isIcsGame || whosays == GE_ICS) {
8158         /* OK -- not an ICS game, or ICS said it was done */
8159         StopClocks();
8160         if (!isIcsGame && !appData.noChessProgram) 
8161           SetUserThinkingEnables();
8162     
8163         /* [HGM] if a machine claims the game end we verify this claim */
8164         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8165             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8166                 char claimer;
8167                 ChessMove trueResult = (ChessMove) -1;
8168
8169                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8170                                             first.twoMachinesColor[0] :
8171                                             second.twoMachinesColor[0] ;
8172
8173                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8174                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8175                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8176                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8177                 } else
8178                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8179                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8180                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8181                 } else
8182                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8183                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8184                 }
8185
8186                 // now verify win claims, but not in drop games, as we don't understand those yet
8187                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8188                                                  || gameInfo.variant == VariantGreat) &&
8189                     (result == WhiteWins && claimer == 'w' ||
8190                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8191                       if (appData.debugMode) {
8192                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8193                                 result, epStatus[forwardMostMove], forwardMostMove);
8194                       }
8195                       if(result != trueResult) {
8196                               sprintf(buf, "False win claim: '%s'", resultDetails);
8197                               result = claimer == 'w' ? BlackWins : WhiteWins;
8198                               resultDetails = buf;
8199                       }
8200                 } else
8201                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8202                     && (forwardMostMove <= backwardMostMove ||
8203                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8204                         (claimer=='b')==(forwardMostMove&1))
8205                                                                                   ) {
8206                       /* [HGM] verify: draws that were not flagged are false claims */
8207                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8208                       result = claimer == 'w' ? BlackWins : WhiteWins;
8209                       resultDetails = buf;
8210                 }
8211                 /* (Claiming a loss is accepted no questions asked!) */
8212             }
8213             /* [HGM] bare: don't allow bare King to win */
8214             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8215                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8216                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8217                && result != GameIsDrawn)
8218             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8219                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8220                         int p = (int)boards[forwardMostMove][i][j] - color;
8221                         if(p >= 0 && p <= (int)WhiteKing) k++;
8222                 }
8223                 if (appData.debugMode) {
8224                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8225                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8226                 }
8227                 if(k <= 1) {
8228                         result = GameIsDrawn;
8229                         sprintf(buf, "%s but bare king", resultDetails);
8230                         resultDetails = buf;
8231                 }
8232             }
8233         }
8234
8235
8236         if(serverMoves != NULL && !loadFlag) { char c = '=';
8237             if(result==WhiteWins) c = '+';
8238             if(result==BlackWins) c = '-';
8239             if(resultDetails != NULL)
8240                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8241         }
8242         if (resultDetails != NULL) {
8243             gameInfo.result = result;
8244             gameInfo.resultDetails = StrSave(resultDetails);
8245
8246             /* display last move only if game was not loaded from file */
8247             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8248                 DisplayMove(currentMove - 1);
8249     
8250             if (forwardMostMove != 0) {
8251                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8252                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8253                                                                 ) {
8254                     if (*appData.saveGameFile != NULLCHAR) {
8255                         SaveGameToFile(appData.saveGameFile, TRUE);
8256                     } else if (appData.autoSaveGames) {
8257                         AutoSaveGame();
8258                     }
8259                     if (*appData.savePositionFile != NULLCHAR) {
8260                         SavePositionToFile(appData.savePositionFile);
8261                     }
8262                 }
8263             }
8264
8265             /* Tell program how game ended in case it is learning */
8266             /* [HGM] Moved this to after saving the PGN, just in case */
8267             /* engine died and we got here through time loss. In that */
8268             /* case we will get a fatal error writing the pipe, which */
8269             /* would otherwise lose us the PGN.                       */
8270             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8271             /* output during GameEnds should never be fatal anymore   */
8272             if (gameMode == MachinePlaysWhite ||
8273                 gameMode == MachinePlaysBlack ||
8274                 gameMode == TwoMachinesPlay ||
8275                 gameMode == IcsPlayingWhite ||
8276                 gameMode == IcsPlayingBlack ||
8277                 gameMode == BeginningOfGame) {
8278                 char buf[MSG_SIZ];
8279                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8280                         resultDetails);
8281                 if (first.pr != NoProc) {
8282                     SendToProgram(buf, &first);
8283                 }
8284                 if (second.pr != NoProc &&
8285                     gameMode == TwoMachinesPlay) {
8286                     SendToProgram(buf, &second);
8287                 }
8288             }
8289         }
8290
8291         if (appData.icsActive) {
8292             if (appData.quietPlay &&
8293                 (gameMode == IcsPlayingWhite ||
8294                  gameMode == IcsPlayingBlack)) {
8295                 SendToICS(ics_prefix);
8296                 SendToICS("set shout 1\n");
8297             }
8298             nextGameMode = IcsIdle;
8299             ics_user_moved = FALSE;
8300             /* clean up premove.  It's ugly when the game has ended and the
8301              * premove highlights are still on the board.
8302              */
8303             if (gotPremove) {
8304               gotPremove = FALSE;
8305               ClearPremoveHighlights();
8306               DrawPosition(FALSE, boards[currentMove]);
8307             }
8308             if (whosays == GE_ICS) {
8309                 switch (result) {
8310                 case WhiteWins:
8311                     if (gameMode == IcsPlayingWhite)
8312                         PlayIcsWinSound();
8313                     else if(gameMode == IcsPlayingBlack)
8314                         PlayIcsLossSound();
8315                     break;
8316                 case BlackWins:
8317                     if (gameMode == IcsPlayingBlack)
8318                         PlayIcsWinSound();
8319                     else if(gameMode == IcsPlayingWhite)
8320                         PlayIcsLossSound();
8321                     break;
8322                 case GameIsDrawn:
8323                     PlayIcsDrawSound();
8324                     break;
8325                 default:
8326                     PlayIcsUnfinishedSound();
8327                 }
8328             }
8329         } else if (gameMode == EditGame ||
8330                    gameMode == PlayFromGameFile || 
8331                    gameMode == AnalyzeMode || 
8332                    gameMode == AnalyzeFile) {
8333             nextGameMode = gameMode;
8334         } else {
8335             nextGameMode = EndOfGame;
8336         }
8337         pausing = FALSE;
8338         ModeHighlight();
8339     } else {
8340         nextGameMode = gameMode;
8341     }
8342
8343     if (appData.noChessProgram) {
8344         gameMode = nextGameMode;
8345         ModeHighlight();
8346         endingGame = 0; /* [HGM] crash */
8347         return;
8348     }
8349
8350     if (first.reuse) {
8351         /* Put first chess program into idle state */
8352         if (first.pr != NoProc &&
8353             (gameMode == MachinePlaysWhite ||
8354              gameMode == MachinePlaysBlack ||
8355              gameMode == TwoMachinesPlay ||
8356              gameMode == IcsPlayingWhite ||
8357              gameMode == IcsPlayingBlack ||
8358              gameMode == BeginningOfGame)) {
8359             SendToProgram("force\n", &first);
8360             if (first.usePing) {
8361               char buf[MSG_SIZ];
8362               sprintf(buf, "ping %d\n", ++first.lastPing);
8363               SendToProgram(buf, &first);
8364             }
8365         }
8366     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8367         /* Kill off first chess program */
8368         if (first.isr != NULL)
8369           RemoveInputSource(first.isr);
8370         first.isr = NULL;
8371     
8372         if (first.pr != NoProc) {
8373             ExitAnalyzeMode();
8374             DoSleep( appData.delayBeforeQuit );
8375             SendToProgram("quit\n", &first);
8376             DoSleep( appData.delayAfterQuit );
8377             DestroyChildProcess(first.pr, first.useSigterm);
8378         }
8379         first.pr = NoProc;
8380     }
8381     if (second.reuse) {
8382         /* Put second chess program into idle state */
8383         if (second.pr != NoProc &&
8384             gameMode == TwoMachinesPlay) {
8385             SendToProgram("force\n", &second);
8386             if (second.usePing) {
8387               char buf[MSG_SIZ];
8388               sprintf(buf, "ping %d\n", ++second.lastPing);
8389               SendToProgram(buf, &second);
8390             }
8391         }
8392     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8393         /* Kill off second chess program */
8394         if (second.isr != NULL)
8395           RemoveInputSource(second.isr);
8396         second.isr = NULL;
8397     
8398         if (second.pr != NoProc) {
8399             DoSleep( appData.delayBeforeQuit );
8400             SendToProgram("quit\n", &second);
8401             DoSleep( appData.delayAfterQuit );
8402             DestroyChildProcess(second.pr, second.useSigterm);
8403         }
8404         second.pr = NoProc;
8405     }
8406
8407     if (matchMode && gameMode == TwoMachinesPlay) {
8408         switch (result) {
8409         case WhiteWins:
8410           if (first.twoMachinesColor[0] == 'w') {
8411             first.matchWins++;
8412           } else {
8413             second.matchWins++;
8414           }
8415           break;
8416         case BlackWins:
8417           if (first.twoMachinesColor[0] == 'b') {
8418             first.matchWins++;
8419           } else {
8420             second.matchWins++;
8421           }
8422           break;
8423         default:
8424           break;
8425         }
8426         if (matchGame < appData.matchGames) {
8427             char *tmp;
8428             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8429                 tmp = first.twoMachinesColor;
8430                 first.twoMachinesColor = second.twoMachinesColor;
8431                 second.twoMachinesColor = tmp;
8432             }
8433             gameMode = nextGameMode;
8434             matchGame++;
8435             if(appData.matchPause>10000 || appData.matchPause<10)
8436                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8437             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8438             endingGame = 0; /* [HGM] crash */
8439             return;
8440         } else {
8441             char buf[MSG_SIZ];
8442             gameMode = nextGameMode;
8443             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8444                     first.tidy, second.tidy,
8445                     first.matchWins, second.matchWins,
8446                     appData.matchGames - (first.matchWins + second.matchWins));
8447             DisplayFatalError(buf, 0, 0);
8448         }
8449     }
8450     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8451         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8452       ExitAnalyzeMode();
8453     gameMode = nextGameMode;
8454     ModeHighlight();
8455     endingGame = 0;  /* [HGM] crash */
8456 }
8457
8458 /* Assumes program was just initialized (initString sent).
8459    Leaves program in force mode. */
8460 void
8461 FeedMovesToProgram(cps, upto) 
8462      ChessProgramState *cps;
8463      int upto;
8464 {
8465     int i;
8466     
8467     if (appData.debugMode)
8468       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8469               startedFromSetupPosition ? "position and " : "",
8470               backwardMostMove, upto, cps->which);
8471     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8472         // [HGM] variantswitch: make engine aware of new variant
8473         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8474                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8475         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8476         SendToProgram(buf, cps);
8477         currentlyInitializedVariant = gameInfo.variant;
8478     }
8479     SendToProgram("force\n", cps);
8480     if (startedFromSetupPosition) {
8481         SendBoard(cps, backwardMostMove);
8482     if (appData.debugMode) {
8483         fprintf(debugFP, "feedMoves\n");
8484     }
8485     }
8486     for (i = backwardMostMove; i < upto; i++) {
8487         SendMoveToProgram(i, cps);
8488     }
8489 }
8490
8491
8492 void
8493 ResurrectChessProgram()
8494 {
8495      /* The chess program may have exited.
8496         If so, restart it and feed it all the moves made so far. */
8497
8498     if (appData.noChessProgram || first.pr != NoProc) return;
8499     
8500     StartChessProgram(&first);
8501     InitChessProgram(&first, FALSE);
8502     FeedMovesToProgram(&first, currentMove);
8503
8504     if (!first.sendTime) {
8505         /* can't tell gnuchess what its clock should read,
8506            so we bow to its notion. */
8507         ResetClocks();
8508         timeRemaining[0][currentMove] = whiteTimeRemaining;
8509         timeRemaining[1][currentMove] = blackTimeRemaining;
8510     }
8511
8512     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8513                 appData.icsEngineAnalyze) && first.analysisSupport) {
8514       SendToProgram("analyze\n", &first);
8515       first.analyzing = TRUE;
8516     }
8517 }
8518
8519 /*
8520  * Button procedures
8521  */
8522 void
8523 Reset(redraw, init)
8524      int redraw, init;
8525 {
8526     int i;
8527
8528     if (appData.debugMode) {
8529         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8530                 redraw, init, gameMode);
8531     }
8532     pausing = pauseExamInvalid = FALSE;
8533     startedFromSetupPosition = blackPlaysFirst = FALSE;
8534     firstMove = TRUE;
8535     whiteFlag = blackFlag = FALSE;
8536     userOfferedDraw = FALSE;
8537     hintRequested = bookRequested = FALSE;
8538     first.maybeThinking = FALSE;
8539     second.maybeThinking = FALSE;
8540     first.bookSuspend = FALSE; // [HGM] book
8541     second.bookSuspend = FALSE;
8542     thinkOutput[0] = NULLCHAR;
8543     lastHint[0] = NULLCHAR;
8544     ClearGameInfo(&gameInfo);
8545     gameInfo.variant = StringToVariant(appData.variant);
8546     ics_user_moved = ics_clock_paused = FALSE;
8547     ics_getting_history = H_FALSE;
8548     ics_gamenum = -1;
8549     white_holding[0] = black_holding[0] = NULLCHAR;
8550     ClearProgramStats();
8551     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8552     
8553     ResetFrontEnd();
8554     ClearHighlights();
8555     flipView = appData.flipView;
8556     ClearPremoveHighlights();
8557     gotPremove = FALSE;
8558     alarmSounded = FALSE;
8559
8560     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8561     if(appData.serverMovesName != NULL) {
8562         /* [HGM] prepare to make moves file for broadcasting */
8563         clock_t t = clock();
8564         if(serverMoves != NULL) fclose(serverMoves);
8565         serverMoves = fopen(appData.serverMovesName, "r");
8566         if(serverMoves != NULL) {
8567             fclose(serverMoves);
8568             /* delay 15 sec before overwriting, so all clients can see end */
8569             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8570         }
8571         serverMoves = fopen(appData.serverMovesName, "w");
8572     }
8573
8574     ExitAnalyzeMode();
8575     gameMode = BeginningOfGame;
8576     ModeHighlight();
8577     if(appData.icsActive) gameInfo.variant = VariantNormal;
8578     currentMove = forwardMostMove = backwardMostMove = 0;
8579     InitPosition(redraw);
8580     for (i = 0; i < MAX_MOVES; i++) {
8581         if (commentList[i] != NULL) {
8582             free(commentList[i]);
8583             commentList[i] = NULL;
8584         }
8585     }
8586     ResetClocks();
8587     timeRemaining[0][0] = whiteTimeRemaining;
8588     timeRemaining[1][0] = blackTimeRemaining;
8589     if (first.pr == NULL) {
8590         StartChessProgram(&first);
8591     }
8592     if (init) {
8593             InitChessProgram(&first, startedFromSetupPosition);
8594     }
8595     DisplayTitle("");
8596     DisplayMessage("", "");
8597     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8598     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8599 }
8600
8601 void
8602 AutoPlayGameLoop()
8603 {
8604     for (;;) {
8605         if (!AutoPlayOneMove())
8606           return;
8607         if (matchMode || appData.timeDelay == 0)
8608           continue;
8609         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8610           return;
8611         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8612         break;
8613     }
8614 }
8615
8616
8617 int
8618 AutoPlayOneMove()
8619 {
8620     int fromX, fromY, toX, toY;
8621
8622     if (appData.debugMode) {
8623       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8624     }
8625
8626     if (gameMode != PlayFromGameFile)
8627       return FALSE;
8628
8629     if (currentMove >= forwardMostMove) {
8630       gameMode = EditGame;
8631       ModeHighlight();
8632
8633       /* [AS] Clear current move marker at the end of a game */
8634       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8635
8636       return FALSE;
8637     }
8638     
8639     toX = moveList[currentMove][2] - AAA;
8640     toY = moveList[currentMove][3] - ONE;
8641
8642     if (moveList[currentMove][1] == '@') {
8643         if (appData.highlightLastMove) {
8644             SetHighlights(-1, -1, toX, toY);
8645         }
8646     } else {
8647         fromX = moveList[currentMove][0] - AAA;
8648         fromY = moveList[currentMove][1] - ONE;
8649
8650         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8651
8652         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8653
8654         if (appData.highlightLastMove) {
8655             SetHighlights(fromX, fromY, toX, toY);
8656         }
8657     }
8658     DisplayMove(currentMove);
8659     SendMoveToProgram(currentMove++, &first);
8660     DisplayBothClocks();
8661     DrawPosition(FALSE, boards[currentMove]);
8662     // [HGM] PV info: always display, routine tests if empty
8663     DisplayComment(currentMove - 1, commentList[currentMove]);
8664     return TRUE;
8665 }
8666
8667
8668 int
8669 LoadGameOneMove(readAhead)
8670      ChessMove readAhead;
8671 {
8672     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8673     char promoChar = NULLCHAR;
8674     ChessMove moveType;
8675     char move[MSG_SIZ];
8676     char *p, *q;
8677     
8678     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8679         gameMode != AnalyzeMode && gameMode != Training) {
8680         gameFileFP = NULL;
8681         return FALSE;
8682     }
8683     
8684     yyboardindex = forwardMostMove;
8685     if (readAhead != (ChessMove)0) {
8686       moveType = readAhead;
8687     } else {
8688       if (gameFileFP == NULL)
8689           return FALSE;
8690       moveType = (ChessMove) yylex();
8691     }
8692     
8693     done = FALSE;
8694     switch (moveType) {
8695       case Comment:
8696         if (appData.debugMode) 
8697           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8698         p = yy_text;
8699         if (*p == '{' || *p == '[' || *p == '(') {
8700             p[strlen(p) - 1] = NULLCHAR;
8701             p++;
8702         }
8703
8704         /* append the comment but don't display it */
8705         while (*p == '\n') p++;
8706         AppendComment(currentMove, p);
8707         return TRUE;
8708
8709       case WhiteCapturesEnPassant:
8710       case BlackCapturesEnPassant:
8711       case WhitePromotionChancellor:
8712       case BlackPromotionChancellor:
8713       case WhitePromotionArchbishop:
8714       case BlackPromotionArchbishop:
8715       case WhitePromotionCentaur:
8716       case BlackPromotionCentaur:
8717       case WhitePromotionQueen:
8718       case BlackPromotionQueen:
8719       case WhitePromotionRook:
8720       case BlackPromotionRook:
8721       case WhitePromotionBishop:
8722       case BlackPromotionBishop:
8723       case WhitePromotionKnight:
8724       case BlackPromotionKnight:
8725       case WhitePromotionKing:
8726       case BlackPromotionKing:
8727       case NormalMove:
8728       case WhiteKingSideCastle:
8729       case WhiteQueenSideCastle:
8730       case BlackKingSideCastle:
8731       case BlackQueenSideCastle:
8732       case WhiteKingSideCastleWild:
8733       case WhiteQueenSideCastleWild:
8734       case BlackKingSideCastleWild:
8735       case BlackQueenSideCastleWild:
8736       /* PUSH Fabien */
8737       case WhiteHSideCastleFR:
8738       case WhiteASideCastleFR:
8739       case BlackHSideCastleFR:
8740       case BlackASideCastleFR:
8741       /* POP Fabien */
8742         if (appData.debugMode)
8743           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8744         fromX = currentMoveString[0] - AAA;
8745         fromY = currentMoveString[1] - ONE;
8746         toX = currentMoveString[2] - AAA;
8747         toY = currentMoveString[3] - ONE;
8748         promoChar = currentMoveString[4];
8749         break;
8750
8751       case WhiteDrop:
8752       case BlackDrop:
8753         if (appData.debugMode)
8754           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8755         fromX = moveType == WhiteDrop ?
8756           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8757         (int) CharToPiece(ToLower(currentMoveString[0]));
8758         fromY = DROP_RANK;
8759         toX = currentMoveString[2] - AAA;
8760         toY = currentMoveString[3] - ONE;
8761         break;
8762
8763       case WhiteWins:
8764       case BlackWins:
8765       case GameIsDrawn:
8766       case GameUnfinished:
8767         if (appData.debugMode)
8768           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8769         p = strchr(yy_text, '{');
8770         if (p == NULL) p = strchr(yy_text, '(');
8771         if (p == NULL) {
8772             p = yy_text;
8773             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8774         } else {
8775             q = strchr(p, *p == '{' ? '}' : ')');
8776             if (q != NULL) *q = NULLCHAR;
8777             p++;
8778         }
8779         GameEnds(moveType, p, GE_FILE);
8780         done = TRUE;
8781         if (cmailMsgLoaded) {
8782             ClearHighlights();
8783             flipView = WhiteOnMove(currentMove);
8784             if (moveType == GameUnfinished) flipView = !flipView;
8785             if (appData.debugMode)
8786               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8787         }
8788         break;
8789
8790       case (ChessMove) 0:       /* end of file */
8791         if (appData.debugMode)
8792           fprintf(debugFP, "Parser hit end of file\n");
8793         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8794                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8795           case MT_NONE:
8796           case MT_CHECK:
8797             break;
8798           case MT_CHECKMATE:
8799           case MT_STAINMATE:
8800             if (WhiteOnMove(currentMove)) {
8801                 GameEnds(BlackWins, "Black mates", GE_FILE);
8802             } else {
8803                 GameEnds(WhiteWins, "White mates", GE_FILE);
8804             }
8805             break;
8806           case MT_STALEMATE:
8807             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8808             break;
8809         }
8810         done = TRUE;
8811         break;
8812
8813       case MoveNumberOne:
8814         if (lastLoadGameStart == GNUChessGame) {
8815             /* GNUChessGames have numbers, but they aren't move numbers */
8816             if (appData.debugMode)
8817               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8818                       yy_text, (int) moveType);
8819             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8820         }
8821         /* else fall thru */
8822
8823       case XBoardGame:
8824       case GNUChessGame:
8825       case PGNTag:
8826         /* Reached start of next game in file */
8827         if (appData.debugMode)
8828           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8829         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8830                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8831           case MT_NONE:
8832           case MT_CHECK:
8833             break;
8834           case MT_CHECKMATE:
8835           case MT_STAINMATE:
8836             if (WhiteOnMove(currentMove)) {
8837                 GameEnds(BlackWins, "Black mates", GE_FILE);
8838             } else {
8839                 GameEnds(WhiteWins, "White mates", GE_FILE);
8840             }
8841             break;
8842           case MT_STALEMATE:
8843             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8844             break;
8845         }
8846         done = TRUE;
8847         break;
8848
8849       case PositionDiagram:     /* should not happen; ignore */
8850       case ElapsedTime:         /* ignore */
8851       case NAG:                 /* ignore */
8852         if (appData.debugMode)
8853           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8854                   yy_text, (int) moveType);
8855         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8856
8857       case IllegalMove:
8858         if (appData.testLegality) {
8859             if (appData.debugMode)
8860               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8861             sprintf(move, _("Illegal move: %d.%s%s"),
8862                     (forwardMostMove / 2) + 1,
8863                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8864             DisplayError(move, 0);
8865             done = TRUE;
8866         } else {
8867             if (appData.debugMode)
8868               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8869                       yy_text, currentMoveString);
8870             fromX = currentMoveString[0] - AAA;
8871             fromY = currentMoveString[1] - ONE;
8872             toX = currentMoveString[2] - AAA;
8873             toY = currentMoveString[3] - ONE;
8874             promoChar = currentMoveString[4];
8875         }
8876         break;
8877
8878       case AmbiguousMove:
8879         if (appData.debugMode)
8880           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8881         sprintf(move, _("Ambiguous move: %d.%s%s"),
8882                 (forwardMostMove / 2) + 1,
8883                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8884         DisplayError(move, 0);
8885         done = TRUE;
8886         break;
8887
8888       default:
8889       case ImpossibleMove:
8890         if (appData.debugMode)
8891           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8892         sprintf(move, _("Illegal move: %d.%s%s"),
8893                 (forwardMostMove / 2) + 1,
8894                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8895         DisplayError(move, 0);
8896         done = TRUE;
8897         break;
8898     }
8899
8900     if (done) {
8901         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8902             DrawPosition(FALSE, boards[currentMove]);
8903             DisplayBothClocks();
8904             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8905               DisplayComment(currentMove - 1, commentList[currentMove]);
8906         }
8907         (void) StopLoadGameTimer();
8908         gameFileFP = NULL;
8909         cmailOldMove = forwardMostMove;
8910         return FALSE;
8911     } else {
8912         /* currentMoveString is set as a side-effect of yylex */
8913         strcat(currentMoveString, "\n");
8914         strcpy(moveList[forwardMostMove], currentMoveString);
8915         
8916         thinkOutput[0] = NULLCHAR;
8917         MakeMove(fromX, fromY, toX, toY, promoChar);
8918         currentMove = forwardMostMove;
8919         return TRUE;
8920     }
8921 }
8922
8923 /* Load the nth game from the given file */
8924 int
8925 LoadGameFromFile(filename, n, title, useList)
8926      char *filename;
8927      int n;
8928      char *title;
8929      /*Boolean*/ int useList;
8930 {
8931     FILE *f;
8932     char buf[MSG_SIZ];
8933
8934     if (strcmp(filename, "-") == 0) {
8935         f = stdin;
8936         title = "stdin";
8937     } else {
8938         f = fopen(filename, "rb");
8939         if (f == NULL) {
8940           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8941             DisplayError(buf, errno);
8942             return FALSE;
8943         }
8944     }
8945     if (fseek(f, 0, 0) == -1) {
8946         /* f is not seekable; probably a pipe */
8947         useList = FALSE;
8948     }
8949     if (useList && n == 0) {
8950         int error = GameListBuild(f);
8951         if (error) {
8952             DisplayError(_("Cannot build game list"), error);
8953         } else if (!ListEmpty(&gameList) &&
8954                    ((ListGame *) gameList.tailPred)->number > 1) {
8955             GameListPopUp(f, title);
8956             return TRUE;
8957         }
8958         GameListDestroy();
8959         n = 1;
8960     }
8961     if (n == 0) n = 1;
8962     return LoadGame(f, n, title, FALSE);
8963 }
8964
8965
8966 void
8967 MakeRegisteredMove()
8968 {
8969     int fromX, fromY, toX, toY;
8970     char promoChar;
8971     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8972         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8973           case CMAIL_MOVE:
8974           case CMAIL_DRAW:
8975             if (appData.debugMode)
8976               fprintf(debugFP, "Restoring %s for game %d\n",
8977                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8978     
8979             thinkOutput[0] = NULLCHAR;
8980             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8981             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8982             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8983             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8984             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8985             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8986             MakeMove(fromX, fromY, toX, toY, promoChar);
8987             ShowMove(fromX, fromY, toX, toY);
8988               
8989             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8990                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8991               case MT_NONE:
8992               case MT_CHECK:
8993                 break;
8994                 
8995               case MT_CHECKMATE:
8996               case MT_STAINMATE:
8997                 if (WhiteOnMove(currentMove)) {
8998                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8999                 } else {
9000                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9001                 }
9002                 break;
9003                 
9004               case MT_STALEMATE:
9005                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9006                 break;
9007             }
9008
9009             break;
9010             
9011           case CMAIL_RESIGN:
9012             if (WhiteOnMove(currentMove)) {
9013                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9014             } else {
9015                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9016             }
9017             break;
9018             
9019           case CMAIL_ACCEPT:
9020             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9021             break;
9022               
9023           default:
9024             break;
9025         }
9026     }
9027
9028     return;
9029 }
9030
9031 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9032 int
9033 CmailLoadGame(f, gameNumber, title, useList)
9034      FILE *f;
9035      int gameNumber;
9036      char *title;
9037      int useList;
9038 {
9039     int retVal;
9040
9041     if (gameNumber > nCmailGames) {
9042         DisplayError(_("No more games in this message"), 0);
9043         return FALSE;
9044     }
9045     if (f == lastLoadGameFP) {
9046         int offset = gameNumber - lastLoadGameNumber;
9047         if (offset == 0) {
9048             cmailMsg[0] = NULLCHAR;
9049             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9050                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9051                 nCmailMovesRegistered--;
9052             }
9053             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9054             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9055                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9056             }
9057         } else {
9058             if (! RegisterMove()) return FALSE;
9059         }
9060     }
9061
9062     retVal = LoadGame(f, gameNumber, title, useList);
9063
9064     /* Make move registered during previous look at this game, if any */
9065     MakeRegisteredMove();
9066
9067     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9068         commentList[currentMove]
9069           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9070         DisplayComment(currentMove - 1, commentList[currentMove]);
9071     }
9072
9073     return retVal;
9074 }
9075
9076 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9077 int
9078 ReloadGame(offset)
9079      int offset;
9080 {
9081     int gameNumber = lastLoadGameNumber + offset;
9082     if (lastLoadGameFP == NULL) {
9083         DisplayError(_("No game has been loaded yet"), 0);
9084         return FALSE;
9085     }
9086     if (gameNumber <= 0) {
9087         DisplayError(_("Can't back up any further"), 0);
9088         return FALSE;
9089     }
9090     if (cmailMsgLoaded) {
9091         return CmailLoadGame(lastLoadGameFP, gameNumber,
9092                              lastLoadGameTitle, lastLoadGameUseList);
9093     } else {
9094         return LoadGame(lastLoadGameFP, gameNumber,
9095                         lastLoadGameTitle, lastLoadGameUseList);
9096     }
9097 }
9098
9099
9100
9101 /* Load the nth game from open file f */
9102 int
9103 LoadGame(f, gameNumber, title, useList)
9104      FILE *f;
9105      int gameNumber;
9106      char *title;
9107      int useList;
9108 {
9109     ChessMove cm;
9110     char buf[MSG_SIZ];
9111     int gn = gameNumber;
9112     ListGame *lg = NULL;
9113     int numPGNTags = 0;
9114     int err;
9115     GameMode oldGameMode;
9116     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9117
9118     if (appData.debugMode) 
9119         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9120
9121     if (gameMode == Training )
9122         SetTrainingModeOff();
9123
9124     oldGameMode = gameMode;
9125     if (gameMode != BeginningOfGame) {
9126       Reset(FALSE, TRUE);
9127     }
9128
9129     gameFileFP = f;
9130     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9131         fclose(lastLoadGameFP);
9132     }
9133
9134     if (useList) {
9135         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9136         
9137         if (lg) {
9138             fseek(f, lg->offset, 0);
9139             GameListHighlight(gameNumber);
9140             gn = 1;
9141         }
9142         else {
9143             DisplayError(_("Game number out of range"), 0);
9144             return FALSE;
9145         }
9146     } else {
9147         GameListDestroy();
9148         if (fseek(f, 0, 0) == -1) {
9149             if (f == lastLoadGameFP ?
9150                 gameNumber == lastLoadGameNumber + 1 :
9151                 gameNumber == 1) {
9152                 gn = 1;
9153             } else {
9154                 DisplayError(_("Can't seek on game file"), 0);
9155                 return FALSE;
9156             }
9157         }
9158     }
9159     lastLoadGameFP = f;
9160     lastLoadGameNumber = gameNumber;
9161     strcpy(lastLoadGameTitle, title);
9162     lastLoadGameUseList = useList;
9163
9164     yynewfile(f);
9165
9166     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9167       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9168                 lg->gameInfo.black);
9169             DisplayTitle(buf);
9170     } else if (*title != NULLCHAR) {
9171         if (gameNumber > 1) {
9172             sprintf(buf, "%s %d", title, gameNumber);
9173             DisplayTitle(buf);
9174         } else {
9175             DisplayTitle(title);
9176         }
9177     }
9178
9179     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9180         gameMode = PlayFromGameFile;
9181         ModeHighlight();
9182     }
9183
9184     currentMove = forwardMostMove = backwardMostMove = 0;
9185     CopyBoard(boards[0], initialPosition);
9186     StopClocks();
9187
9188     /*
9189      * Skip the first gn-1 games in the file.
9190      * Also skip over anything that precedes an identifiable 
9191      * start of game marker, to avoid being confused by 
9192      * garbage at the start of the file.  Currently 
9193      * recognized start of game markers are the move number "1",
9194      * the pattern "gnuchess .* game", the pattern
9195      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9196      * A game that starts with one of the latter two patterns
9197      * will also have a move number 1, possibly
9198      * following a position diagram.
9199      * 5-4-02: Let's try being more lenient and allowing a game to
9200      * start with an unnumbered move.  Does that break anything?
9201      */
9202     cm = lastLoadGameStart = (ChessMove) 0;
9203     while (gn > 0) {
9204         yyboardindex = forwardMostMove;
9205         cm = (ChessMove) yylex();
9206         switch (cm) {
9207           case (ChessMove) 0:
9208             if (cmailMsgLoaded) {
9209                 nCmailGames = CMAIL_MAX_GAMES - gn;
9210             } else {
9211                 Reset(TRUE, TRUE);
9212                 DisplayError(_("Game not found in file"), 0);
9213             }
9214             return FALSE;
9215
9216           case GNUChessGame:
9217           case XBoardGame:
9218             gn--;
9219             lastLoadGameStart = cm;
9220             break;
9221             
9222           case MoveNumberOne:
9223             switch (lastLoadGameStart) {
9224               case GNUChessGame:
9225               case XBoardGame:
9226               case PGNTag:
9227                 break;
9228               case MoveNumberOne:
9229               case (ChessMove) 0:
9230                 gn--;           /* count this game */
9231                 lastLoadGameStart = cm;
9232                 break;
9233               default:
9234                 /* impossible */
9235                 break;
9236             }
9237             break;
9238
9239           case PGNTag:
9240             switch (lastLoadGameStart) {
9241               case GNUChessGame:
9242               case PGNTag:
9243               case MoveNumberOne:
9244               case (ChessMove) 0:
9245                 gn--;           /* count this game */
9246                 lastLoadGameStart = cm;
9247                 break;
9248               case XBoardGame:
9249                 lastLoadGameStart = cm; /* game counted already */
9250                 break;
9251               default:
9252                 /* impossible */
9253                 break;
9254             }
9255             if (gn > 0) {
9256                 do {
9257                     yyboardindex = forwardMostMove;
9258                     cm = (ChessMove) yylex();
9259                 } while (cm == PGNTag || cm == Comment);
9260             }
9261             break;
9262
9263           case WhiteWins:
9264           case BlackWins:
9265           case GameIsDrawn:
9266             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9267                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9268                     != CMAIL_OLD_RESULT) {
9269                     nCmailResults ++ ;
9270                     cmailResult[  CMAIL_MAX_GAMES
9271                                 - gn - 1] = CMAIL_OLD_RESULT;
9272                 }
9273             }
9274             break;
9275
9276           case NormalMove:
9277             /* Only a NormalMove can be at the start of a game
9278              * without a position diagram. */
9279             if (lastLoadGameStart == (ChessMove) 0) {
9280               gn--;
9281               lastLoadGameStart = MoveNumberOne;
9282             }
9283             break;
9284
9285           default:
9286             break;
9287         }
9288     }
9289     
9290     if (appData.debugMode)
9291       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9292
9293     if (cm == XBoardGame) {
9294         /* Skip any header junk before position diagram and/or move 1 */
9295         for (;;) {
9296             yyboardindex = forwardMostMove;
9297             cm = (ChessMove) yylex();
9298
9299             if (cm == (ChessMove) 0 ||
9300                 cm == GNUChessGame || cm == XBoardGame) {
9301                 /* Empty game; pretend end-of-file and handle later */
9302                 cm = (ChessMove) 0;
9303                 break;
9304             }
9305
9306             if (cm == MoveNumberOne || cm == PositionDiagram ||
9307                 cm == PGNTag || cm == Comment)
9308               break;
9309         }
9310     } else if (cm == GNUChessGame) {
9311         if (gameInfo.event != NULL) {
9312             free(gameInfo.event);
9313         }
9314         gameInfo.event = StrSave(yy_text);
9315     }   
9316
9317     startedFromSetupPosition = FALSE;
9318     while (cm == PGNTag) {
9319         if (appData.debugMode) 
9320           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9321         err = ParsePGNTag(yy_text, &gameInfo);
9322         if (!err) numPGNTags++;
9323
9324         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9325         if(gameInfo.variant != oldVariant) {
9326             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9327             InitPosition(TRUE);
9328             oldVariant = gameInfo.variant;
9329             if (appData.debugMode) 
9330               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9331         }
9332
9333
9334         if (gameInfo.fen != NULL) {
9335           Board initial_position;
9336           startedFromSetupPosition = TRUE;
9337           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9338             Reset(TRUE, TRUE);
9339             DisplayError(_("Bad FEN position in file"), 0);
9340             return FALSE;
9341           }
9342           CopyBoard(boards[0], initial_position);
9343           if (blackPlaysFirst) {
9344             currentMove = forwardMostMove = backwardMostMove = 1;
9345             CopyBoard(boards[1], initial_position);
9346             strcpy(moveList[0], "");
9347             strcpy(parseList[0], "");
9348             timeRemaining[0][1] = whiteTimeRemaining;
9349             timeRemaining[1][1] = blackTimeRemaining;
9350             if (commentList[0] != NULL) {
9351               commentList[1] = commentList[0];
9352               commentList[0] = NULL;
9353             }
9354           } else {
9355             currentMove = forwardMostMove = backwardMostMove = 0;
9356           }
9357           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9358           {   int i;
9359               initialRulePlies = FENrulePlies;
9360               epStatus[forwardMostMove] = FENepStatus;
9361               for( i=0; i< nrCastlingRights; i++ )
9362                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9363           }
9364           yyboardindex = forwardMostMove;
9365           free(gameInfo.fen);
9366           gameInfo.fen = NULL;
9367         }
9368
9369         yyboardindex = forwardMostMove;
9370         cm = (ChessMove) yylex();
9371
9372         /* Handle comments interspersed among the tags */
9373         while (cm == Comment) {
9374             char *p;
9375             if (appData.debugMode) 
9376               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9377             p = yy_text;
9378             if (*p == '{' || *p == '[' || *p == '(') {
9379                 p[strlen(p) - 1] = NULLCHAR;
9380                 p++;
9381             }
9382             while (*p == '\n') p++;
9383             AppendComment(currentMove, p);
9384             yyboardindex = forwardMostMove;
9385             cm = (ChessMove) yylex();
9386         }
9387     }
9388
9389     /* don't rely on existence of Event tag since if game was
9390      * pasted from clipboard the Event tag may not exist
9391      */
9392     if (numPGNTags > 0){
9393         char *tags;
9394         if (gameInfo.variant == VariantNormal) {
9395           gameInfo.variant = StringToVariant(gameInfo.event);
9396         }
9397         if (!matchMode) {
9398           if( appData.autoDisplayTags ) {
9399             tags = PGNTags(&gameInfo);
9400             TagsPopUp(tags, CmailMsg());
9401             free(tags);
9402           }
9403         }
9404     } else {
9405         /* Make something up, but don't display it now */
9406         SetGameInfo();
9407         TagsPopDown();
9408     }
9409
9410     if (cm == PositionDiagram) {
9411         int i, j;
9412         char *p;
9413         Board initial_position;
9414
9415         if (appData.debugMode)
9416           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9417
9418         if (!startedFromSetupPosition) {
9419             p = yy_text;
9420             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9421               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9422                 switch (*p) {
9423                   case '[':
9424                   case '-':
9425                   case ' ':
9426                   case '\t':
9427                   case '\n':
9428                   case '\r':
9429                     break;
9430                   default:
9431                     initial_position[i][j++] = CharToPiece(*p);
9432                     break;
9433                 }
9434             while (*p == ' ' || *p == '\t' ||
9435                    *p == '\n' || *p == '\r') p++;
9436         
9437             if (strncmp(p, "black", strlen("black"))==0)
9438               blackPlaysFirst = TRUE;
9439             else
9440               blackPlaysFirst = FALSE;
9441             startedFromSetupPosition = TRUE;
9442         
9443             CopyBoard(boards[0], initial_position);
9444             if (blackPlaysFirst) {
9445                 currentMove = forwardMostMove = backwardMostMove = 1;
9446                 CopyBoard(boards[1], initial_position);
9447                 strcpy(moveList[0], "");
9448                 strcpy(parseList[0], "");
9449                 timeRemaining[0][1] = whiteTimeRemaining;
9450                 timeRemaining[1][1] = blackTimeRemaining;
9451                 if (commentList[0] != NULL) {
9452                     commentList[1] = commentList[0];
9453                     commentList[0] = NULL;
9454                 }
9455             } else {
9456                 currentMove = forwardMostMove = backwardMostMove = 0;
9457             }
9458         }
9459         yyboardindex = forwardMostMove;
9460         cm = (ChessMove) yylex();
9461     }
9462
9463     if (first.pr == NoProc) {
9464         StartChessProgram(&first);
9465     }
9466     InitChessProgram(&first, FALSE);
9467     SendToProgram("force\n", &first);
9468     if (startedFromSetupPosition) {
9469         SendBoard(&first, forwardMostMove);
9470     if (appData.debugMode) {
9471         fprintf(debugFP, "Load Game\n");
9472     }
9473         DisplayBothClocks();
9474     }      
9475
9476     /* [HGM] server: flag to write setup moves in broadcast file as one */
9477     loadFlag = appData.suppressLoadMoves;
9478
9479     while (cm == Comment) {
9480         char *p;
9481         if (appData.debugMode) 
9482           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9483         p = yy_text;
9484         if (*p == '{' || *p == '[' || *p == '(') {
9485             p[strlen(p) - 1] = NULLCHAR;
9486             p++;
9487         }
9488         while (*p == '\n') p++;
9489         AppendComment(currentMove, p);
9490         yyboardindex = forwardMostMove;
9491         cm = (ChessMove) yylex();
9492     }
9493
9494     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9495         cm == WhiteWins || cm == BlackWins ||
9496         cm == GameIsDrawn || cm == GameUnfinished) {
9497         DisplayMessage("", _("No moves in game"));
9498         if (cmailMsgLoaded) {
9499             if (appData.debugMode)
9500               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9501             ClearHighlights();
9502             flipView = FALSE;
9503         }
9504         DrawPosition(FALSE, boards[currentMove]);
9505         DisplayBothClocks();
9506         gameMode = EditGame;
9507         ModeHighlight();
9508         gameFileFP = NULL;
9509         cmailOldMove = 0;
9510         return TRUE;
9511     }
9512
9513     // [HGM] PV info: routine tests if comment empty
9514     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9515         DisplayComment(currentMove - 1, commentList[currentMove]);
9516     }
9517     if (!matchMode && appData.timeDelay != 0) 
9518       DrawPosition(FALSE, boards[currentMove]);
9519
9520     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9521       programStats.ok_to_send = 1;
9522     }
9523
9524     /* if the first token after the PGN tags is a move
9525      * and not move number 1, retrieve it from the parser 
9526      */
9527     if (cm != MoveNumberOne)
9528         LoadGameOneMove(cm);
9529
9530     /* load the remaining moves from the file */
9531     while (LoadGameOneMove((ChessMove)0)) {
9532       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9533       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9534     }
9535
9536     /* rewind to the start of the game */
9537     currentMove = backwardMostMove;
9538
9539     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9540
9541     if (oldGameMode == AnalyzeFile ||
9542         oldGameMode == AnalyzeMode) {
9543       AnalyzeFileEvent();
9544     }
9545
9546     if (matchMode || appData.timeDelay == 0) {
9547       ToEndEvent();
9548       gameMode = EditGame;
9549       ModeHighlight();
9550     } else if (appData.timeDelay > 0) {
9551       AutoPlayGameLoop();
9552     }
9553
9554     if (appData.debugMode) 
9555         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9556
9557     loadFlag = 0; /* [HGM] true game starts */
9558     return TRUE;
9559 }
9560
9561 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9562 int
9563 ReloadPosition(offset)
9564      int offset;
9565 {
9566     int positionNumber = lastLoadPositionNumber + offset;
9567     if (lastLoadPositionFP == NULL) {
9568         DisplayError(_("No position has been loaded yet"), 0);
9569         return FALSE;
9570     }
9571     if (positionNumber <= 0) {
9572         DisplayError(_("Can't back up any further"), 0);
9573         return FALSE;
9574     }
9575     return LoadPosition(lastLoadPositionFP, positionNumber,
9576                         lastLoadPositionTitle);
9577 }
9578
9579 /* Load the nth position from the given file */
9580 int
9581 LoadPositionFromFile(filename, n, title)
9582      char *filename;
9583      int n;
9584      char *title;
9585 {
9586     FILE *f;
9587     char buf[MSG_SIZ];
9588
9589     if (strcmp(filename, "-") == 0) {
9590         return LoadPosition(stdin, n, "stdin");
9591     } else {
9592         f = fopen(filename, "rb");
9593         if (f == NULL) {
9594             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9595             DisplayError(buf, errno);
9596             return FALSE;
9597         } else {
9598             return LoadPosition(f, n, title);
9599         }
9600     }
9601 }
9602
9603 /* Load the nth position from the given open file, and close it */
9604 int
9605 LoadPosition(f, positionNumber, title)
9606      FILE *f;
9607      int positionNumber;
9608      char *title;
9609 {
9610     char *p, line[MSG_SIZ];
9611     Board initial_position;
9612     int i, j, fenMode, pn;
9613     
9614     if (gameMode == Training )
9615         SetTrainingModeOff();
9616
9617     if (gameMode != BeginningOfGame) {
9618         Reset(FALSE, TRUE);
9619     }
9620     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9621         fclose(lastLoadPositionFP);
9622     }
9623     if (positionNumber == 0) positionNumber = 1;
9624     lastLoadPositionFP = f;
9625     lastLoadPositionNumber = positionNumber;
9626     strcpy(lastLoadPositionTitle, title);
9627     if (first.pr == NoProc) {
9628       StartChessProgram(&first);
9629       InitChessProgram(&first, FALSE);
9630     }    
9631     pn = positionNumber;
9632     if (positionNumber < 0) {
9633         /* Negative position number means to seek to that byte offset */
9634         if (fseek(f, -positionNumber, 0) == -1) {
9635             DisplayError(_("Can't seek on position file"), 0);
9636             return FALSE;
9637         };
9638         pn = 1;
9639     } else {
9640         if (fseek(f, 0, 0) == -1) {
9641             if (f == lastLoadPositionFP ?
9642                 positionNumber == lastLoadPositionNumber + 1 :
9643                 positionNumber == 1) {
9644                 pn = 1;
9645             } else {
9646                 DisplayError(_("Can't seek on position file"), 0);
9647                 return FALSE;
9648             }
9649         }
9650     }
9651     /* See if this file is FEN or old-style xboard */
9652     if (fgets(line, MSG_SIZ, f) == NULL) {
9653         DisplayError(_("Position not found in file"), 0);
9654         return FALSE;
9655     }
9656     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9657     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9658
9659     if (pn >= 2) {
9660         if (fenMode || line[0] == '#') pn--;
9661         while (pn > 0) {
9662             /* skip positions before number pn */
9663             if (fgets(line, MSG_SIZ, f) == NULL) {
9664                 Reset(TRUE, TRUE);
9665                 DisplayError(_("Position not found in file"), 0);
9666                 return FALSE;
9667             }
9668             if (fenMode || line[0] == '#') pn--;
9669         }
9670     }
9671
9672     if (fenMode) {
9673         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9674             DisplayError(_("Bad FEN position in file"), 0);
9675             return FALSE;
9676         }
9677     } else {
9678         (void) fgets(line, MSG_SIZ, f);
9679         (void) fgets(line, MSG_SIZ, f);
9680     
9681         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9682             (void) fgets(line, MSG_SIZ, f);
9683             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9684                 if (*p == ' ')
9685                   continue;
9686                 initial_position[i][j++] = CharToPiece(*p);
9687             }
9688         }
9689     
9690         blackPlaysFirst = FALSE;
9691         if (!feof(f)) {
9692             (void) fgets(line, MSG_SIZ, f);
9693             if (strncmp(line, "black", strlen("black"))==0)
9694               blackPlaysFirst = TRUE;
9695         }
9696     }
9697     startedFromSetupPosition = TRUE;
9698     
9699     SendToProgram("force\n", &first);
9700     CopyBoard(boards[0], initial_position);
9701     if (blackPlaysFirst) {
9702         currentMove = forwardMostMove = backwardMostMove = 1;
9703         strcpy(moveList[0], "");
9704         strcpy(parseList[0], "");
9705         CopyBoard(boards[1], initial_position);
9706         DisplayMessage("", _("Black to play"));
9707     } else {
9708         currentMove = forwardMostMove = backwardMostMove = 0;
9709         DisplayMessage("", _("White to play"));
9710     }
9711           /* [HGM] copy FEN attributes as well */
9712           {   int i;
9713               initialRulePlies = FENrulePlies;
9714               epStatus[forwardMostMove] = FENepStatus;
9715               for( i=0; i< nrCastlingRights; i++ )
9716                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9717           }
9718     SendBoard(&first, forwardMostMove);
9719     if (appData.debugMode) {
9720 int i, j;
9721   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9722   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9723         fprintf(debugFP, "Load Position\n");
9724     }
9725
9726     if (positionNumber > 1) {
9727         sprintf(line, "%s %d", title, positionNumber);
9728         DisplayTitle(line);
9729     } else {
9730         DisplayTitle(title);
9731     }
9732     gameMode = EditGame;
9733     ModeHighlight();
9734     ResetClocks();
9735     timeRemaining[0][1] = whiteTimeRemaining;
9736     timeRemaining[1][1] = blackTimeRemaining;
9737     DrawPosition(FALSE, boards[currentMove]);
9738    
9739     return TRUE;
9740 }
9741
9742
9743 void
9744 CopyPlayerNameIntoFileName(dest, src)
9745      char **dest, *src;
9746 {
9747     while (*src != NULLCHAR && *src != ',') {
9748         if (*src == ' ') {
9749             *(*dest)++ = '_';
9750             src++;
9751         } else {
9752             *(*dest)++ = *src++;
9753         }
9754     }
9755 }
9756
9757 char *DefaultFileName(ext)
9758      char *ext;
9759 {
9760     static char def[MSG_SIZ];
9761     char *p;
9762
9763     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9764         p = def;
9765         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9766         *p++ = '-';
9767         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9768         *p++ = '.';
9769         strcpy(p, ext);
9770     } else {
9771         def[0] = NULLCHAR;
9772     }
9773     return def;
9774 }
9775
9776 /* Save the current game to the given file */
9777 int
9778 SaveGameToFile(filename, append)
9779      char *filename;
9780      int append;
9781 {
9782     FILE *f;
9783     char buf[MSG_SIZ];
9784
9785     if (strcmp(filename, "-") == 0) {
9786         return SaveGame(stdout, 0, NULL);
9787     } else {
9788         f = fopen(filename, append ? "a" : "w");
9789         if (f == NULL) {
9790             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9791             DisplayError(buf, errno);
9792             return FALSE;
9793         } else {
9794             return SaveGame(f, 0, NULL);
9795         }
9796     }
9797 }
9798
9799 char *
9800 SavePart(str)
9801      char *str;
9802 {
9803     static char buf[MSG_SIZ];
9804     char *p;
9805     
9806     p = strchr(str, ' ');
9807     if (p == NULL) return str;
9808     strncpy(buf, str, p - str);
9809     buf[p - str] = NULLCHAR;
9810     return buf;
9811 }
9812
9813 #define PGN_MAX_LINE 75
9814
9815 #define PGN_SIDE_WHITE  0
9816 #define PGN_SIDE_BLACK  1
9817
9818 /* [AS] */
9819 static int FindFirstMoveOutOfBook( int side )
9820 {
9821     int result = -1;
9822
9823     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9824         int index = backwardMostMove;
9825         int has_book_hit = 0;
9826
9827         if( (index % 2) != side ) {
9828             index++;
9829         }
9830
9831         while( index < forwardMostMove ) {
9832             /* Check to see if engine is in book */
9833             int depth = pvInfoList[index].depth;
9834             int score = pvInfoList[index].score;
9835             int in_book = 0;
9836
9837             if( depth <= 2 ) {
9838                 in_book = 1;
9839             }
9840             else if( score == 0 && depth == 63 ) {
9841                 in_book = 1; /* Zappa */
9842             }
9843             else if( score == 2 && depth == 99 ) {
9844                 in_book = 1; /* Abrok */
9845             }
9846
9847             has_book_hit += in_book;
9848
9849             if( ! in_book ) {
9850                 result = index;
9851
9852                 break;
9853             }
9854
9855             index += 2;
9856         }
9857     }
9858
9859     return result;
9860 }
9861
9862 /* [AS] */
9863 void GetOutOfBookInfo( char * buf )
9864 {
9865     int oob[2];
9866     int i;
9867     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9868
9869     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9870     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9871
9872     *buf = '\0';
9873
9874     if( oob[0] >= 0 || oob[1] >= 0 ) {
9875         for( i=0; i<2; i++ ) {
9876             int idx = oob[i];
9877
9878             if( idx >= 0 ) {
9879                 if( i > 0 && oob[0] >= 0 ) {
9880                     strcat( buf, "   " );
9881                 }
9882
9883                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9884                 sprintf( buf+strlen(buf), "%s%.2f", 
9885                     pvInfoList[idx].score >= 0 ? "+" : "",
9886                     pvInfoList[idx].score / 100.0 );
9887             }
9888         }
9889     }
9890 }
9891
9892 /* Save game in PGN style and close the file */
9893 int
9894 SaveGamePGN(f)
9895      FILE *f;
9896 {
9897     int i, offset, linelen, newblock;
9898     time_t tm;
9899 //    char *movetext;
9900     char numtext[32];
9901     int movelen, numlen, blank;
9902     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9903
9904     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9905     
9906     tm = time((time_t *) NULL);
9907     
9908     PrintPGNTags(f, &gameInfo);
9909     
9910     if (backwardMostMove > 0 || startedFromSetupPosition) {
9911         char *fen = PositionToFEN(backwardMostMove, NULL);
9912         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9913         fprintf(f, "\n{--------------\n");
9914         PrintPosition(f, backwardMostMove);
9915         fprintf(f, "--------------}\n");
9916         free(fen);
9917     }
9918     else {
9919         /* [AS] Out of book annotation */
9920         if( appData.saveOutOfBookInfo ) {
9921             char buf[64];
9922
9923             GetOutOfBookInfo( buf );
9924
9925             if( buf[0] != '\0' ) {
9926                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9927             }
9928         }
9929
9930         fprintf(f, "\n");
9931     }
9932
9933     i = backwardMostMove;
9934     linelen = 0;
9935     newblock = TRUE;
9936
9937     while (i < forwardMostMove) {
9938         /* Print comments preceding this move */
9939         if (commentList[i] != NULL) {
9940             if (linelen > 0) fprintf(f, "\n");
9941             fprintf(f, "{\n%s}\n", commentList[i]);
9942             linelen = 0;
9943             newblock = TRUE;
9944         }
9945
9946         /* Format move number */
9947         if ((i % 2) == 0) {
9948             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9949         } else {
9950             if (newblock) {
9951                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9952             } else {
9953                 numtext[0] = NULLCHAR;
9954             }
9955         }
9956         numlen = strlen(numtext);
9957         newblock = FALSE;
9958
9959         /* Print move number */
9960         blank = linelen > 0 && numlen > 0;
9961         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9962             fprintf(f, "\n");
9963             linelen = 0;
9964             blank = 0;
9965         }
9966         if (blank) {
9967             fprintf(f, " ");
9968             linelen++;
9969         }
9970         fprintf(f, "%s", numtext);
9971         linelen += numlen;
9972
9973         /* Get move */
9974         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9975         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9976
9977         /* Print move */
9978         blank = linelen > 0 && movelen > 0;
9979         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9980             fprintf(f, "\n");
9981             linelen = 0;
9982             blank = 0;
9983         }
9984         if (blank) {
9985             fprintf(f, " ");
9986             linelen++;
9987         }
9988         fprintf(f, "%s", move_buffer);
9989         linelen += movelen;
9990
9991         /* [AS] Add PV info if present */
9992         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9993             /* [HGM] add time */
9994             char buf[MSG_SIZ]; int seconds;
9995
9996             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9997
9998             if( seconds <= 0) buf[0] = 0; else
9999             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10000                 seconds = (seconds + 4)/10; // round to full seconds
10001                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10002                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10003             }
10004
10005             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10006                 pvInfoList[i].score >= 0 ? "+" : "",
10007                 pvInfoList[i].score / 100.0,
10008                 pvInfoList[i].depth,
10009                 buf );
10010
10011             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10012
10013             /* Print score/depth */
10014             blank = linelen > 0 && movelen > 0;
10015             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10016                 fprintf(f, "\n");
10017                 linelen = 0;
10018                 blank = 0;
10019             }
10020             if (blank) {
10021                 fprintf(f, " ");
10022                 linelen++;
10023             }
10024             fprintf(f, "%s", move_buffer);
10025             linelen += movelen;
10026         }
10027
10028         i++;
10029     }
10030     
10031     /* Start a new line */
10032     if (linelen > 0) fprintf(f, "\n");
10033
10034     /* Print comments after last move */
10035     if (commentList[i] != NULL) {
10036         fprintf(f, "{\n%s}\n", commentList[i]);
10037     }
10038
10039     /* Print result */
10040     if (gameInfo.resultDetails != NULL &&
10041         gameInfo.resultDetails[0] != NULLCHAR) {
10042         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10043                 PGNResult(gameInfo.result));
10044     } else {
10045         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10046     }
10047
10048     fclose(f);
10049     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10050     return TRUE;
10051 }
10052
10053 /* Save game in old style and close the file */
10054 int
10055 SaveGameOldStyle(f)
10056      FILE *f;
10057 {
10058     int i, offset;
10059     time_t tm;
10060     
10061     tm = time((time_t *) NULL);
10062     
10063     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10064     PrintOpponents(f);
10065     
10066     if (backwardMostMove > 0 || startedFromSetupPosition) {
10067         fprintf(f, "\n[--------------\n");
10068         PrintPosition(f, backwardMostMove);
10069         fprintf(f, "--------------]\n");
10070     } else {
10071         fprintf(f, "\n");
10072     }
10073
10074     i = backwardMostMove;
10075     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10076
10077     while (i < forwardMostMove) {
10078         if (commentList[i] != NULL) {
10079             fprintf(f, "[%s]\n", commentList[i]);
10080         }
10081
10082         if ((i % 2) == 1) {
10083             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10084             i++;
10085         } else {
10086             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10087             i++;
10088             if (commentList[i] != NULL) {
10089                 fprintf(f, "\n");
10090                 continue;
10091             }
10092             if (i >= forwardMostMove) {
10093                 fprintf(f, "\n");
10094                 break;
10095             }
10096             fprintf(f, "%s\n", parseList[i]);
10097             i++;
10098         }
10099     }
10100     
10101     if (commentList[i] != NULL) {
10102         fprintf(f, "[%s]\n", commentList[i]);
10103     }
10104
10105     /* This isn't really the old style, but it's close enough */
10106     if (gameInfo.resultDetails != NULL &&
10107         gameInfo.resultDetails[0] != NULLCHAR) {
10108         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10109                 gameInfo.resultDetails);
10110     } else {
10111         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10112     }
10113
10114     fclose(f);
10115     return TRUE;
10116 }
10117
10118 /* Save the current game to open file f and close the file */
10119 int
10120 SaveGame(f, dummy, dummy2)
10121      FILE *f;
10122      int dummy;
10123      char *dummy2;
10124 {
10125     if (gameMode == EditPosition) EditPositionDone(TRUE);
10126     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10127     if (appData.oldSaveStyle)
10128       return SaveGameOldStyle(f);
10129     else
10130       return SaveGamePGN(f);
10131 }
10132
10133 /* Save the current position to the given file */
10134 int
10135 SavePositionToFile(filename)
10136      char *filename;
10137 {
10138     FILE *f;
10139     char buf[MSG_SIZ];
10140
10141     if (strcmp(filename, "-") == 0) {
10142         return SavePosition(stdout, 0, NULL);
10143     } else {
10144         f = fopen(filename, "a");
10145         if (f == NULL) {
10146             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10147             DisplayError(buf, errno);
10148             return FALSE;
10149         } else {
10150             SavePosition(f, 0, NULL);
10151             return TRUE;
10152         }
10153     }
10154 }
10155
10156 /* Save the current position to the given open file and close the file */
10157 int
10158 SavePosition(f, dummy, dummy2)
10159      FILE *f;
10160      int dummy;
10161      char *dummy2;
10162 {
10163     time_t tm;
10164     char *fen;
10165
10166     if (gameMode == EditPosition) EditPositionDone(TRUE);
10167     if (appData.oldSaveStyle) {
10168         tm = time((time_t *) NULL);
10169     
10170         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10171         PrintOpponents(f);
10172         fprintf(f, "[--------------\n");
10173         PrintPosition(f, currentMove);
10174         fprintf(f, "--------------]\n");
10175     } else {
10176         fen = PositionToFEN(currentMove, NULL);
10177         fprintf(f, "%s\n", fen);
10178         free(fen);
10179     }
10180     fclose(f);
10181     return TRUE;
10182 }
10183
10184 void
10185 ReloadCmailMsgEvent(unregister)
10186      int unregister;
10187 {
10188 #if !WIN32
10189     static char *inFilename = NULL;
10190     static char *outFilename;
10191     int i;
10192     struct stat inbuf, outbuf;
10193     int status;
10194     
10195     /* Any registered moves are unregistered if unregister is set, */
10196     /* i.e. invoked by the signal handler */
10197     if (unregister) {
10198         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10199             cmailMoveRegistered[i] = FALSE;
10200             if (cmailCommentList[i] != NULL) {
10201                 free(cmailCommentList[i]);
10202                 cmailCommentList[i] = NULL;
10203             }
10204         }
10205         nCmailMovesRegistered = 0;
10206     }
10207
10208     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10209         cmailResult[i] = CMAIL_NOT_RESULT;
10210     }
10211     nCmailResults = 0;
10212
10213     if (inFilename == NULL) {
10214         /* Because the filenames are static they only get malloced once  */
10215         /* and they never get freed                                      */
10216         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10217         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10218
10219         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10220         sprintf(outFilename, "%s.out", appData.cmailGameName);
10221     }
10222     
10223     status = stat(outFilename, &outbuf);
10224     if (status < 0) {
10225         cmailMailedMove = FALSE;
10226     } else {
10227         status = stat(inFilename, &inbuf);
10228         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10229     }
10230     
10231     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10232        counts the games, notes how each one terminated, etc.
10233        
10234        It would be nice to remove this kludge and instead gather all
10235        the information while building the game list.  (And to keep it
10236        in the game list nodes instead of having a bunch of fixed-size
10237        parallel arrays.)  Note this will require getting each game's
10238        termination from the PGN tags, as the game list builder does
10239        not process the game moves.  --mann
10240        */
10241     cmailMsgLoaded = TRUE;
10242     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10243     
10244     /* Load first game in the file or popup game menu */
10245     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10246
10247 #endif /* !WIN32 */
10248     return;
10249 }
10250
10251 int
10252 RegisterMove()
10253 {
10254     FILE *f;
10255     char string[MSG_SIZ];
10256
10257     if (   cmailMailedMove
10258         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10259         return TRUE;            /* Allow free viewing  */
10260     }
10261
10262     /* Unregister move to ensure that we don't leave RegisterMove        */
10263     /* with the move registered when the conditions for registering no   */
10264     /* longer hold                                                       */
10265     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10266         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10267         nCmailMovesRegistered --;
10268
10269         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10270           {
10271               free(cmailCommentList[lastLoadGameNumber - 1]);
10272               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10273           }
10274     }
10275
10276     if (cmailOldMove == -1) {
10277         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10278         return FALSE;
10279     }
10280
10281     if (currentMove > cmailOldMove + 1) {
10282         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10283         return FALSE;
10284     }
10285
10286     if (currentMove < cmailOldMove) {
10287         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10288         return FALSE;
10289     }
10290
10291     if (forwardMostMove > currentMove) {
10292         /* Silently truncate extra moves */
10293         TruncateGame();
10294     }
10295
10296     if (   (currentMove == cmailOldMove + 1)
10297         || (   (currentMove == cmailOldMove)
10298             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10299                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10300         if (gameInfo.result != GameUnfinished) {
10301             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10302         }
10303
10304         if (commentList[currentMove] != NULL) {
10305             cmailCommentList[lastLoadGameNumber - 1]
10306               = StrSave(commentList[currentMove]);
10307         }
10308         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10309
10310         if (appData.debugMode)
10311           fprintf(debugFP, "Saving %s for game %d\n",
10312                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10313
10314         sprintf(string,
10315                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10316         
10317         f = fopen(string, "w");
10318         if (appData.oldSaveStyle) {
10319             SaveGameOldStyle(f); /* also closes the file */
10320             
10321             sprintf(string, "%s.pos.out", appData.cmailGameName);
10322             f = fopen(string, "w");
10323             SavePosition(f, 0, NULL); /* also closes the file */
10324         } else {
10325             fprintf(f, "{--------------\n");
10326             PrintPosition(f, currentMove);
10327             fprintf(f, "--------------}\n\n");
10328             
10329             SaveGame(f, 0, NULL); /* also closes the file*/
10330         }
10331         
10332         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10333         nCmailMovesRegistered ++;
10334     } else if (nCmailGames == 1) {
10335         DisplayError(_("You have not made a move yet"), 0);
10336         return FALSE;
10337     }
10338
10339     return TRUE;
10340 }
10341
10342 void
10343 MailMoveEvent()
10344 {
10345 #if !WIN32
10346     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10347     FILE *commandOutput;
10348     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10349     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10350     int nBuffers;
10351     int i;
10352     int archived;
10353     char *arcDir;
10354
10355     if (! cmailMsgLoaded) {
10356         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10357         return;
10358     }
10359
10360     if (nCmailGames == nCmailResults) {
10361         DisplayError(_("No unfinished games"), 0);
10362         return;
10363     }
10364
10365 #if CMAIL_PROHIBIT_REMAIL
10366     if (cmailMailedMove) {
10367         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);
10368         DisplayError(msg, 0);
10369         return;
10370     }
10371 #endif
10372
10373     if (! (cmailMailedMove || RegisterMove())) return;
10374     
10375     if (   cmailMailedMove
10376         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10377         sprintf(string, partCommandString,
10378                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10379         commandOutput = popen(string, "r");
10380
10381         if (commandOutput == NULL) {
10382             DisplayError(_("Failed to invoke cmail"), 0);
10383         } else {
10384             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10385                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10386             }
10387             if (nBuffers > 1) {
10388                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10389                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10390                 nBytes = MSG_SIZ - 1;
10391             } else {
10392                 (void) memcpy(msg, buffer, nBytes);
10393             }
10394             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10395
10396             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10397                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10398
10399                 archived = TRUE;
10400                 for (i = 0; i < nCmailGames; i ++) {
10401                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10402                         archived = FALSE;
10403                     }
10404                 }
10405                 if (   archived
10406                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10407                         != NULL)) {
10408                     sprintf(buffer, "%s/%s.%s.archive",
10409                             arcDir,
10410                             appData.cmailGameName,
10411                             gameInfo.date);
10412                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10413                     cmailMsgLoaded = FALSE;
10414                 }
10415             }
10416
10417             DisplayInformation(msg);
10418             pclose(commandOutput);
10419         }
10420     } else {
10421         if ((*cmailMsg) != '\0') {
10422             DisplayInformation(cmailMsg);
10423         }
10424     }
10425
10426     return;
10427 #endif /* !WIN32 */
10428 }
10429
10430 char *
10431 CmailMsg()
10432 {
10433 #if WIN32
10434     return NULL;
10435 #else
10436     int  prependComma = 0;
10437     char number[5];
10438     char string[MSG_SIZ];       /* Space for game-list */
10439     int  i;
10440     
10441     if (!cmailMsgLoaded) return "";
10442
10443     if (cmailMailedMove) {
10444         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10445     } else {
10446         /* Create a list of games left */
10447         sprintf(string, "[");
10448         for (i = 0; i < nCmailGames; i ++) {
10449             if (! (   cmailMoveRegistered[i]
10450                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10451                 if (prependComma) {
10452                     sprintf(number, ",%d", i + 1);
10453                 } else {
10454                     sprintf(number, "%d", i + 1);
10455                     prependComma = 1;
10456                 }
10457                 
10458                 strcat(string, number);
10459             }
10460         }
10461         strcat(string, "]");
10462
10463         if (nCmailMovesRegistered + nCmailResults == 0) {
10464             switch (nCmailGames) {
10465               case 1:
10466                 sprintf(cmailMsg,
10467                         _("Still need to make move for game\n"));
10468                 break;
10469                 
10470               case 2:
10471                 sprintf(cmailMsg,
10472                         _("Still need to make moves for both games\n"));
10473                 break;
10474                 
10475               default:
10476                 sprintf(cmailMsg,
10477                         _("Still need to make moves for all %d games\n"),
10478                         nCmailGames);
10479                 break;
10480             }
10481         } else {
10482             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10483               case 1:
10484                 sprintf(cmailMsg,
10485                         _("Still need to make a move for game %s\n"),
10486                         string);
10487                 break;
10488                 
10489               case 0:
10490                 if (nCmailResults == nCmailGames) {
10491                     sprintf(cmailMsg, _("No unfinished games\n"));
10492                 } else {
10493                     sprintf(cmailMsg, _("Ready to send mail\n"));
10494                 }
10495                 break;
10496                 
10497               default:
10498                 sprintf(cmailMsg,
10499                         _("Still need to make moves for games %s\n"),
10500                         string);
10501             }
10502         }
10503     }
10504     return cmailMsg;
10505 #endif /* WIN32 */
10506 }
10507
10508 void
10509 ResetGameEvent()
10510 {
10511     if (gameMode == Training)
10512       SetTrainingModeOff();
10513
10514     Reset(TRUE, TRUE);
10515     cmailMsgLoaded = FALSE;
10516     if (appData.icsActive) {
10517       SendToICS(ics_prefix);
10518       SendToICS("refresh\n");
10519     }
10520 }
10521
10522 void
10523 ExitEvent(status)
10524      int status;
10525 {
10526     exiting++;
10527     if (exiting > 2) {
10528       /* Give up on clean exit */
10529       exit(status);
10530     }
10531     if (exiting > 1) {
10532       /* Keep trying for clean exit */
10533       return;
10534     }
10535
10536     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10537
10538     if (telnetISR != NULL) {
10539       RemoveInputSource(telnetISR);
10540     }
10541     if (icsPR != NoProc) {
10542       DestroyChildProcess(icsPR, TRUE);
10543     }
10544
10545     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10546     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10547
10548     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10549     /* make sure this other one finishes before killing it!                  */
10550     if(endingGame) { int count = 0;
10551         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10552         while(endingGame && count++ < 10) DoSleep(1);
10553         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10554     }
10555
10556     /* Kill off chess programs */
10557     if (first.pr != NoProc) {
10558         ExitAnalyzeMode();
10559         
10560         DoSleep( appData.delayBeforeQuit );
10561         SendToProgram("quit\n", &first);
10562         DoSleep( appData.delayAfterQuit );
10563         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10564     }
10565     if (second.pr != NoProc) {
10566         DoSleep( appData.delayBeforeQuit );
10567         SendToProgram("quit\n", &second);
10568         DoSleep( appData.delayAfterQuit );
10569         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10570     }
10571     if (first.isr != NULL) {
10572         RemoveInputSource(first.isr);
10573     }
10574     if (second.isr != NULL) {
10575         RemoveInputSource(second.isr);
10576     }
10577
10578     ShutDownFrontEnd();
10579     exit(status);
10580 }
10581
10582 void
10583 PauseEvent()
10584 {
10585     if (appData.debugMode)
10586         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10587     if (pausing) {
10588         pausing = FALSE;
10589         ModeHighlight();
10590         if (gameMode == MachinePlaysWhite ||
10591             gameMode == MachinePlaysBlack) {
10592             StartClocks();
10593         } else {
10594             DisplayBothClocks();
10595         }
10596         if (gameMode == PlayFromGameFile) {
10597             if (appData.timeDelay >= 0) 
10598                 AutoPlayGameLoop();
10599         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10600             Reset(FALSE, TRUE);
10601             SendToICS(ics_prefix);
10602             SendToICS("refresh\n");
10603         } else if (currentMove < forwardMostMove) {
10604             ForwardInner(forwardMostMove);
10605         }
10606         pauseExamInvalid = FALSE;
10607     } else {
10608         switch (gameMode) {
10609           default:
10610             return;
10611           case IcsExamining:
10612             pauseExamForwardMostMove = forwardMostMove;
10613             pauseExamInvalid = FALSE;
10614             /* fall through */
10615           case IcsObserving:
10616           case IcsPlayingWhite:
10617           case IcsPlayingBlack:
10618             pausing = TRUE;
10619             ModeHighlight();
10620             return;
10621           case PlayFromGameFile:
10622             (void) StopLoadGameTimer();
10623             pausing = TRUE;
10624             ModeHighlight();
10625             break;
10626           case BeginningOfGame:
10627             if (appData.icsActive) return;
10628             /* else fall through */
10629           case MachinePlaysWhite:
10630           case MachinePlaysBlack:
10631           case TwoMachinesPlay:
10632             if (forwardMostMove == 0)
10633               return;           /* don't pause if no one has moved */
10634             if ((gameMode == MachinePlaysWhite &&
10635                  !WhiteOnMove(forwardMostMove)) ||
10636                 (gameMode == MachinePlaysBlack &&
10637                  WhiteOnMove(forwardMostMove))) {
10638                 StopClocks();
10639             }
10640             pausing = TRUE;
10641             ModeHighlight();
10642             break;
10643         }
10644     }
10645 }
10646
10647 void
10648 EditCommentEvent()
10649 {
10650     char title[MSG_SIZ];
10651
10652     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10653         strcpy(title, _("Edit comment"));
10654     } else {
10655         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10656                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10657                 parseList[currentMove - 1]);
10658     }
10659
10660     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10661 }
10662
10663
10664 void
10665 EditTagsEvent()
10666 {
10667     char *tags = PGNTags(&gameInfo);
10668     EditTagsPopUp(tags);
10669     free(tags);
10670 }
10671
10672 void
10673 AnalyzeModeEvent()
10674 {
10675     if (appData.noChessProgram || gameMode == AnalyzeMode)
10676       return;
10677
10678     if (gameMode != AnalyzeFile) {
10679         if (!appData.icsEngineAnalyze) {
10680                EditGameEvent();
10681                if (gameMode != EditGame) return;
10682         }
10683         ResurrectChessProgram();
10684         SendToProgram("analyze\n", &first);
10685         first.analyzing = TRUE;
10686         /*first.maybeThinking = TRUE;*/
10687         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10688         EngineOutputPopUp();
10689     }
10690     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10691     pausing = FALSE;
10692     ModeHighlight();
10693     SetGameInfo();
10694
10695     StartAnalysisClock();
10696     GetTimeMark(&lastNodeCountTime);
10697     lastNodeCount = 0;
10698 }
10699
10700 void
10701 AnalyzeFileEvent()
10702 {
10703     if (appData.noChessProgram || gameMode == AnalyzeFile)
10704       return;
10705
10706     if (gameMode != AnalyzeMode) {
10707         EditGameEvent();
10708         if (gameMode != EditGame) return;
10709         ResurrectChessProgram();
10710         SendToProgram("analyze\n", &first);
10711         first.analyzing = TRUE;
10712         /*first.maybeThinking = TRUE;*/
10713         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10714         EngineOutputPopUp();
10715     }
10716     gameMode = AnalyzeFile;
10717     pausing = FALSE;
10718     ModeHighlight();
10719     SetGameInfo();
10720
10721     StartAnalysisClock();
10722     GetTimeMark(&lastNodeCountTime);
10723     lastNodeCount = 0;
10724 }
10725
10726 void
10727 MachineWhiteEvent()
10728 {
10729     char buf[MSG_SIZ];
10730     char *bookHit = NULL;
10731
10732     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10733       return;
10734
10735
10736     if (gameMode == PlayFromGameFile || 
10737         gameMode == TwoMachinesPlay  || 
10738         gameMode == Training         || 
10739         gameMode == AnalyzeMode      || 
10740         gameMode == EndOfGame)
10741         EditGameEvent();
10742
10743     if (gameMode == EditPosition) 
10744         EditPositionDone(TRUE);
10745
10746     if (!WhiteOnMove(currentMove)) {
10747         DisplayError(_("It is not White's turn"), 0);
10748         return;
10749     }
10750   
10751     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10752       ExitAnalyzeMode();
10753
10754     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10755         gameMode == AnalyzeFile)
10756         TruncateGame();
10757
10758     ResurrectChessProgram();    /* in case it isn't running */
10759     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10760         gameMode = MachinePlaysWhite;
10761         ResetClocks();
10762     } else
10763     gameMode = MachinePlaysWhite;
10764     pausing = FALSE;
10765     ModeHighlight();
10766     SetGameInfo();
10767     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10768     DisplayTitle(buf);
10769     if (first.sendName) {
10770       sprintf(buf, "name %s\n", gameInfo.black);
10771       SendToProgram(buf, &first);
10772     }
10773     if (first.sendTime) {
10774       if (first.useColors) {
10775         SendToProgram("black\n", &first); /*gnu kludge*/
10776       }
10777       SendTimeRemaining(&first, TRUE);
10778     }
10779     if (first.useColors) {
10780       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10781     }
10782     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10783     SetMachineThinkingEnables();
10784     first.maybeThinking = TRUE;
10785     StartClocks();
10786     firstMove = FALSE;
10787
10788     if (appData.autoFlipView && !flipView) {
10789       flipView = !flipView;
10790       DrawPosition(FALSE, NULL);
10791       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10792     }
10793
10794     if(bookHit) { // [HGM] book: simulate book reply
10795         static char bookMove[MSG_SIZ]; // a bit generous?
10796
10797         programStats.nodes = programStats.depth = programStats.time = 
10798         programStats.score = programStats.got_only_move = 0;
10799         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10800
10801         strcpy(bookMove, "move ");
10802         strcat(bookMove, bookHit);
10803         HandleMachineMove(bookMove, &first);
10804     }
10805 }
10806
10807 void
10808 MachineBlackEvent()
10809 {
10810     char buf[MSG_SIZ];
10811    char *bookHit = NULL;
10812
10813     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10814         return;
10815
10816
10817     if (gameMode == PlayFromGameFile || 
10818         gameMode == TwoMachinesPlay  || 
10819         gameMode == Training         || 
10820         gameMode == AnalyzeMode      || 
10821         gameMode == EndOfGame)
10822         EditGameEvent();
10823
10824     if (gameMode == EditPosition) 
10825         EditPositionDone(TRUE);
10826
10827     if (WhiteOnMove(currentMove)) {
10828         DisplayError(_("It is not Black's turn"), 0);
10829         return;
10830     }
10831     
10832     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10833       ExitAnalyzeMode();
10834
10835     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10836         gameMode == AnalyzeFile)
10837         TruncateGame();
10838
10839     ResurrectChessProgram();    /* in case it isn't running */
10840     gameMode = MachinePlaysBlack;
10841     pausing = FALSE;
10842     ModeHighlight();
10843     SetGameInfo();
10844     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10845     DisplayTitle(buf);
10846     if (first.sendName) {
10847       sprintf(buf, "name %s\n", gameInfo.white);
10848       SendToProgram(buf, &first);
10849     }
10850     if (first.sendTime) {
10851       if (first.useColors) {
10852         SendToProgram("white\n", &first); /*gnu kludge*/
10853       }
10854       SendTimeRemaining(&first, FALSE);
10855     }
10856     if (first.useColors) {
10857       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10858     }
10859     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10860     SetMachineThinkingEnables();
10861     first.maybeThinking = TRUE;
10862     StartClocks();
10863
10864     if (appData.autoFlipView && flipView) {
10865       flipView = !flipView;
10866       DrawPosition(FALSE, NULL);
10867       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10868     }
10869     if(bookHit) { // [HGM] book: simulate book reply
10870         static char bookMove[MSG_SIZ]; // a bit generous?
10871
10872         programStats.nodes = programStats.depth = programStats.time = 
10873         programStats.score = programStats.got_only_move = 0;
10874         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10875
10876         strcpy(bookMove, "move ");
10877         strcat(bookMove, bookHit);
10878         HandleMachineMove(bookMove, &first);
10879     }
10880 }
10881
10882
10883 void
10884 DisplayTwoMachinesTitle()
10885 {
10886     char buf[MSG_SIZ];
10887     if (appData.matchGames > 0) {
10888         if (first.twoMachinesColor[0] == 'w') {
10889             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10890                     gameInfo.white, gameInfo.black,
10891                     first.matchWins, second.matchWins,
10892                     matchGame - 1 - (first.matchWins + second.matchWins));
10893         } else {
10894             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10895                     gameInfo.white, gameInfo.black,
10896                     second.matchWins, first.matchWins,
10897                     matchGame - 1 - (first.matchWins + second.matchWins));
10898         }
10899     } else {
10900         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10901     }
10902     DisplayTitle(buf);
10903 }
10904
10905 void
10906 TwoMachinesEvent P((void))
10907 {
10908     int i;
10909     char buf[MSG_SIZ];
10910     ChessProgramState *onmove;
10911     char *bookHit = NULL;
10912     
10913     if (appData.noChessProgram) return;
10914
10915     switch (gameMode) {
10916       case TwoMachinesPlay:
10917         return;
10918       case MachinePlaysWhite:
10919       case MachinePlaysBlack:
10920         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10921             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10922             return;
10923         }
10924         /* fall through */
10925       case BeginningOfGame:
10926       case PlayFromGameFile:
10927       case EndOfGame:
10928         EditGameEvent();
10929         if (gameMode != EditGame) return;
10930         break;
10931       case EditPosition:
10932         EditPositionDone(TRUE);
10933         break;
10934       case AnalyzeMode:
10935       case AnalyzeFile:
10936         ExitAnalyzeMode();
10937         break;
10938       case EditGame:
10939       default:
10940         break;
10941     }
10942
10943     forwardMostMove = currentMove;
10944     ResurrectChessProgram();    /* in case first program isn't running */
10945
10946     if (second.pr == NULL) {
10947         StartChessProgram(&second);
10948         if (second.protocolVersion == 1) {
10949           TwoMachinesEventIfReady();
10950         } else {
10951           /* kludge: allow timeout for initial "feature" command */
10952           FreezeUI();
10953           DisplayMessage("", _("Starting second chess program"));
10954           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10955         }
10956         return;
10957     }
10958     DisplayMessage("", "");
10959     InitChessProgram(&second, FALSE);
10960     SendToProgram("force\n", &second);
10961     if (startedFromSetupPosition) {
10962         SendBoard(&second, backwardMostMove);
10963     if (appData.debugMode) {
10964         fprintf(debugFP, "Two Machines\n");
10965     }
10966     }
10967     for (i = backwardMostMove; i < forwardMostMove; i++) {
10968         SendMoveToProgram(i, &second);
10969     }
10970
10971     gameMode = TwoMachinesPlay;
10972     pausing = FALSE;
10973     ModeHighlight();
10974     SetGameInfo();
10975     DisplayTwoMachinesTitle();
10976     firstMove = TRUE;
10977     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10978         onmove = &first;
10979     } else {
10980         onmove = &second;
10981     }
10982
10983     SendToProgram(first.computerString, &first);
10984     if (first.sendName) {
10985       sprintf(buf, "name %s\n", second.tidy);
10986       SendToProgram(buf, &first);
10987     }
10988     SendToProgram(second.computerString, &second);
10989     if (second.sendName) {
10990       sprintf(buf, "name %s\n", first.tidy);
10991       SendToProgram(buf, &second);
10992     }
10993
10994     ResetClocks();
10995     if (!first.sendTime || !second.sendTime) {
10996         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10997         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10998     }
10999     if (onmove->sendTime) {
11000       if (onmove->useColors) {
11001         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11002       }
11003       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11004     }
11005     if (onmove->useColors) {
11006       SendToProgram(onmove->twoMachinesColor, onmove);
11007     }
11008     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11009 //    SendToProgram("go\n", onmove);
11010     onmove->maybeThinking = TRUE;
11011     SetMachineThinkingEnables();
11012
11013     StartClocks();
11014
11015     if(bookHit) { // [HGM] book: simulate book reply
11016         static char bookMove[MSG_SIZ]; // a bit generous?
11017
11018         programStats.nodes = programStats.depth = programStats.time = 
11019         programStats.score = programStats.got_only_move = 0;
11020         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11021
11022         strcpy(bookMove, "move ");
11023         strcat(bookMove, bookHit);
11024         savedMessage = bookMove; // args for deferred call
11025         savedState = onmove;
11026         ScheduleDelayedEvent(DeferredBookMove, 1);
11027     }
11028 }
11029
11030 void
11031 TrainingEvent()
11032 {
11033     if (gameMode == Training) {
11034       SetTrainingModeOff();
11035       gameMode = PlayFromGameFile;
11036       DisplayMessage("", _("Training mode off"));
11037     } else {
11038       gameMode = Training;
11039       animateTraining = appData.animate;
11040
11041       /* make sure we are not already at the end of the game */
11042       if (currentMove < forwardMostMove) {
11043         SetTrainingModeOn();
11044         DisplayMessage("", _("Training mode on"));
11045       } else {
11046         gameMode = PlayFromGameFile;
11047         DisplayError(_("Already at end of game"), 0);
11048       }
11049     }
11050     ModeHighlight();
11051 }
11052
11053 void
11054 IcsClientEvent()
11055 {
11056     if (!appData.icsActive) return;
11057     switch (gameMode) {
11058       case IcsPlayingWhite:
11059       case IcsPlayingBlack:
11060       case IcsObserving:
11061       case IcsIdle:
11062       case BeginningOfGame:
11063       case IcsExamining:
11064         return;
11065
11066       case EditGame:
11067         break;
11068
11069       case EditPosition:
11070         EditPositionDone(TRUE);
11071         break;
11072
11073       case AnalyzeMode:
11074       case AnalyzeFile:
11075         ExitAnalyzeMode();
11076         break;
11077         
11078       default:
11079         EditGameEvent();
11080         break;
11081     }
11082
11083     gameMode = IcsIdle;
11084     ModeHighlight();
11085     return;
11086 }
11087
11088
11089 void
11090 EditGameEvent()
11091 {
11092     int i;
11093
11094     switch (gameMode) {
11095       case Training:
11096         SetTrainingModeOff();
11097         break;
11098       case MachinePlaysWhite:
11099       case MachinePlaysBlack:
11100       case BeginningOfGame:
11101         SendToProgram("force\n", &first);
11102         SetUserThinkingEnables();
11103         break;
11104       case PlayFromGameFile:
11105         (void) StopLoadGameTimer();
11106         if (gameFileFP != NULL) {
11107             gameFileFP = NULL;
11108         }
11109         break;
11110       case EditPosition:
11111         EditPositionDone(TRUE);
11112         break;
11113       case AnalyzeMode:
11114       case AnalyzeFile:
11115         ExitAnalyzeMode();
11116         SendToProgram("force\n", &first);
11117         break;
11118       case TwoMachinesPlay:
11119         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11120         ResurrectChessProgram();
11121         SetUserThinkingEnables();
11122         break;
11123       case EndOfGame:
11124         ResurrectChessProgram();
11125         break;
11126       case IcsPlayingBlack:
11127       case IcsPlayingWhite:
11128         DisplayError(_("Warning: You are still playing a game"), 0);
11129         break;
11130       case IcsObserving:
11131         DisplayError(_("Warning: You are still observing a game"), 0);
11132         break;
11133       case IcsExamining:
11134         DisplayError(_("Warning: You are still examining a game"), 0);
11135         break;
11136       case IcsIdle:
11137         break;
11138       case EditGame:
11139       default:
11140         return;
11141     }
11142     
11143     pausing = FALSE;
11144     StopClocks();
11145     first.offeredDraw = second.offeredDraw = 0;
11146
11147     if (gameMode == PlayFromGameFile) {
11148         whiteTimeRemaining = timeRemaining[0][currentMove];
11149         blackTimeRemaining = timeRemaining[1][currentMove];
11150         DisplayTitle("");
11151     }
11152
11153     if (gameMode == MachinePlaysWhite ||
11154         gameMode == MachinePlaysBlack ||
11155         gameMode == TwoMachinesPlay ||
11156         gameMode == EndOfGame) {
11157         i = forwardMostMove;
11158         while (i > currentMove) {
11159             SendToProgram("undo\n", &first);
11160             i--;
11161         }
11162         whiteTimeRemaining = timeRemaining[0][currentMove];
11163         blackTimeRemaining = timeRemaining[1][currentMove];
11164         DisplayBothClocks();
11165         if (whiteFlag || blackFlag) {
11166             whiteFlag = blackFlag = 0;
11167         }
11168         DisplayTitle("");
11169     }           
11170     
11171     gameMode = EditGame;
11172     ModeHighlight();
11173     SetGameInfo();
11174 }
11175
11176
11177 void
11178 EditPositionEvent()
11179 {
11180     if (gameMode == EditPosition) {
11181         EditGameEvent();
11182         return;
11183     }
11184     
11185     EditGameEvent();
11186     if (gameMode != EditGame) return;
11187     
11188     gameMode = EditPosition;
11189     ModeHighlight();
11190     SetGameInfo();
11191     if (currentMove > 0)
11192       CopyBoard(boards[0], boards[currentMove]);
11193     
11194     blackPlaysFirst = !WhiteOnMove(currentMove);
11195     ResetClocks();
11196     currentMove = forwardMostMove = backwardMostMove = 0;
11197     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11198     DisplayMove(-1);
11199 }
11200
11201 void
11202 ExitAnalyzeMode()
11203 {
11204     /* [DM] icsEngineAnalyze - possible call from other functions */
11205     if (appData.icsEngineAnalyze) {
11206         appData.icsEngineAnalyze = FALSE;
11207
11208         DisplayMessage("",_("Close ICS engine analyze..."));
11209     }
11210     if (first.analysisSupport && first.analyzing) {
11211       SendToProgram("exit\n", &first);
11212       first.analyzing = FALSE;
11213     }
11214     thinkOutput[0] = NULLCHAR;
11215 }
11216
11217 void
11218 EditPositionDone(Boolean fakeRights)
11219 {
11220     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11221
11222     startedFromSetupPosition = TRUE;
11223     InitChessProgram(&first, FALSE);
11224     if(fakeRights)  
11225       { /* don't do this if we just pasted FEN */
11226         castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11227         if(boards[0][0][BOARD_WIDTH>>1] == king) 
11228           {
11229             castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11230             castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11231           } 
11232         else 
11233           castlingRights[0][2] = -1;
11234         if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) 
11235           {
11236             castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11237             castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11238           } 
11239         else 
11240           castlingRights[0][5] = -1;
11241       }
11242     SendToProgram("force\n", &first);
11243     if (blackPlaysFirst) {
11244         strcpy(moveList[0], "");
11245         strcpy(parseList[0], "");
11246         currentMove = forwardMostMove = backwardMostMove = 1;
11247         CopyBoard(boards[1], boards[0]);
11248         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11249         { int i;
11250           epStatus[1] = epStatus[0];
11251           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11252         }
11253     } else {
11254         currentMove = forwardMostMove = backwardMostMove = 0;
11255     }
11256     SendBoard(&first, forwardMostMove);
11257     if (appData.debugMode) {
11258         fprintf(debugFP, "EditPosDone\n");
11259     }
11260     DisplayTitle("");
11261     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11262     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11263     gameMode = EditGame;
11264     ModeHighlight();
11265     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11266     ClearHighlights(); /* [AS] */
11267 }
11268
11269 /* Pause for `ms' milliseconds */
11270 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11271 void
11272 TimeDelay(ms)
11273      long ms;
11274 {
11275     TimeMark m1, m2;
11276
11277     GetTimeMark(&m1);
11278     do {
11279         GetTimeMark(&m2);
11280     } while (SubtractTimeMarks(&m2, &m1) < ms);
11281 }
11282
11283 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11284 void
11285 SendMultiLineToICS(buf)
11286      char *buf;
11287 {
11288     char temp[MSG_SIZ+1], *p;
11289     int len;
11290
11291     len = strlen(buf);
11292     if (len > MSG_SIZ)
11293       len = MSG_SIZ;
11294   
11295     strncpy(temp, buf, len);
11296     temp[len] = 0;
11297
11298     p = temp;
11299     while (*p) {
11300         if (*p == '\n' || *p == '\r')
11301           *p = ' ';
11302         ++p;
11303     }
11304
11305     strcat(temp, "\n");
11306     SendToICS(temp);
11307     SendToPlayer(temp, strlen(temp));
11308 }
11309
11310 void
11311 SetWhiteToPlayEvent()
11312 {
11313     if (gameMode == EditPosition) {
11314         blackPlaysFirst = FALSE;
11315         DisplayBothClocks();    /* works because currentMove is 0 */
11316     } else if (gameMode == IcsExamining) {
11317         SendToICS(ics_prefix);
11318         SendToICS("tomove white\n");
11319     }
11320 }
11321
11322 void
11323 SetBlackToPlayEvent()
11324 {
11325     if (gameMode == EditPosition) {
11326         blackPlaysFirst = TRUE;
11327         currentMove = 1;        /* kludge */
11328         DisplayBothClocks();
11329         currentMove = 0;
11330     } else if (gameMode == IcsExamining) {
11331         SendToICS(ics_prefix);
11332         SendToICS("tomove black\n");
11333     }
11334 }
11335
11336 void
11337 EditPositionMenuEvent(selection, x, y)
11338      ChessSquare selection;
11339      int x, y;
11340 {
11341     char buf[MSG_SIZ];
11342     ChessSquare piece = boards[0][y][x];
11343
11344     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11345
11346     switch (selection) {
11347       case ClearBoard:
11348         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11349             SendToICS(ics_prefix);
11350             SendToICS("bsetup clear\n");
11351         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11352             SendToICS(ics_prefix);
11353             SendToICS("clearboard\n");
11354         } else {
11355             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11356                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11357                 for (y = 0; y < BOARD_HEIGHT; y++) {
11358                     if (gameMode == IcsExamining) {
11359                         if (boards[currentMove][y][x] != EmptySquare) {
11360                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11361                                     AAA + x, ONE + y);
11362                             SendToICS(buf);
11363                         }
11364                     } else {
11365                         boards[0][y][x] = p;
11366                     }
11367                 }
11368             }
11369         }
11370         if (gameMode == EditPosition) {
11371             DrawPosition(FALSE, boards[0]);
11372         }
11373         break;
11374
11375       case WhitePlay:
11376         SetWhiteToPlayEvent();
11377         break;
11378
11379       case BlackPlay:
11380         SetBlackToPlayEvent();
11381         break;
11382
11383       case EmptySquare:
11384         if (gameMode == IcsExamining) {
11385             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11386             SendToICS(buf);
11387         } else {
11388             boards[0][y][x] = EmptySquare;
11389             DrawPosition(FALSE, boards[0]);
11390         }
11391         break;
11392
11393       case PromotePiece:
11394         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11395            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11396             selection = (ChessSquare) (PROMOTED piece);
11397         } else if(piece == EmptySquare) selection = WhiteSilver;
11398         else selection = (ChessSquare)((int)piece - 1);
11399         goto defaultlabel;
11400
11401       case DemotePiece:
11402         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11403            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11404             selection = (ChessSquare) (DEMOTED piece);
11405         } else if(piece == EmptySquare) selection = BlackSilver;
11406         else selection = (ChessSquare)((int)piece + 1);       
11407         goto defaultlabel;
11408
11409       case WhiteQueen:
11410       case BlackQueen:
11411         if(gameInfo.variant == VariantShatranj ||
11412            gameInfo.variant == VariantXiangqi  ||
11413            gameInfo.variant == VariantCourier  ||
11414            gameInfo.variant == VariantMakruk     )
11415             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11416         goto defaultlabel;
11417
11418       case WhiteKing:
11419       case BlackKing:
11420         if(gameInfo.variant == VariantXiangqi)
11421             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11422         if(gameInfo.variant == VariantKnightmate)
11423             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11424       default:
11425         defaultlabel:
11426         if (gameMode == IcsExamining) {
11427             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11428                     PieceToChar(selection), AAA + x, ONE + y);
11429             SendToICS(buf);
11430         } else {
11431             boards[0][y][x] = selection;
11432             DrawPosition(FALSE, boards[0]);
11433         }
11434         break;
11435     }
11436 }
11437
11438
11439 void
11440 DropMenuEvent(selection, x, y)
11441      ChessSquare selection;
11442      int x, y;
11443 {
11444     ChessMove moveType;
11445
11446     switch (gameMode) {
11447       case IcsPlayingWhite:
11448       case MachinePlaysBlack:
11449         if (!WhiteOnMove(currentMove)) {
11450             DisplayMoveError(_("It is Black's turn"));
11451             return;
11452         }
11453         moveType = WhiteDrop;
11454         break;
11455       case IcsPlayingBlack:
11456       case MachinePlaysWhite:
11457         if (WhiteOnMove(currentMove)) {
11458             DisplayMoveError(_("It is White's turn"));
11459             return;
11460         }
11461         moveType = BlackDrop;
11462         break;
11463       case EditGame:
11464         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11465         break;
11466       default:
11467         return;
11468     }
11469
11470     if (moveType == BlackDrop && selection < BlackPawn) {
11471       selection = (ChessSquare) ((int) selection
11472                                  + (int) BlackPawn - (int) WhitePawn);
11473     }
11474     if (boards[currentMove][y][x] != EmptySquare) {
11475         DisplayMoveError(_("That square is occupied"));
11476         return;
11477     }
11478
11479     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11480 }
11481
11482 void
11483 AcceptEvent()
11484 {
11485     /* Accept a pending offer of any kind from opponent */
11486     
11487     if (appData.icsActive) {
11488         SendToICS(ics_prefix);
11489         SendToICS("accept\n");
11490     } else if (cmailMsgLoaded) {
11491         if (currentMove == cmailOldMove &&
11492             commentList[cmailOldMove] != NULL &&
11493             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11494                    "Black offers a draw" : "White offers a draw")) {
11495             TruncateGame();
11496             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11497             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11498         } else {
11499             DisplayError(_("There is no pending offer on this move"), 0);
11500             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11501         }
11502     } else {
11503         /* Not used for offers from chess program */
11504     }
11505 }
11506
11507 void
11508 DeclineEvent()
11509 {
11510     /* Decline a pending offer of any kind from opponent */
11511     
11512     if (appData.icsActive) {
11513         SendToICS(ics_prefix);
11514         SendToICS("decline\n");
11515     } else if (cmailMsgLoaded) {
11516         if (currentMove == cmailOldMove &&
11517             commentList[cmailOldMove] != NULL &&
11518             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11519                    "Black offers a draw" : "White offers a draw")) {
11520 #ifdef NOTDEF
11521             AppendComment(cmailOldMove, "Draw declined");
11522             DisplayComment(cmailOldMove - 1, "Draw declined");
11523 #endif /*NOTDEF*/
11524         } else {
11525             DisplayError(_("There is no pending offer on this move"), 0);
11526         }
11527     } else {
11528         /* Not used for offers from chess program */
11529     }
11530 }
11531
11532 void
11533 RematchEvent()
11534 {
11535     /* Issue ICS rematch command */
11536     if (appData.icsActive) {
11537         SendToICS(ics_prefix);
11538         SendToICS("rematch\n");
11539     }
11540 }
11541
11542 void
11543 CallFlagEvent()
11544 {
11545     /* Call your opponent's flag (claim a win on time) */
11546     if (appData.icsActive) {
11547         SendToICS(ics_prefix);
11548         SendToICS("flag\n");
11549     } else {
11550         switch (gameMode) {
11551           default:
11552             return;
11553           case MachinePlaysWhite:
11554             if (whiteFlag) {
11555                 if (blackFlag)
11556                   GameEnds(GameIsDrawn, "Both players ran out of time",
11557                            GE_PLAYER);
11558                 else
11559                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11560             } else {
11561                 DisplayError(_("Your opponent is not out of time"), 0);
11562             }
11563             break;
11564           case MachinePlaysBlack:
11565             if (blackFlag) {
11566                 if (whiteFlag)
11567                   GameEnds(GameIsDrawn, "Both players ran out of time",
11568                            GE_PLAYER);
11569                 else
11570                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11571             } else {
11572                 DisplayError(_("Your opponent is not out of time"), 0);
11573             }
11574             break;
11575         }
11576     }
11577 }
11578
11579 void
11580 DrawEvent()
11581 {
11582     /* Offer draw or accept pending draw offer from opponent */
11583     
11584     if (appData.icsActive) {
11585         /* Note: tournament rules require draw offers to be
11586            made after you make your move but before you punch
11587            your clock.  Currently ICS doesn't let you do that;
11588            instead, you immediately punch your clock after making
11589            a move, but you can offer a draw at any time. */
11590         
11591         SendToICS(ics_prefix);
11592         SendToICS("draw\n");
11593     } else if (cmailMsgLoaded) {
11594         if (currentMove == cmailOldMove &&
11595             commentList[cmailOldMove] != NULL &&
11596             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11597                    "Black offers a draw" : "White offers a draw")) {
11598             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11599             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11600         } else if (currentMove == cmailOldMove + 1) {
11601             char *offer = WhiteOnMove(cmailOldMove) ?
11602               "White offers a draw" : "Black offers a draw";
11603             AppendComment(currentMove, offer);
11604             DisplayComment(currentMove - 1, offer);
11605             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11606         } else {
11607             DisplayError(_("You must make your move before offering a draw"), 0);
11608             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11609         }
11610     } else if (first.offeredDraw) {
11611         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11612     } else {
11613         if (first.sendDrawOffers) {
11614             SendToProgram("draw\n", &first);
11615             userOfferedDraw = TRUE;
11616         }
11617     }
11618 }
11619
11620 void
11621 AdjournEvent()
11622 {
11623     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11624     
11625     if (appData.icsActive) {
11626         SendToICS(ics_prefix);
11627         SendToICS("adjourn\n");
11628     } else {
11629         /* Currently GNU Chess doesn't offer or accept Adjourns */
11630     }
11631 }
11632
11633
11634 void
11635 AbortEvent()
11636 {
11637     /* Offer Abort or accept pending Abort offer from opponent */
11638     
11639     if (appData.icsActive) {
11640         SendToICS(ics_prefix);
11641         SendToICS("abort\n");
11642     } else {
11643         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11644     }
11645 }
11646
11647 void
11648 ResignEvent()
11649 {
11650     /* Resign.  You can do this even if it's not your turn. */
11651     
11652     if (appData.icsActive) {
11653         SendToICS(ics_prefix);
11654         SendToICS("resign\n");
11655     } else {
11656         switch (gameMode) {
11657           case MachinePlaysWhite:
11658             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11659             break;
11660           case MachinePlaysBlack:
11661             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11662             break;
11663           case EditGame:
11664             if (cmailMsgLoaded) {
11665                 TruncateGame();
11666                 if (WhiteOnMove(cmailOldMove)) {
11667                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11668                 } else {
11669                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11670                 }
11671                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11672             }
11673             break;
11674           default:
11675             break;
11676         }
11677     }
11678 }
11679
11680
11681 void
11682 StopObservingEvent()
11683 {
11684     /* Stop observing current games */
11685     SendToICS(ics_prefix);
11686     SendToICS("unobserve\n");
11687 }
11688
11689 void
11690 StopExaminingEvent()
11691 {
11692     /* Stop observing current game */
11693     SendToICS(ics_prefix);
11694     SendToICS("unexamine\n");
11695 }
11696
11697 void
11698 ForwardInner(target)
11699      int target;
11700 {
11701     int limit;
11702
11703     if (appData.debugMode)
11704         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11705                 target, currentMove, forwardMostMove);
11706
11707     if (gameMode == EditPosition)
11708       return;
11709
11710     if (gameMode == PlayFromGameFile && !pausing)
11711       PauseEvent();
11712     
11713     if (gameMode == IcsExamining && pausing)
11714       limit = pauseExamForwardMostMove;
11715     else
11716       limit = forwardMostMove;
11717     
11718     if (target > limit) target = limit;
11719
11720     if (target > 0 && moveList[target - 1][0]) {
11721         int fromX, fromY, toX, toY;
11722         toX = moveList[target - 1][2] - AAA;
11723         toY = moveList[target - 1][3] - ONE;
11724         if (moveList[target - 1][1] == '@') {
11725             if (appData.highlightLastMove) {
11726                 SetHighlights(-1, -1, toX, toY);
11727             }
11728         } else {
11729             fromX = moveList[target - 1][0] - AAA;
11730             fromY = moveList[target - 1][1] - ONE;
11731             if (target == currentMove + 1) {
11732                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11733             }
11734             if (appData.highlightLastMove) {
11735                 SetHighlights(fromX, fromY, toX, toY);
11736             }
11737         }
11738     }
11739     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11740         gameMode == Training || gameMode == PlayFromGameFile || 
11741         gameMode == AnalyzeFile) {
11742         while (currentMove < target) {
11743             SendMoveToProgram(currentMove++, &first);
11744         }
11745     } else {
11746         currentMove = target;
11747     }
11748     
11749     if (gameMode == EditGame || gameMode == EndOfGame) {
11750         whiteTimeRemaining = timeRemaining[0][currentMove];
11751         blackTimeRemaining = timeRemaining[1][currentMove];
11752     }
11753     DisplayBothClocks();
11754     DisplayMove(currentMove - 1);
11755     DrawPosition(FALSE, boards[currentMove]);
11756     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11757     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11758         DisplayComment(currentMove - 1, commentList[currentMove]);
11759     }
11760 }
11761
11762
11763 void
11764 ForwardEvent()
11765 {
11766     if (gameMode == IcsExamining && !pausing) {
11767         SendToICS(ics_prefix);
11768         SendToICS("forward\n");
11769     } else {
11770         ForwardInner(currentMove + 1);
11771     }
11772 }
11773
11774 void
11775 ToEndEvent()
11776 {
11777     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11778         /* to optimze, we temporarily turn off analysis mode while we feed
11779          * the remaining moves to the engine. Otherwise we get analysis output
11780          * after each move.
11781          */ 
11782         if (first.analysisSupport) {
11783           SendToProgram("exit\nforce\n", &first);
11784           first.analyzing = FALSE;
11785         }
11786     }
11787         
11788     if (gameMode == IcsExamining && !pausing) {
11789         SendToICS(ics_prefix);
11790         SendToICS("forward 999999\n");
11791     } else {
11792         ForwardInner(forwardMostMove);
11793     }
11794
11795     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11796         /* we have fed all the moves, so reactivate analysis mode */
11797         SendToProgram("analyze\n", &first);
11798         first.analyzing = TRUE;
11799         /*first.maybeThinking = TRUE;*/
11800         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11801     }
11802 }
11803
11804 void
11805 BackwardInner(target)
11806      int target;
11807 {
11808     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11809
11810     if (appData.debugMode)
11811         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11812                 target, currentMove, forwardMostMove);
11813
11814     if (gameMode == EditPosition) return;
11815     if (currentMove <= backwardMostMove) {
11816         ClearHighlights();
11817         DrawPosition(full_redraw, boards[currentMove]);
11818         return;
11819     }
11820     if (gameMode == PlayFromGameFile && !pausing)
11821       PauseEvent();
11822     
11823     if (moveList[target][0]) {
11824         int fromX, fromY, toX, toY;
11825         toX = moveList[target][2] - AAA;
11826         toY = moveList[target][3] - ONE;
11827         if (moveList[target][1] == '@') {
11828             if (appData.highlightLastMove) {
11829                 SetHighlights(-1, -1, toX, toY);
11830             }
11831         } else {
11832             fromX = moveList[target][0] - AAA;
11833             fromY = moveList[target][1] - ONE;
11834             if (target == currentMove - 1) {
11835                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11836             }
11837             if (appData.highlightLastMove) {
11838                 SetHighlights(fromX, fromY, toX, toY);
11839             }
11840         }
11841     }
11842     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11843         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11844         while (currentMove > target) {
11845             SendToProgram("undo\n", &first);
11846             currentMove--;
11847         }
11848     } else {
11849         currentMove = target;
11850     }
11851     
11852     if (gameMode == EditGame || gameMode == EndOfGame) {
11853         whiteTimeRemaining = timeRemaining[0][currentMove];
11854         blackTimeRemaining = timeRemaining[1][currentMove];
11855     }
11856     DisplayBothClocks();
11857     DisplayMove(currentMove - 1);
11858     DrawPosition(full_redraw, boards[currentMove]);
11859     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11860     // [HGM] PV info: routine tests if comment empty
11861     DisplayComment(currentMove - 1, commentList[currentMove]);
11862 }
11863
11864 void
11865 BackwardEvent()
11866 {
11867     if (gameMode == IcsExamining && !pausing) {
11868         SendToICS(ics_prefix);
11869         SendToICS("backward\n");
11870     } else {
11871         BackwardInner(currentMove - 1);
11872     }
11873 }
11874
11875 void
11876 ToStartEvent()
11877 {
11878     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11879         /* to optimize, we temporarily turn off analysis mode while we undo
11880          * all the moves. Otherwise we get analysis output after each undo.
11881          */ 
11882         if (first.analysisSupport) {
11883           SendToProgram("exit\nforce\n", &first);
11884           first.analyzing = FALSE;
11885         }
11886     }
11887
11888     if (gameMode == IcsExamining && !pausing) {
11889         SendToICS(ics_prefix);
11890         SendToICS("backward 999999\n");
11891     } else {
11892         BackwardInner(backwardMostMove);
11893     }
11894
11895     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11896         /* we have fed all the moves, so reactivate analysis mode */
11897         SendToProgram("analyze\n", &first);
11898         first.analyzing = TRUE;
11899         /*first.maybeThinking = TRUE;*/
11900         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11901     }
11902 }
11903
11904 void
11905 ToNrEvent(int to)
11906 {
11907   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11908   if (to >= forwardMostMove) to = forwardMostMove;
11909   if (to <= backwardMostMove) to = backwardMostMove;
11910   if (to < currentMove) {
11911     BackwardInner(to);
11912   } else {
11913     ForwardInner(to);
11914   }
11915 }
11916
11917 void
11918 RevertEvent()
11919 {
11920     if (gameMode != IcsExamining) {
11921         DisplayError(_("You are not examining a game"), 0);
11922         return;
11923     }
11924     if (pausing) {
11925         DisplayError(_("You can't revert while pausing"), 0);
11926         return;
11927     }
11928     SendToICS(ics_prefix);
11929     SendToICS("revert\n");
11930 }
11931
11932 void
11933 RetractMoveEvent()
11934 {
11935     switch (gameMode) {
11936       case MachinePlaysWhite:
11937       case MachinePlaysBlack:
11938         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11939             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11940             return;
11941         }
11942         if (forwardMostMove < 2) return;
11943         currentMove = forwardMostMove = forwardMostMove - 2;
11944         whiteTimeRemaining = timeRemaining[0][currentMove];
11945         blackTimeRemaining = timeRemaining[1][currentMove];
11946         DisplayBothClocks();
11947         DisplayMove(currentMove - 1);
11948         ClearHighlights();/*!! could figure this out*/
11949         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11950         SendToProgram("remove\n", &first);
11951         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11952         break;
11953
11954       case BeginningOfGame:
11955       default:
11956         break;
11957
11958       case IcsPlayingWhite:
11959       case IcsPlayingBlack:
11960         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11961             SendToICS(ics_prefix);
11962             SendToICS("takeback 2\n");
11963         } else {
11964             SendToICS(ics_prefix);
11965             SendToICS("takeback 1\n");
11966         }
11967         break;
11968     }
11969 }
11970
11971 void
11972 MoveNowEvent()
11973 {
11974     ChessProgramState *cps;
11975
11976     switch (gameMode) {
11977       case MachinePlaysWhite:
11978         if (!WhiteOnMove(forwardMostMove)) {
11979             DisplayError(_("It is your turn"), 0);
11980             return;
11981         }
11982         cps = &first;
11983         break;
11984       case MachinePlaysBlack:
11985         if (WhiteOnMove(forwardMostMove)) {
11986             DisplayError(_("It is your turn"), 0);
11987             return;
11988         }
11989         cps = &first;
11990         break;
11991       case TwoMachinesPlay:
11992         if (WhiteOnMove(forwardMostMove) ==
11993             (first.twoMachinesColor[0] == 'w')) {
11994             cps = &first;
11995         } else {
11996             cps = &second;
11997         }
11998         break;
11999       case BeginningOfGame:
12000       default:
12001         return;
12002     }
12003     SendToProgram("?\n", cps);
12004 }
12005
12006 void
12007 TruncateGameEvent()
12008 {
12009     EditGameEvent();
12010     if (gameMode != EditGame) return;
12011     TruncateGame();
12012 }
12013
12014 void
12015 TruncateGame()
12016 {
12017     if (forwardMostMove > currentMove) {
12018         if (gameInfo.resultDetails != NULL) {
12019             free(gameInfo.resultDetails);
12020             gameInfo.resultDetails = NULL;
12021             gameInfo.result = GameUnfinished;
12022         }
12023         forwardMostMove = currentMove;
12024         HistorySet(parseList, backwardMostMove, forwardMostMove,
12025                    currentMove-1);
12026     }
12027 }
12028
12029 void
12030 HintEvent()
12031 {
12032     if (appData.noChessProgram) return;
12033     switch (gameMode) {
12034       case MachinePlaysWhite:
12035         if (WhiteOnMove(forwardMostMove)) {
12036             DisplayError(_("Wait until your turn"), 0);
12037             return;
12038         }
12039         break;
12040       case BeginningOfGame:
12041       case MachinePlaysBlack:
12042         if (!WhiteOnMove(forwardMostMove)) {
12043             DisplayError(_("Wait until your turn"), 0);
12044             return;
12045         }
12046         break;
12047       default:
12048         DisplayError(_("No hint available"), 0);
12049         return;
12050     }
12051     SendToProgram("hint\n", &first);
12052     hintRequested = TRUE;
12053 }
12054
12055 void
12056 BookEvent()
12057 {
12058     if (appData.noChessProgram) return;
12059     switch (gameMode) {
12060       case MachinePlaysWhite:
12061         if (WhiteOnMove(forwardMostMove)) {
12062             DisplayError(_("Wait until your turn"), 0);
12063             return;
12064         }
12065         break;
12066       case BeginningOfGame:
12067       case MachinePlaysBlack:
12068         if (!WhiteOnMove(forwardMostMove)) {
12069             DisplayError(_("Wait until your turn"), 0);
12070             return;
12071         }
12072         break;
12073       case EditPosition:
12074         EditPositionDone(TRUE);
12075         break;
12076       case TwoMachinesPlay:
12077         return;
12078       default:
12079         break;
12080     }
12081     SendToProgram("bk\n", &first);
12082     bookOutput[0] = NULLCHAR;
12083     bookRequested = TRUE;
12084 }
12085
12086 void
12087 AboutGameEvent()
12088 {
12089     char *tags = PGNTags(&gameInfo);
12090     TagsPopUp(tags, CmailMsg());
12091     free(tags);
12092 }
12093
12094 /* end button procedures */
12095
12096 void
12097 PrintPosition(fp, move)
12098      FILE *fp;
12099      int move;
12100 {
12101     int i, j;
12102     
12103     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12104         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12105             char c = PieceToChar(boards[move][i][j]);
12106             fputc(c == 'x' ? '.' : c, fp);
12107             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12108         }
12109     }
12110     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12111       fprintf(fp, "white to play\n");
12112     else
12113       fprintf(fp, "black to play\n");
12114 }
12115
12116 void
12117 PrintOpponents(fp)
12118      FILE *fp;
12119 {
12120     if (gameInfo.white != NULL) {
12121         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12122     } else {
12123         fprintf(fp, "\n");
12124     }
12125 }
12126
12127 /* Find last component of program's own name, using some heuristics */
12128 void
12129 TidyProgramName(prog, host, buf)
12130      char *prog, *host, buf[MSG_SIZ];
12131 {
12132     char *p, *q;
12133     int local = (strcmp(host, "localhost") == 0);
12134     while (!local && (p = strchr(prog, ';')) != NULL) {
12135         p++;
12136         while (*p == ' ') p++;
12137         prog = p;
12138     }
12139     if (*prog == '"' || *prog == '\'') {
12140         q = strchr(prog + 1, *prog);
12141     } else {
12142         q = strchr(prog, ' ');
12143     }
12144     if (q == NULL) q = prog + strlen(prog);
12145     p = q;
12146     while (p >= prog && *p != '/' && *p != '\\') p--;
12147     p++;
12148     if(p == prog && *p == '"') p++;
12149     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12150     memcpy(buf, p, q - p);
12151     buf[q - p] = NULLCHAR;
12152     if (!local) {
12153         strcat(buf, "@");
12154         strcat(buf, host);
12155     }
12156 }
12157
12158 char *
12159 TimeControlTagValue()
12160 {
12161     char buf[MSG_SIZ];
12162     if (!appData.clockMode) {
12163         strcpy(buf, "-");
12164     } else if (movesPerSession > 0) {
12165         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12166     } else if (timeIncrement == 0) {
12167         sprintf(buf, "%ld", timeControl/1000);
12168     } else {
12169         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12170     }
12171     return StrSave(buf);
12172 }
12173
12174 void
12175 SetGameInfo()
12176 {
12177     /* This routine is used only for certain modes */
12178     VariantClass v = gameInfo.variant;
12179     ClearGameInfo(&gameInfo);
12180     gameInfo.variant = v;
12181
12182     switch (gameMode) {
12183       case MachinePlaysWhite:
12184         gameInfo.event = StrSave( appData.pgnEventHeader );
12185         gameInfo.site = StrSave(HostName());
12186         gameInfo.date = PGNDate();
12187         gameInfo.round = StrSave("-");
12188         gameInfo.white = StrSave(first.tidy);
12189         gameInfo.black = StrSave(UserName());
12190         gameInfo.timeControl = TimeControlTagValue();
12191         break;
12192
12193       case MachinePlaysBlack:
12194         gameInfo.event = StrSave( appData.pgnEventHeader );
12195         gameInfo.site = StrSave(HostName());
12196         gameInfo.date = PGNDate();
12197         gameInfo.round = StrSave("-");
12198         gameInfo.white = StrSave(UserName());
12199         gameInfo.black = StrSave(first.tidy);
12200         gameInfo.timeControl = TimeControlTagValue();
12201         break;
12202
12203       case TwoMachinesPlay:
12204         gameInfo.event = StrSave( appData.pgnEventHeader );
12205         gameInfo.site = StrSave(HostName());
12206         gameInfo.date = PGNDate();
12207         if (matchGame > 0) {
12208             char buf[MSG_SIZ];
12209             sprintf(buf, "%d", matchGame);
12210             gameInfo.round = StrSave(buf);
12211         } else {
12212             gameInfo.round = StrSave("-");
12213         }
12214         if (first.twoMachinesColor[0] == 'w') {
12215             gameInfo.white = StrSave(first.tidy);
12216             gameInfo.black = StrSave(second.tidy);
12217         } else {
12218             gameInfo.white = StrSave(second.tidy);
12219             gameInfo.black = StrSave(first.tidy);
12220         }
12221         gameInfo.timeControl = TimeControlTagValue();
12222         break;
12223
12224       case EditGame:
12225         gameInfo.event = StrSave("Edited game");
12226         gameInfo.site = StrSave(HostName());
12227         gameInfo.date = PGNDate();
12228         gameInfo.round = StrSave("-");
12229         gameInfo.white = StrSave("-");
12230         gameInfo.black = StrSave("-");
12231         break;
12232
12233       case EditPosition:
12234         gameInfo.event = StrSave("Edited position");
12235         gameInfo.site = StrSave(HostName());
12236         gameInfo.date = PGNDate();
12237         gameInfo.round = StrSave("-");
12238         gameInfo.white = StrSave("-");
12239         gameInfo.black = StrSave("-");
12240         break;
12241
12242       case IcsPlayingWhite:
12243       case IcsPlayingBlack:
12244       case IcsObserving:
12245       case IcsExamining:
12246         break;
12247
12248       case PlayFromGameFile:
12249         gameInfo.event = StrSave("Game from non-PGN file");
12250         gameInfo.site = StrSave(HostName());
12251         gameInfo.date = PGNDate();
12252         gameInfo.round = StrSave("-");
12253         gameInfo.white = StrSave("?");
12254         gameInfo.black = StrSave("?");
12255         break;
12256
12257       default:
12258         break;
12259     }
12260 }
12261
12262 void
12263 ReplaceComment(index, text)
12264      int index;
12265      char *text;
12266 {
12267     int len;
12268
12269     while (*text == '\n') text++;
12270     len = strlen(text);
12271     while (len > 0 && text[len - 1] == '\n') len--;
12272
12273     if (commentList[index] != NULL)
12274       free(commentList[index]);
12275
12276     if (len == 0) {
12277         commentList[index] = NULL;
12278         return;
12279     }
12280     commentList[index] = (char *) malloc(len + 2);
12281     strncpy(commentList[index], text, len);
12282     commentList[index][len] = '\n';
12283     commentList[index][len + 1] = NULLCHAR;
12284 }
12285
12286 void
12287 CrushCRs(text)
12288      char *text;
12289 {
12290   char *p = text;
12291   char *q = text;
12292   char ch;
12293
12294   do {
12295     ch = *p++;
12296     if (ch == '\r') continue;
12297     *q++ = ch;
12298   } while (ch != '\0');
12299 }
12300
12301 void
12302 AppendComment(index, text)
12303      int index;
12304      char *text;
12305 {
12306     int oldlen, len;
12307     char *old;
12308
12309     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12310
12311     CrushCRs(text);
12312     while (*text == '\n') text++;
12313     len = strlen(text);
12314     while (len > 0 && text[len - 1] == '\n') len--;
12315
12316     if (len == 0) return;
12317
12318     if (commentList[index] != NULL) {
12319         old = commentList[index];
12320         oldlen = strlen(old);
12321         commentList[index] = (char *) malloc(oldlen + len + 2);
12322         strcpy(commentList[index], old);
12323         free(old);
12324         strncpy(&commentList[index][oldlen], text, len);
12325         commentList[index][oldlen + len] = '\n';
12326         commentList[index][oldlen + len + 1] = NULLCHAR;
12327     } else {
12328         commentList[index] = (char *) malloc(len + 2);
12329         strncpy(commentList[index], text, len);
12330         commentList[index][len] = '\n';
12331         commentList[index][len + 1] = NULLCHAR;
12332     }
12333 }
12334
12335 static char * FindStr( char * text, char * sub_text )
12336 {
12337     char * result = strstr( text, sub_text );
12338
12339     if( result != NULL ) {
12340         result += strlen( sub_text );
12341     }
12342
12343     return result;
12344 }
12345
12346 /* [AS] Try to extract PV info from PGN comment */
12347 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12348 char *GetInfoFromComment( int index, char * text )
12349 {
12350     char * sep = text;
12351
12352     if( text != NULL && index > 0 ) {
12353         int score = 0;
12354         int depth = 0;
12355         int time = -1, sec = 0, deci;
12356         char * s_eval = FindStr( text, "[%eval " );
12357         char * s_emt = FindStr( text, "[%emt " );
12358
12359         if( s_eval != NULL || s_emt != NULL ) {
12360             /* New style */
12361             char delim;
12362
12363             if( s_eval != NULL ) {
12364                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12365                     return text;
12366                 }
12367
12368                 if( delim != ']' ) {
12369                     return text;
12370                 }
12371             }
12372
12373             if( s_emt != NULL ) {
12374             }
12375         }
12376         else {
12377             /* We expect something like: [+|-]nnn.nn/dd */
12378             int score_lo = 0;
12379
12380             sep = strchr( text, '/' );
12381             if( sep == NULL || sep < (text+4) ) {
12382                 return text;
12383             }
12384
12385             time = -1; sec = -1; deci = -1;
12386             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12387                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12388                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12389                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12390                 return text;
12391             }
12392
12393             if( score_lo < 0 || score_lo >= 100 ) {
12394                 return text;
12395             }
12396
12397             if(sec >= 0) time = 600*time + 10*sec; else
12398             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12399
12400             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12401
12402             /* [HGM] PV time: now locate end of PV info */
12403             while( *++sep >= '0' && *sep <= '9'); // strip depth
12404             if(time >= 0)
12405             while( *++sep >= '0' && *sep <= '9'); // strip time
12406             if(sec >= 0)
12407             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12408             if(deci >= 0)
12409             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12410             while(*sep == ' ') sep++;
12411         }
12412
12413         if( depth <= 0 ) {
12414             return text;
12415         }
12416
12417         if( time < 0 ) {
12418             time = -1;
12419         }
12420
12421         pvInfoList[index-1].depth = depth;
12422         pvInfoList[index-1].score = score;
12423         pvInfoList[index-1].time  = 10*time; // centi-sec
12424     }
12425     return sep;
12426 }
12427
12428 void
12429 SendToProgram(message, cps)
12430      char *message;
12431      ChessProgramState *cps;
12432 {
12433     int count, outCount, error;
12434     char buf[MSG_SIZ];
12435
12436     if (cps->pr == NULL) return;
12437     Attention(cps);
12438     
12439     if (appData.debugMode) {
12440         TimeMark now;
12441         GetTimeMark(&now);
12442         fprintf(debugFP, "%ld >%-6s: %s", 
12443                 SubtractTimeMarks(&now, &programStartTime),
12444                 cps->which, message);
12445     }
12446     
12447     count = strlen(message);
12448     outCount = OutputToProcess(cps->pr, message, count, &error);
12449     if (outCount < count && !exiting 
12450                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12451         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12452         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12453             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12454                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12455                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12456             } else {
12457                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12458             }
12459             gameInfo.resultDetails = StrSave(buf);
12460         }
12461         DisplayFatalError(buf, error, 1);
12462     }
12463 }
12464
12465 void
12466 ReceiveFromProgram(isr, closure, message, count, error)
12467      InputSourceRef isr;
12468      VOIDSTAR closure;
12469      char *message;
12470      int count;
12471      int error;
12472 {
12473     char *end_str;
12474     char buf[MSG_SIZ];
12475     ChessProgramState *cps = (ChessProgramState *)closure;
12476
12477     if (isr != cps->isr) return; /* Killed intentionally */
12478     if (count <= 0) {
12479         if (count == 0) {
12480             sprintf(buf,
12481                     _("Error: %s chess program (%s) exited unexpectedly"),
12482                     cps->which, cps->program);
12483         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12484                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12485                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12486                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12487                 } else {
12488                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12489                 }
12490                 gameInfo.resultDetails = StrSave(buf);
12491             }
12492             RemoveInputSource(cps->isr);
12493             DisplayFatalError(buf, 0, 1);
12494         } else {
12495             sprintf(buf,
12496                     _("Error reading from %s chess program (%s)"),
12497                     cps->which, cps->program);
12498             RemoveInputSource(cps->isr);
12499
12500             /* [AS] Program is misbehaving badly... kill it */
12501             if( count == -2 ) {
12502                 DestroyChildProcess( cps->pr, 9 );
12503                 cps->pr = NoProc;
12504             }
12505
12506             DisplayFatalError(buf, error, 1);
12507         }
12508         return;
12509     }
12510     
12511     if ((end_str = strchr(message, '\r')) != NULL)
12512       *end_str = NULLCHAR;
12513     if ((end_str = strchr(message, '\n')) != NULL)
12514       *end_str = NULLCHAR;
12515     
12516     if (appData.debugMode) {
12517         TimeMark now; int print = 1;
12518         char *quote = ""; char c; int i;
12519
12520         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12521                 char start = message[0];
12522                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12523                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12524                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12525                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12526                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12527                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12528                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12529                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12530                         { quote = "# "; print = (appData.engineComments == 2); }
12531                 message[0] = start; // restore original message
12532         }
12533         if(print) {
12534                 GetTimeMark(&now);
12535                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12536                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12537                         quote,
12538                         message);
12539         }
12540     }
12541
12542     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12543     if (appData.icsEngineAnalyze) {
12544         if (strstr(message, "whisper") != NULL ||
12545              strstr(message, "kibitz") != NULL || 
12546             strstr(message, "tellics") != NULL) return;
12547     }
12548
12549     HandleMachineMove(message, cps);
12550 }
12551
12552
12553 void
12554 SendTimeControl(cps, mps, tc, inc, sd, st)
12555      ChessProgramState *cps;
12556      int mps, inc, sd, st;
12557      long tc;
12558 {
12559     char buf[MSG_SIZ];
12560     int seconds;
12561
12562     if( timeControl_2 > 0 ) {
12563         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12564             tc = timeControl_2;
12565         }
12566     }
12567     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12568     inc /= cps->timeOdds;
12569     st  /= cps->timeOdds;
12570
12571     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12572
12573     if (st > 0) {
12574       /* Set exact time per move, normally using st command */
12575       if (cps->stKludge) {
12576         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12577         seconds = st % 60;
12578         if (seconds == 0) {
12579           sprintf(buf, "level 1 %d\n", st/60);
12580         } else {
12581           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12582         }
12583       } else {
12584         sprintf(buf, "st %d\n", st);
12585       }
12586     } else {
12587       /* Set conventional or incremental time control, using level command */
12588       if (seconds == 0) {
12589         /* Note old gnuchess bug -- minutes:seconds used to not work.
12590            Fixed in later versions, but still avoid :seconds
12591            when seconds is 0. */
12592         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12593       } else {
12594         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12595                 seconds, inc/1000);
12596       }
12597     }
12598     SendToProgram(buf, cps);
12599
12600     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12601     /* Orthogonally, limit search to given depth */
12602     if (sd > 0) {
12603       if (cps->sdKludge) {
12604         sprintf(buf, "depth\n%d\n", sd);
12605       } else {
12606         sprintf(buf, "sd %d\n", sd);
12607       }
12608       SendToProgram(buf, cps);
12609     }
12610
12611     if(cps->nps > 0) { /* [HGM] nps */
12612         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12613         else {
12614                 sprintf(buf, "nps %d\n", cps->nps);
12615               SendToProgram(buf, cps);
12616         }
12617     }
12618 }
12619
12620 ChessProgramState *WhitePlayer()
12621 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12622 {
12623     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12624        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12625         return &second;
12626     return &first;
12627 }
12628
12629 void
12630 SendTimeRemaining(cps, machineWhite)
12631      ChessProgramState *cps;
12632      int /*boolean*/ machineWhite;
12633 {
12634     char message[MSG_SIZ];
12635     long time, otime;
12636
12637     /* Note: this routine must be called when the clocks are stopped
12638        or when they have *just* been set or switched; otherwise
12639        it will be off by the time since the current tick started.
12640     */
12641     if (machineWhite) {
12642         time = whiteTimeRemaining / 10;
12643         otime = blackTimeRemaining / 10;
12644     } else {
12645         time = blackTimeRemaining / 10;
12646         otime = whiteTimeRemaining / 10;
12647     }
12648     /* [HGM] translate opponent's time by time-odds factor */
12649     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12650     if (appData.debugMode) {
12651         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12652     }
12653
12654     if (time <= 0) time = 1;
12655     if (otime <= 0) otime = 1;
12656     
12657     sprintf(message, "time %ld\n", time);
12658     SendToProgram(message, cps);
12659
12660     sprintf(message, "otim %ld\n", otime);
12661     SendToProgram(message, cps);
12662 }
12663
12664 int
12665 BoolFeature(p, name, loc, cps)
12666      char **p;
12667      char *name;
12668      int *loc;
12669      ChessProgramState *cps;
12670 {
12671   char buf[MSG_SIZ];
12672   int len = strlen(name);
12673   int val;
12674   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12675     (*p) += len + 1;
12676     sscanf(*p, "%d", &val);
12677     *loc = (val != 0);
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 IntFeature(p, name, loc, cps)
12688      char **p;
12689      char *name;
12690      int *loc;
12691      ChessProgramState *cps;
12692 {
12693   char buf[MSG_SIZ];
12694   int len = strlen(name);
12695   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12696     (*p) += len + 1;
12697     sscanf(*p, "%d", loc);
12698     while (**p && **p != ' ') (*p)++;
12699     sprintf(buf, "accepted %s\n", name);
12700     SendToProgram(buf, cps);
12701     return TRUE;
12702   }
12703   return FALSE;
12704 }
12705
12706 int
12707 StringFeature(p, name, loc, cps)
12708      char **p;
12709      char *name;
12710      char loc[];
12711      ChessProgramState *cps;
12712 {
12713   char buf[MSG_SIZ];
12714   int len = strlen(name);
12715   if (strncmp((*p), name, len) == 0
12716       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12717     (*p) += len + 2;
12718     sscanf(*p, "%[^\"]", loc);
12719     while (**p && **p != '\"') (*p)++;
12720     if (**p == '\"') (*p)++;
12721     sprintf(buf, "accepted %s\n", name);
12722     SendToProgram(buf, cps);
12723     return TRUE;
12724   }
12725   return FALSE;
12726 }
12727
12728 int 
12729 ParseOption(Option *opt, ChessProgramState *cps)
12730 // [HGM] options: process the string that defines an engine option, and determine
12731 // name, type, default value, and allowed value range
12732 {
12733         char *p, *q, buf[MSG_SIZ];
12734         int n, min = (-1)<<31, max = 1<<31, def;
12735
12736         if(p = strstr(opt->name, " -spin ")) {
12737             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12738             if(max < min) max = min; // enforce consistency
12739             if(def < min) def = min;
12740             if(def > max) def = max;
12741             opt->value = def;
12742             opt->min = min;
12743             opt->max = max;
12744             opt->type = Spin;
12745         } else if((p = strstr(opt->name, " -slider "))) {
12746             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12747             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12748             if(max < min) max = min; // enforce consistency
12749             if(def < min) def = min;
12750             if(def > max) def = max;
12751             opt->value = def;
12752             opt->min = min;
12753             opt->max = max;
12754             opt->type = Spin; // Slider;
12755         } else if((p = strstr(opt->name, " -string "))) {
12756             opt->textValue = p+9;
12757             opt->type = TextBox;
12758         } else if((p = strstr(opt->name, " -file "))) {
12759             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12760             opt->textValue = p+7;
12761             opt->type = TextBox; // FileName;
12762         } else if((p = strstr(opt->name, " -path "))) {
12763             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12764             opt->textValue = p+7;
12765             opt->type = TextBox; // PathName;
12766         } else if(p = strstr(opt->name, " -check ")) {
12767             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12768             opt->value = (def != 0);
12769             opt->type = CheckBox;
12770         } else if(p = strstr(opt->name, " -combo ")) {
12771             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12772             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12773             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12774             opt->value = n = 0;
12775             while(q = StrStr(q, " /// ")) {
12776                 n++; *q = 0;    // count choices, and null-terminate each of them
12777                 q += 5;
12778                 if(*q == '*') { // remember default, which is marked with * prefix
12779                     q++;
12780                     opt->value = n;
12781                 }
12782                 cps->comboList[cps->comboCnt++] = q;
12783             }
12784             cps->comboList[cps->comboCnt++] = NULL;
12785             opt->max = n + 1;
12786             opt->type = ComboBox;
12787         } else if(p = strstr(opt->name, " -button")) {
12788             opt->type = Button;
12789         } else if(p = strstr(opt->name, " -save")) {
12790             opt->type = SaveButton;
12791         } else return FALSE;
12792         *p = 0; // terminate option name
12793         // now look if the command-line options define a setting for this engine option.
12794         if(cps->optionSettings && cps->optionSettings[0])
12795             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12796         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12797                 sprintf(buf, "option %s", p);
12798                 if(p = strstr(buf, ",")) *p = 0;
12799                 strcat(buf, "\n");
12800                 SendToProgram(buf, cps);
12801         }
12802         return TRUE;
12803 }
12804
12805 void
12806 FeatureDone(cps, val)
12807      ChessProgramState* cps;
12808      int val;
12809 {
12810   DelayedEventCallback cb = GetDelayedEvent();
12811   if ((cb == InitBackEnd3 && cps == &first) ||
12812       (cb == TwoMachinesEventIfReady && cps == &second)) {
12813     CancelDelayedEvent();
12814     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12815   }
12816   cps->initDone = val;
12817 }
12818
12819 /* Parse feature command from engine */
12820 void
12821 ParseFeatures(args, cps)
12822      char* args;
12823      ChessProgramState *cps;  
12824 {
12825   char *p = args;
12826   char *q;
12827   int val;
12828   char buf[MSG_SIZ];
12829
12830   for (;;) {
12831     while (*p == ' ') p++;
12832     if (*p == NULLCHAR) return;
12833
12834     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12835     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12836     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12837     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12838     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12839     if (BoolFeature(&p, "reuse", &val, cps)) {
12840       /* Engine can disable reuse, but can't enable it if user said no */
12841       if (!val) cps->reuse = FALSE;
12842       continue;
12843     }
12844     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12845     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12846       if (gameMode == TwoMachinesPlay) {
12847         DisplayTwoMachinesTitle();
12848       } else {
12849         DisplayTitle("");
12850       }
12851       continue;
12852     }
12853     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12854     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12855     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12856     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12857     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12858     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12859     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12860     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12861     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12862     if (IntFeature(&p, "done", &val, cps)) {
12863       FeatureDone(cps, val);
12864       continue;
12865     }
12866     /* Added by Tord: */
12867     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12868     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12869     /* End of additions by Tord */
12870
12871     /* [HGM] added features: */
12872     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12873     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12874     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12875     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12876     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12877     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12878     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12879         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12880             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12881             SendToProgram(buf, cps);
12882             continue;
12883         }
12884         if(cps->nrOptions >= MAX_OPTIONS) {
12885             cps->nrOptions--;
12886             sprintf(buf, "%s engine has too many options\n", cps->which);
12887             DisplayError(buf, 0);
12888         }
12889         continue;
12890     }
12891     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12892     /* End of additions by HGM */
12893
12894     /* unknown feature: complain and skip */
12895     q = p;
12896     while (*q && *q != '=') q++;
12897     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12898     SendToProgram(buf, cps);
12899     p = q;
12900     if (*p == '=') {
12901       p++;
12902       if (*p == '\"') {
12903         p++;
12904         while (*p && *p != '\"') p++;
12905         if (*p == '\"') p++;
12906       } else {
12907         while (*p && *p != ' ') p++;
12908       }
12909     }
12910   }
12911
12912 }
12913
12914 void
12915 PeriodicUpdatesEvent(newState)
12916      int newState;
12917 {
12918     if (newState == appData.periodicUpdates)
12919       return;
12920
12921     appData.periodicUpdates=newState;
12922
12923     /* Display type changes, so update it now */
12924 //    DisplayAnalysis();
12925
12926     /* Get the ball rolling again... */
12927     if (newState) {
12928         AnalysisPeriodicEvent(1);
12929         StartAnalysisClock();
12930     }
12931 }
12932
12933 void
12934 PonderNextMoveEvent(newState)
12935      int newState;
12936 {
12937     if (newState == appData.ponderNextMove) return;
12938     if (gameMode == EditPosition) EditPositionDone(TRUE);
12939     if (newState) {
12940         SendToProgram("hard\n", &first);
12941         if (gameMode == TwoMachinesPlay) {
12942             SendToProgram("hard\n", &second);
12943         }
12944     } else {
12945         SendToProgram("easy\n", &first);
12946         thinkOutput[0] = NULLCHAR;
12947         if (gameMode == TwoMachinesPlay) {
12948             SendToProgram("easy\n", &second);
12949         }
12950     }
12951     appData.ponderNextMove = newState;
12952 }
12953
12954 void
12955 NewSettingEvent(option, command, value)
12956      char *command;
12957      int option, value;
12958 {
12959     char buf[MSG_SIZ];
12960
12961     if (gameMode == EditPosition) EditPositionDone(TRUE);
12962     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12963     SendToProgram(buf, &first);
12964     if (gameMode == TwoMachinesPlay) {
12965         SendToProgram(buf, &second);
12966     }
12967 }
12968
12969 void
12970 ShowThinkingEvent()
12971 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12972 {
12973     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12974     int newState = appData.showThinking
12975         // [HGM] thinking: other features now need thinking output as well
12976         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12977     
12978     if (oldState == newState) return;
12979     oldState = newState;
12980     if (gameMode == EditPosition) EditPositionDone(TRUE);
12981     if (oldState) {
12982         SendToProgram("post\n", &first);
12983         if (gameMode == TwoMachinesPlay) {
12984             SendToProgram("post\n", &second);
12985         }
12986     } else {
12987         SendToProgram("nopost\n", &first);
12988         thinkOutput[0] = NULLCHAR;
12989         if (gameMode == TwoMachinesPlay) {
12990             SendToProgram("nopost\n", &second);
12991         }
12992     }
12993 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12994 }
12995
12996 void
12997 AskQuestionEvent(title, question, replyPrefix, which)
12998      char *title; char *question; char *replyPrefix; char *which;
12999 {
13000   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13001   if (pr == NoProc) return;
13002   AskQuestion(title, question, replyPrefix, pr);
13003 }
13004
13005 void
13006 DisplayMove(moveNumber)
13007      int moveNumber;
13008 {
13009     char message[MSG_SIZ];
13010     char res[MSG_SIZ];
13011     char cpThinkOutput[MSG_SIZ];
13012
13013     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13014     
13015     if (moveNumber == forwardMostMove - 1 || 
13016         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13017
13018         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13019
13020         if (strchr(cpThinkOutput, '\n')) {
13021             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13022         }
13023     } else {
13024         *cpThinkOutput = NULLCHAR;
13025     }
13026
13027     /* [AS] Hide thinking from human user */
13028     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13029         *cpThinkOutput = NULLCHAR;
13030         if( thinkOutput[0] != NULLCHAR ) {
13031             int i;
13032
13033             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13034                 cpThinkOutput[i] = '.';
13035             }
13036             cpThinkOutput[i] = NULLCHAR;
13037             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13038         }
13039     }
13040
13041     if (moveNumber == forwardMostMove - 1 &&
13042         gameInfo.resultDetails != NULL) {
13043         if (gameInfo.resultDetails[0] == NULLCHAR) {
13044             sprintf(res, " %s", PGNResult(gameInfo.result));
13045         } else {
13046             sprintf(res, " {%s} %s",
13047                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13048         }
13049     } else {
13050         res[0] = NULLCHAR;
13051     }
13052
13053     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13054         DisplayMessage(res, cpThinkOutput);
13055     } else {
13056         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13057                 WhiteOnMove(moveNumber) ? " " : ".. ",
13058                 parseList[moveNumber], res);
13059         DisplayMessage(message, cpThinkOutput);
13060     }
13061 }
13062
13063 void
13064 DisplayComment(moveNumber, text)
13065      int moveNumber;
13066      char *text;
13067 {
13068     char title[MSG_SIZ];
13069     char buf[8000]; // comment can be long!
13070     int score, depth;
13071     
13072     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13073       strcpy(title, "Comment");
13074     } else {
13075       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13076               WhiteOnMove(moveNumber) ? " " : ".. ",
13077               parseList[moveNumber]);
13078     }
13079     // [HGM] PV info: display PV info together with (or as) comment
13080     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13081       if(text == NULL) text = "";                                           
13082       score = pvInfoList[moveNumber].score;
13083       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13084               depth, (pvInfoList[moveNumber].time+50)/100, text);
13085       text = buf;
13086     }
13087     if (text != NULL && (appData.autoDisplayComment || commentUp))
13088         CommentPopUp(title, text);
13089 }
13090
13091 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13092  * might be busy thinking or pondering.  It can be omitted if your
13093  * gnuchess is configured to stop thinking immediately on any user
13094  * input.  However, that gnuchess feature depends on the FIONREAD
13095  * ioctl, which does not work properly on some flavors of Unix.
13096  */
13097 void
13098 Attention(cps)
13099      ChessProgramState *cps;
13100 {
13101 #if ATTENTION
13102     if (!cps->useSigint) return;
13103     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13104     switch (gameMode) {
13105       case MachinePlaysWhite:
13106       case MachinePlaysBlack:
13107       case TwoMachinesPlay:
13108       case IcsPlayingWhite:
13109       case IcsPlayingBlack:
13110       case AnalyzeMode:
13111       case AnalyzeFile:
13112         /* Skip if we know it isn't thinking */
13113         if (!cps->maybeThinking) return;
13114         if (appData.debugMode)
13115           fprintf(debugFP, "Interrupting %s\n", cps->which);
13116         InterruptChildProcess(cps->pr);
13117         cps->maybeThinking = FALSE;
13118         break;
13119       default:
13120         break;
13121     }
13122 #endif /*ATTENTION*/
13123 }
13124
13125 int
13126 CheckFlags()
13127 {
13128     if (whiteTimeRemaining <= 0) {
13129         if (!whiteFlag) {
13130             whiteFlag = TRUE;
13131             if (appData.icsActive) {
13132                 if (appData.autoCallFlag &&
13133                     gameMode == IcsPlayingBlack && !blackFlag) {
13134                   SendToICS(ics_prefix);
13135                   SendToICS("flag\n");
13136                 }
13137             } else {
13138                 if (blackFlag) {
13139                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13140                 } else {
13141                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13142                     if (appData.autoCallFlag) {
13143                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13144                         return TRUE;
13145                     }
13146                 }
13147             }
13148         }
13149     }
13150     if (blackTimeRemaining <= 0) {
13151         if (!blackFlag) {
13152             blackFlag = TRUE;
13153             if (appData.icsActive) {
13154                 if (appData.autoCallFlag &&
13155                     gameMode == IcsPlayingWhite && !whiteFlag) {
13156                   SendToICS(ics_prefix);
13157                   SendToICS("flag\n");
13158                 }
13159             } else {
13160                 if (whiteFlag) {
13161                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13162                 } else {
13163                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13164                     if (appData.autoCallFlag) {
13165                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13166                         return TRUE;
13167                     }
13168                 }
13169             }
13170         }
13171     }
13172     return FALSE;
13173 }
13174
13175 void
13176 CheckTimeControl()
13177 {
13178     if (!appData.clockMode || appData.icsActive ||
13179         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13180
13181     /*
13182      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13183      */
13184     if ( !WhiteOnMove(forwardMostMove) )
13185         /* White made time control */
13186         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13187         /* [HGM] time odds: correct new time quota for time odds! */
13188                                             / WhitePlayer()->timeOdds;
13189       else
13190         /* Black made time control */
13191         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13192                                             / WhitePlayer()->other->timeOdds;
13193 }
13194
13195 void
13196 DisplayBothClocks()
13197 {
13198     int wom = gameMode == EditPosition ?
13199       !blackPlaysFirst : WhiteOnMove(currentMove);
13200     DisplayWhiteClock(whiteTimeRemaining, wom);
13201     DisplayBlackClock(blackTimeRemaining, !wom);
13202 }
13203
13204
13205 /* Timekeeping seems to be a portability nightmare.  I think everyone
13206    has ftime(), but I'm really not sure, so I'm including some ifdefs
13207    to use other calls if you don't.  Clocks will be less accurate if
13208    you have neither ftime nor gettimeofday.
13209 */
13210
13211 /* VS 2008 requires the #include outside of the function */
13212 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13213 #include <sys/timeb.h>
13214 #endif
13215
13216 /* Get the current time as a TimeMark */
13217 void
13218 GetTimeMark(tm)
13219      TimeMark *tm;
13220 {
13221 #if HAVE_GETTIMEOFDAY
13222
13223     struct timeval timeVal;
13224     struct timezone timeZone;
13225
13226     gettimeofday(&timeVal, &timeZone);
13227     tm->sec = (long) timeVal.tv_sec; 
13228     tm->ms = (int) (timeVal.tv_usec / 1000L);
13229
13230 #else /*!HAVE_GETTIMEOFDAY*/
13231 #if HAVE_FTIME
13232
13233 // include <sys/timeb.h> / moved to just above start of function
13234     struct timeb timeB;
13235
13236     ftime(&timeB);
13237     tm->sec = (long) timeB.time;
13238     tm->ms = (int) timeB.millitm;
13239
13240 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13241     tm->sec = (long) time(NULL);
13242     tm->ms = 0;
13243 #endif
13244 #endif
13245 }
13246
13247 /* Return the difference in milliseconds between two
13248    time marks.  We assume the difference will fit in a long!
13249 */
13250 long
13251 SubtractTimeMarks(tm2, tm1)
13252      TimeMark *tm2, *tm1;
13253 {
13254     return 1000L*(tm2->sec - tm1->sec) +
13255            (long) (tm2->ms - tm1->ms);
13256 }
13257
13258
13259 /*
13260  * Code to manage the game clocks.
13261  *
13262  * In tournament play, black starts the clock and then white makes a move.
13263  * We give the human user a slight advantage if he is playing white---the
13264  * clocks don't run until he makes his first move, so it takes zero time.
13265  * Also, we don't account for network lag, so we could get out of sync
13266  * with GNU Chess's clock -- but then, referees are always right.  
13267  */
13268
13269 static TimeMark tickStartTM;
13270 static long intendedTickLength;
13271
13272 long
13273 NextTickLength(timeRemaining)
13274      long timeRemaining;
13275 {
13276     long nominalTickLength, nextTickLength;
13277
13278     if (timeRemaining > 0L && timeRemaining <= 10000L)
13279       nominalTickLength = 100L;
13280     else
13281       nominalTickLength = 1000L;
13282     nextTickLength = timeRemaining % nominalTickLength;
13283     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13284
13285     return nextTickLength;
13286 }
13287
13288 /* Adjust clock one minute up or down */
13289 void
13290 AdjustClock(Boolean which, int dir)
13291 {
13292     if(which) blackTimeRemaining += 60000*dir;
13293     else      whiteTimeRemaining += 60000*dir;
13294     DisplayBothClocks();
13295 }
13296
13297 /* Stop clocks and reset to a fresh time control */
13298 void
13299 ResetClocks() 
13300 {
13301     (void) StopClockTimer();
13302     if (appData.icsActive) {
13303         whiteTimeRemaining = blackTimeRemaining = 0;
13304     } else { /* [HGM] correct new time quote for time odds */
13305         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13306         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13307     }
13308     if (whiteFlag || blackFlag) {
13309         DisplayTitle("");
13310         whiteFlag = blackFlag = FALSE;
13311     }
13312     DisplayBothClocks();
13313 }
13314
13315 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13316
13317 /* Decrement running clock by amount of time that has passed */
13318 void
13319 DecrementClocks()
13320 {
13321     long timeRemaining;
13322     long lastTickLength, fudge;
13323     TimeMark now;
13324
13325     if (!appData.clockMode) return;
13326     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13327         
13328     GetTimeMark(&now);
13329
13330     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13331
13332     /* Fudge if we woke up a little too soon */
13333     fudge = intendedTickLength - lastTickLength;
13334     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13335
13336     if (WhiteOnMove(forwardMostMove)) {
13337         if(whiteNPS >= 0) lastTickLength = 0;
13338         timeRemaining = whiteTimeRemaining -= lastTickLength;
13339         DisplayWhiteClock(whiteTimeRemaining - fudge,
13340                           WhiteOnMove(currentMove));
13341     } else {
13342         if(blackNPS >= 0) lastTickLength = 0;
13343         timeRemaining = blackTimeRemaining -= lastTickLength;
13344         DisplayBlackClock(blackTimeRemaining - fudge,
13345                           !WhiteOnMove(currentMove));
13346     }
13347
13348     if (CheckFlags()) return;
13349         
13350     tickStartTM = now;
13351     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13352     StartClockTimer(intendedTickLength);
13353
13354     /* if the time remaining has fallen below the alarm threshold, sound the
13355      * alarm. if the alarm has sounded and (due to a takeback or time control
13356      * with increment) the time remaining has increased to a level above the
13357      * threshold, reset the alarm so it can sound again. 
13358      */
13359     
13360     if (appData.icsActive && appData.icsAlarm) {
13361
13362         /* make sure we are dealing with the user's clock */
13363         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13364                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13365            )) return;
13366
13367         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13368             alarmSounded = FALSE;
13369         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13370             PlayAlarmSound();
13371             alarmSounded = TRUE;
13372         }
13373     }
13374 }
13375
13376
13377 /* A player has just moved, so stop the previously running
13378    clock and (if in clock mode) start the other one.
13379    We redisplay both clocks in case we're in ICS mode, because
13380    ICS gives us an update to both clocks after every move.
13381    Note that this routine is called *after* forwardMostMove
13382    is updated, so the last fractional tick must be subtracted
13383    from the color that is *not* on move now.
13384 */
13385 void
13386 SwitchClocks(int newMoveNr)
13387 {
13388     long lastTickLength;
13389     TimeMark now;
13390     int flagged = FALSE;
13391
13392     GetTimeMark(&now);
13393
13394     if (StopClockTimer() && appData.clockMode) {
13395         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13396         if (!WhiteOnMove(forwardMostMove)) {
13397             if(blackNPS >= 0) lastTickLength = 0;
13398             blackTimeRemaining -= lastTickLength;
13399            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13400 //         if(pvInfoList[forwardMostMove-1].time == -1)
13401                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13402                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13403         } else {
13404            if(whiteNPS >= 0) lastTickLength = 0;
13405            whiteTimeRemaining -= lastTickLength;
13406            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13407 //         if(pvInfoList[forwardMostMove-1].time == -1)
13408                  pvInfoList[forwardMostMove-1].time = 
13409                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13410         }
13411         flagged = CheckFlags();
13412     }
13413     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
13414     CheckTimeControl();
13415
13416     if (flagged || !appData.clockMode) return;
13417
13418     switch (gameMode) {
13419       case MachinePlaysBlack:
13420       case MachinePlaysWhite:
13421       case BeginningOfGame:
13422         if (pausing) return;
13423         break;
13424
13425       case EditGame:
13426       case PlayFromGameFile:
13427       case IcsExamining:
13428         return;
13429
13430       default:
13431         break;
13432     }
13433
13434     tickStartTM = now;
13435     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13436       whiteTimeRemaining : blackTimeRemaining);
13437     StartClockTimer(intendedTickLength);
13438 }
13439         
13440
13441 /* Stop both clocks */
13442 void
13443 StopClocks()
13444 {       
13445     long lastTickLength;
13446     TimeMark now;
13447
13448     if (!StopClockTimer()) return;
13449     if (!appData.clockMode) return;
13450
13451     GetTimeMark(&now);
13452
13453     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13454     if (WhiteOnMove(forwardMostMove)) {
13455         if(whiteNPS >= 0) lastTickLength = 0;
13456         whiteTimeRemaining -= lastTickLength;
13457         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13458     } else {
13459         if(blackNPS >= 0) lastTickLength = 0;
13460         blackTimeRemaining -= lastTickLength;
13461         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13462     }
13463     CheckFlags();
13464 }
13465         
13466 /* Start clock of player on move.  Time may have been reset, so
13467    if clock is already running, stop and restart it. */
13468 void
13469 StartClocks()
13470 {
13471     (void) StopClockTimer(); /* in case it was running already */
13472     DisplayBothClocks();
13473     if (CheckFlags()) return;
13474
13475     if (!appData.clockMode) return;
13476     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13477
13478     GetTimeMark(&tickStartTM);
13479     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13480       whiteTimeRemaining : blackTimeRemaining);
13481
13482    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13483     whiteNPS = blackNPS = -1; 
13484     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13485        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13486         whiteNPS = first.nps;
13487     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13488        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13489         blackNPS = first.nps;
13490     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13491         whiteNPS = second.nps;
13492     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13493         blackNPS = second.nps;
13494     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13495
13496     StartClockTimer(intendedTickLength);
13497 }
13498
13499 char *
13500 TimeString(ms)
13501      long ms;
13502 {
13503     long second, minute, hour, day;
13504     char *sign = "";
13505     static char buf[32];
13506     
13507     if (ms > 0 && ms <= 9900) {
13508       /* convert milliseconds to tenths, rounding up */
13509       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13510
13511       sprintf(buf, " %03.1f ", tenths/10.0);
13512       return buf;
13513     }
13514
13515     /* convert milliseconds to seconds, rounding up */
13516     /* use floating point to avoid strangeness of integer division
13517        with negative dividends on many machines */
13518     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13519
13520     if (second < 0) {
13521         sign = "-";
13522         second = -second;
13523     }
13524     
13525     day = second / (60 * 60 * 24);
13526     second = second % (60 * 60 * 24);
13527     hour = second / (60 * 60);
13528     second = second % (60 * 60);
13529     minute = second / 60;
13530     second = second % 60;
13531     
13532     if (day > 0)
13533       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13534               sign, day, hour, minute, second);
13535     else if (hour > 0)
13536       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13537     else
13538       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13539     
13540     return buf;
13541 }
13542
13543
13544 /*
13545  * This is necessary because some C libraries aren't ANSI C compliant yet.
13546  */
13547 char *
13548 StrStr(string, match)
13549      char *string, *match;
13550 {
13551     int i, length;
13552     
13553     length = strlen(match);
13554     
13555     for (i = strlen(string) - length; i >= 0; i--, string++)
13556       if (!strncmp(match, string, length))
13557         return string;
13558     
13559     return NULL;
13560 }
13561
13562 char *
13563 StrCaseStr(string, match)
13564      char *string, *match;
13565 {
13566     int i, j, length;
13567     
13568     length = strlen(match);
13569     
13570     for (i = strlen(string) - length; i >= 0; i--, string++) {
13571         for (j = 0; j < length; j++) {
13572             if (ToLower(match[j]) != ToLower(string[j]))
13573               break;
13574         }
13575         if (j == length) return string;
13576     }
13577
13578     return NULL;
13579 }
13580
13581 #ifndef _amigados
13582 int
13583 StrCaseCmp(s1, s2)
13584      char *s1, *s2;
13585 {
13586     char c1, c2;
13587     
13588     for (;;) {
13589         c1 = ToLower(*s1++);
13590         c2 = ToLower(*s2++);
13591         if (c1 > c2) return 1;
13592         if (c1 < c2) return -1;
13593         if (c1 == NULLCHAR) return 0;
13594     }
13595 }
13596
13597
13598 int
13599 ToLower(c)
13600      int c;
13601 {
13602     return isupper(c) ? tolower(c) : c;
13603 }
13604
13605
13606 int
13607 ToUpper(c)
13608      int c;
13609 {
13610     return islower(c) ? toupper(c) : c;
13611 }
13612 #endif /* !_amigados    */
13613
13614 char *
13615 StrSave(s)
13616      char *s;
13617 {
13618     char *ret;
13619
13620     if ((ret = (char *) malloc(strlen(s) + 1))) {
13621         strcpy(ret, s);
13622     }
13623     return ret;
13624 }
13625
13626 char *
13627 StrSavePtr(s, savePtr)
13628      char *s, **savePtr;
13629 {
13630     if (*savePtr) {
13631         free(*savePtr);
13632     }
13633     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13634         strcpy(*savePtr, s);
13635     }
13636     return(*savePtr);
13637 }
13638
13639 char *
13640 PGNDate()
13641 {
13642     time_t clock;
13643     struct tm *tm;
13644     char buf[MSG_SIZ];
13645
13646     clock = time((time_t *)NULL);
13647     tm = localtime(&clock);
13648     sprintf(buf, "%04d.%02d.%02d",
13649             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13650     return StrSave(buf);
13651 }
13652
13653
13654 char *
13655 PositionToFEN(move, overrideCastling)
13656      int move;
13657      char *overrideCastling;
13658 {
13659     int i, j, fromX, fromY, toX, toY;
13660     int whiteToPlay;
13661     char buf[128];
13662     char *p, *q;
13663     int emptycount;
13664     ChessSquare piece;
13665
13666     whiteToPlay = (gameMode == EditPosition) ?
13667       !blackPlaysFirst : (move % 2 == 0);
13668     p = buf;
13669
13670     /* Piece placement data */
13671     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13672         emptycount = 0;
13673         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13674             if (boards[move][i][j] == EmptySquare) {
13675                 emptycount++;
13676             } else { ChessSquare piece = boards[move][i][j];
13677                 if (emptycount > 0) {
13678                     if(emptycount<10) /* [HGM] can be >= 10 */
13679                         *p++ = '0' + emptycount;
13680                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13681                     emptycount = 0;
13682                 }
13683                 if(PieceToChar(piece) == '+') {
13684                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13685                     *p++ = '+';
13686                     piece = (ChessSquare)(DEMOTED piece);
13687                 } 
13688                 *p++ = PieceToChar(piece);
13689                 if(p[-1] == '~') {
13690                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13691                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13692                     *p++ = '~';
13693                 }
13694             }
13695         }
13696         if (emptycount > 0) {
13697             if(emptycount<10) /* [HGM] can be >= 10 */
13698                 *p++ = '0' + emptycount;
13699             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13700             emptycount = 0;
13701         }
13702         *p++ = '/';
13703     }
13704     *(p - 1) = ' ';
13705
13706     /* [HGM] print Crazyhouse or Shogi holdings */
13707     if( gameInfo.holdingsWidth ) {
13708         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13709         q = p;
13710         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13711             piece = boards[move][i][BOARD_WIDTH-1];
13712             if( piece != EmptySquare )
13713               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13714                   *p++ = PieceToChar(piece);
13715         }
13716         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13717             piece = boards[move][BOARD_HEIGHT-i-1][0];
13718             if( piece != EmptySquare )
13719               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13720                   *p++ = PieceToChar(piece);
13721         }
13722
13723         if( q == p ) *p++ = '-';
13724         *p++ = ']';
13725         *p++ = ' ';
13726     }
13727
13728     /* Active color */
13729     *p++ = whiteToPlay ? 'w' : 'b';
13730     *p++ = ' ';
13731
13732   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13733     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13734   } else {
13735   if(nrCastlingRights) {
13736      q = p;
13737      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13738        /* [HGM] write directly from rights */
13739            if(castlingRights[move][2] >= 0 &&
13740               castlingRights[move][0] >= 0   )
13741                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13742            if(castlingRights[move][2] >= 0 &&
13743               castlingRights[move][1] >= 0   )
13744                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13745            if(castlingRights[move][5] >= 0 &&
13746               castlingRights[move][3] >= 0   )
13747                 *p++ = castlingRights[move][3] + AAA;
13748            if(castlingRights[move][5] >= 0 &&
13749               castlingRights[move][4] >= 0   )
13750                 *p++ = castlingRights[move][4] + AAA;
13751      } else {
13752
13753         /* [HGM] write true castling rights */
13754         if( nrCastlingRights == 6 ) {
13755             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13756                castlingRights[move][2] >= 0  ) *p++ = 'K';
13757             if(castlingRights[move][1] == BOARD_LEFT &&
13758                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13759             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13760                castlingRights[move][5] >= 0  ) *p++ = 'k';
13761             if(castlingRights[move][4] == BOARD_LEFT &&
13762                castlingRights[move][5] >= 0  ) *p++ = 'q';
13763         }
13764      }
13765      if (q == p) *p++ = '-'; /* No castling rights */
13766      *p++ = ' ';
13767   }
13768
13769   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13770      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
13771     /* En passant target square */
13772     if (move > backwardMostMove) {
13773         fromX = moveList[move - 1][0] - AAA;
13774         fromY = moveList[move - 1][1] - ONE;
13775         toX = moveList[move - 1][2] - AAA;
13776         toY = moveList[move - 1][3] - ONE;
13777         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13778             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13779             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13780             fromX == toX) {
13781             /* 2-square pawn move just happened */
13782             *p++ = toX + AAA;
13783             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13784         } else {
13785             *p++ = '-';
13786         }
13787     } else if(move == backwardMostMove) {
13788         // [HGM] perhaps we should always do it like this, and forget the above?
13789         if(epStatus[move] >= 0) {
13790             *p++ = epStatus[move] + AAA;
13791             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13792         } else {
13793             *p++ = '-';
13794         }
13795     } else {
13796         *p++ = '-';
13797     }
13798     *p++ = ' ';
13799   }
13800   }
13801
13802     /* [HGM] find reversible plies */
13803     {   int i = 0, j=move;
13804
13805         if (appData.debugMode) { int k;
13806             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13807             for(k=backwardMostMove; k<=forwardMostMove; k++)
13808                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13809
13810         }
13811
13812         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13813         if( j == backwardMostMove ) i += initialRulePlies;
13814         sprintf(p, "%d ", i);
13815         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13816     }
13817     /* Fullmove number */
13818     sprintf(p, "%d", (move / 2) + 1);
13819     
13820     return StrSave(buf);
13821 }
13822
13823 Boolean
13824 ParseFEN(board, blackPlaysFirst, fen)
13825     Board board;
13826      int *blackPlaysFirst;
13827      char *fen;
13828 {
13829     int i, j;
13830     char *p;
13831     int emptycount;
13832     ChessSquare piece;
13833
13834     p = fen;
13835
13836     /* [HGM] by default clear Crazyhouse holdings, if present */
13837     if(gameInfo.holdingsWidth) {
13838        for(i=0; i<BOARD_HEIGHT; i++) {
13839            board[i][0]             = EmptySquare; /* black holdings */
13840            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13841            board[i][1]             = (ChessSquare) 0; /* black counts */
13842            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13843        }
13844     }
13845
13846     /* Piece placement data */
13847     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13848         j = 0;
13849         for (;;) {
13850             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13851                 if (*p == '/') p++;
13852                 emptycount = gameInfo.boardWidth - j;
13853                 while (emptycount--)
13854                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13855                 break;
13856 #if(BOARD_SIZE >= 10)
13857             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13858                 p++; emptycount=10;
13859                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13860                 while (emptycount--)
13861                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13862 #endif
13863             } else if (isdigit(*p)) {
13864                 emptycount = *p++ - '0';
13865                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13866                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13867                 while (emptycount--)
13868                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13869             } else if (*p == '+' || isalpha(*p)) {
13870                 if (j >= gameInfo.boardWidth) return FALSE;
13871                 if(*p=='+') {
13872                     piece = CharToPiece(*++p);
13873                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13874                     piece = (ChessSquare) (PROMOTED piece ); p++;
13875                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13876                 } else piece = CharToPiece(*p++);
13877
13878                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13879                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13880                     piece = (ChessSquare) (PROMOTED piece);
13881                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13882                     p++;
13883                 }
13884                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13885             } else {
13886                 return FALSE;
13887             }
13888         }
13889     }
13890     while (*p == '/' || *p == ' ') p++;
13891
13892     /* [HGM] look for Crazyhouse holdings here */
13893     while(*p==' ') p++;
13894     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13895         if(*p == '[') p++;
13896         if(*p == '-' ) *p++; /* empty holdings */ else {
13897             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13898             /* if we would allow FEN reading to set board size, we would   */
13899             /* have to add holdings and shift the board read so far here   */
13900             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13901                 *p++;
13902                 if((int) piece >= (int) BlackPawn ) {
13903                     i = (int)piece - (int)BlackPawn;
13904                     i = PieceToNumber((ChessSquare)i);
13905                     if( i >= gameInfo.holdingsSize ) return FALSE;
13906                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13907                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13908                 } else {
13909                     i = (int)piece - (int)WhitePawn;
13910                     i = PieceToNumber((ChessSquare)i);
13911                     if( i >= gameInfo.holdingsSize ) return FALSE;
13912                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13913                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13914                 }
13915             }
13916         }
13917         if(*p == ']') *p++;
13918     }
13919
13920     while(*p == ' ') p++;
13921
13922     /* Active color */
13923     switch (*p++) {
13924       case 'w':
13925         *blackPlaysFirst = FALSE;
13926         break;
13927       case 'b': 
13928         *blackPlaysFirst = TRUE;
13929         break;
13930       default:
13931         return FALSE;
13932     }
13933
13934     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13935     /* return the extra info in global variiables             */
13936
13937     /* set defaults in case FEN is incomplete */
13938     FENepStatus = EP_UNKNOWN;
13939     for(i=0; i<nrCastlingRights; i++ ) {
13940         FENcastlingRights[i] =
13941             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13942     }   /* assume possible unless obviously impossible */
13943     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13944     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13945     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
13946                            && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13947     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13948     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13949     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
13950                            && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13951     FENrulePlies = 0;
13952
13953     while(*p==' ') p++;
13954     if(nrCastlingRights) {
13955       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13956           /* castling indicator present, so default becomes no castlings */
13957           for(i=0; i<nrCastlingRights; i++ ) {
13958                  FENcastlingRights[i] = -1;
13959           }
13960       }
13961       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13962              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13963              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13964              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13965         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13966
13967         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13968             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13969             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13970         }
13971         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
13972             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // scanning fails in these variants
13973         if(whiteKingFile<0 || board[0][whiteKingFile]!=WhiteUnicorn
13974                            && board[0][whiteKingFile]!=WhiteKing) whiteKingFile = -1;
13975         if(blackKingFile<0 || board[BOARD_HEIGHT-1][blackKingFile]!=BlackUnicorn
13976                            && board[BOARD_HEIGHT-1][blackKingFile]!=BlackKing) blackKingFile = -1;
13977         switch(c) {
13978           case'K':
13979               for(i=BOARD_RGHT-1; i>whiteKingFile && board[0][i]!=WhiteRook; i--);
13980               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13981               FENcastlingRights[2] = whiteKingFile;
13982               break;
13983           case'Q':
13984               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13985               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13986               FENcastlingRights[2] = whiteKingFile;
13987               break;
13988           case'k':
13989               for(i=BOARD_RGHT-1; i>blackKingFile && board[BOARD_HEIGHT-1][i]!=BlackRook; i--);
13990               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13991               FENcastlingRights[5] = blackKingFile;
13992               break;
13993           case'q':
13994               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13995               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13996               FENcastlingRights[5] = blackKingFile;
13997           case '-':
13998               break;
13999           default: /* FRC castlings */
14000               if(c >= 'a') { /* black rights */
14001                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14002                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14003                   if(i == BOARD_RGHT) break;
14004                   FENcastlingRights[5] = i;
14005                   c -= AAA;
14006                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14007                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14008                   if(c > i)
14009                       FENcastlingRights[3] = c;
14010                   else
14011                       FENcastlingRights[4] = c;
14012               } else { /* white rights */
14013                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14014                     if(board[0][i] == WhiteKing) break;
14015                   if(i == BOARD_RGHT) break;
14016                   FENcastlingRights[2] = i;
14017                   c -= AAA - 'a' + 'A';
14018                   if(board[0][c] >= WhiteKing) break;
14019                   if(c > i)
14020                       FENcastlingRights[0] = c;
14021                   else
14022                       FENcastlingRights[1] = c;
14023               }
14024         }
14025       }
14026       for(i=0; i<nrCastlingRights; i++)
14027         if(FENcastlingRights[i] >= 0) initialRights[i] = FENcastlingRights[i];
14028     if (appData.debugMode) {
14029         fprintf(debugFP, "FEN castling rights:");
14030         for(i=0; i<nrCastlingRights; i++)
14031         fprintf(debugFP, " %d", FENcastlingRights[i]);
14032         fprintf(debugFP, "\n");
14033     }
14034
14035       while(*p==' ') p++;
14036     }
14037
14038     /* read e.p. field in games that know e.p. capture */
14039     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14040        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14041       if(*p=='-') {
14042         p++; FENepStatus = EP_NONE;
14043       } else {
14044          char c = *p++ - AAA;
14045
14046          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14047          if(*p >= '0' && *p <='9') *p++;
14048          FENepStatus = c;
14049       }
14050     }
14051
14052
14053     if(sscanf(p, "%d", &i) == 1) {
14054         FENrulePlies = i; /* 50-move ply counter */
14055         /* (The move number is still ignored)    */
14056     }
14057
14058     return TRUE;
14059 }
14060       
14061 void
14062 EditPositionPasteFEN(char *fen)
14063 {
14064   if (fen != NULL) {
14065     Board initial_position;
14066
14067     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14068       DisplayError(_("Bad FEN position in clipboard"), 0);
14069       return ;
14070     } else {
14071       int savedBlackPlaysFirst = blackPlaysFirst;
14072       EditPositionEvent();
14073       blackPlaysFirst = savedBlackPlaysFirst;
14074       CopyBoard(boards[0], initial_position);
14075           /* [HGM] copy FEN attributes as well */
14076           {   int i;
14077               initialRulePlies = FENrulePlies;
14078               epStatus[0] = FENepStatus;
14079               for( i=0; i<nrCastlingRights; i++ )
14080                   castlingRights[0][i] = FENcastlingRights[i];
14081           }
14082       EditPositionDone(FALSE);
14083       DisplayBothClocks();
14084       DrawPosition(FALSE, boards[currentMove]);
14085     }
14086   }
14087 }
14088
14089 static char cseq[12] = "\\   ";
14090
14091 Boolean set_cont_sequence(char *new_seq)
14092 {
14093     int len;
14094     Boolean ret;
14095
14096     // handle bad attempts to set the sequence
14097         if (!new_seq)
14098                 return 0; // acceptable error - no debug
14099
14100     len = strlen(new_seq);
14101     ret = (len > 0) && (len < sizeof(cseq));
14102     if (ret)
14103         strcpy(cseq, new_seq);
14104     else if (appData.debugMode)
14105         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14106     return ret;
14107 }
14108
14109 /*
14110     reformat a source message so words don't cross the width boundary.  internal
14111     newlines are not removed.  returns the wrapped size (no null character unless
14112     included in source message).  If dest is NULL, only calculate the size required
14113     for the dest buffer.  lp argument indicats line position upon entry, and it's
14114     passed back upon exit.
14115 */
14116 int wrap(char *dest, char *src, int count, int width, int *lp)
14117 {
14118     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14119
14120     cseq_len = strlen(cseq);
14121     old_line = line = *lp;
14122     ansi = len = clen = 0;
14123
14124     for (i=0; i < count; i++)
14125     {
14126         if (src[i] == '\033')
14127             ansi = 1;
14128
14129         // if we hit the width, back up
14130         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14131         {
14132             // store i & len in case the word is too long
14133             old_i = i, old_len = len;
14134
14135             // find the end of the last word
14136             while (i && src[i] != ' ' && src[i] != '\n')
14137             {
14138                 i--;
14139                 len--;
14140             }
14141
14142             // word too long?  restore i & len before splitting it
14143             if ((old_i-i+clen) >= width)
14144             {
14145                 i = old_i;
14146                 len = old_len;
14147             }
14148
14149             // extra space?
14150             if (i && src[i-1] == ' ')
14151                 len--;
14152
14153             if (src[i] != ' ' && src[i] != '\n')
14154             {
14155                 i--;
14156                 if (len)
14157                     len--;
14158             }
14159
14160             // now append the newline and continuation sequence
14161             if (dest)
14162                 dest[len] = '\n';
14163             len++;
14164             if (dest)
14165                 strncpy(dest+len, cseq, cseq_len);
14166             len += cseq_len;
14167             line = cseq_len;
14168             clen = cseq_len;
14169             continue;
14170         }
14171
14172         if (dest)
14173             dest[len] = src[i];
14174         len++;
14175         if (!ansi)
14176             line++;
14177         if (src[i] == '\n')
14178             line = 0;
14179         if (src[i] == 'm')
14180             ansi = 0;
14181     }
14182     if (dest && appData.debugMode)
14183     {
14184         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14185             count, width, line, len, *lp);
14186         show_bytes(debugFP, src, count);
14187         fprintf(debugFP, "\ndest: ");
14188         show_bytes(debugFP, dest, len);
14189         fprintf(debugFP, "\n");
14190     }
14191     *lp = dest ? line : old_line;
14192
14193     return len;
14194 }