2965507e9390512f476b7f7f71b6608719c4a6b7
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 #else /* not STDC_HEADERS */
81 # if HAVE_STRING_H
82 #  include <string.h>
83 # else /* not HAVE_STRING_H */
84 #  include <strings.h>
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
87
88 #if HAVE_SYS_FCNTL_H
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
91 # if HAVE_FCNTL_H
92 #  include <fcntl.h>
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
95
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
98 # include <time.h>
99 #else
100 # if HAVE_SYS_TIME_H
101 #  include <sys/time.h>
102 # else
103 #  include <time.h>
104 # endif
105 #endif
106
107 #if defined(_amigados) && !defined(__GNUC__)
108 struct timezone {
109     int tz_minuteswest;
110     int tz_dsttime;
111 };
112 extern int gettimeofday(struct timeval *, struct timezone *);
113 #endif
114
115 #if HAVE_UNISTD_H
116 # include <unistd.h>
117 #endif
118
119 #include "common.h"
120 #include "frontend.h"
121 #include "backend.h"
122 #include "parser.h"
123 #include "moves.h"
124 #if ZIPPY
125 # include "zippy.h"
126 #endif
127 #include "backendz.h"
128 #include "gettext.h" 
129  
130 #ifdef ENABLE_NLS 
131 # define _(s) gettext (s) 
132 # define N_(s) gettext_noop (s) 
133 #else 
134 # define _(s) (s) 
135 # define N_(s) s 
136 #endif 
137
138
139 /* A point in time */
140 typedef struct {
141     long sec;  /* Assuming this is >= 32 bits */
142     int ms;    /* Assuming this is >= 16 bits */
143 } TimeMark;
144
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147                          char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149                       char *buf, int count, int error));
150 void ics_printf P((char *format, ...));
151 void SendToICS P((char *s));
152 void SendToICSDelayed P((char *s, long msdelay));
153 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
154                       int toX, int toY));
155 void HandleMachineMove P((char *message, ChessProgramState *cps));
156 int AutoPlayOneMove P((void));
157 int LoadGameOneMove P((ChessMove readAhead));
158 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
159 int LoadPositionFromFile P((char *filename, int n, char *title));
160 int SavePositionToFile P((char *filename));
161 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162                   Board board, char *castle, char *ep));
163 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
164 void ShowMove P((int fromX, int fromY, int toX, int toY));
165 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
166                    /*char*/int promoChar));
167 void BackwardInner P((int target));
168 void ForwardInner P((int target));
169 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
170 void EditPositionDone P((void));
171 void PrintOpponents P((FILE *fp));
172 void PrintPosition P((FILE *fp, int move));
173 void StartChessProgram P((ChessProgramState *cps));
174 void SendToProgram P((char *message, ChessProgramState *cps));
175 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
176 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
177                            char *buf, int count, int error));
178 void SendTimeControl P((ChessProgramState *cps,
179                         int mps, long tc, int inc, int sd, int st));
180 char *TimeControlTagValue P((void));
181 void Attention P((ChessProgramState *cps));
182 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
183 void ResurrectChessProgram P((void));
184 void DisplayComment P((int moveNumber, char *text));
185 void DisplayMove P((int moveNumber));
186 void DisplayAnalysis P((void));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom: 
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /* 
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  epStatus[MAX_MOVES];
445 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int   initialRulePlies, FENrulePlies;
450 char  FENepStatus;
451 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
452 int loadFlag = 0; 
453 int shuffleOpenings;
454 int mute; // mute all sounds
455
456 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
457     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460         BlackKing, BlackBishop, BlackKnight, BlackRook }
461 };
462
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467         BlackKing, BlackKing, BlackKnight, BlackRook }
468 };
469
470 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473     { BlackRook, BlackMan, BlackBishop, BlackQueen,
474         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
475 };
476
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481         BlackKing, BlackBishop, BlackKnight, BlackRook }
482 };
483
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
489 };
490
491
492 #if (BOARD_SIZE>=10)
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
498 };
499
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
505 };
506
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
509         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
511         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
512 };
513
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
516         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
518         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
519 };
520
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
523         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
525         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
526 };
527
528 #ifdef GOTHIC
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
531         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
533         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
534 };
535 #else // !GOTHIC
536 #define GothicArray CapablancaArray
537 #endif // !GOTHIC
538
539 #ifdef FALCON
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
542         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
544         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !FALCON
547 #define FalconArray CapablancaArray
548 #endif // !FALCON
549
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
556
557 #if (BOARD_SIZE>=12)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
563 };
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
567
568
569 Board initialPosition;
570
571
572 /* Convert str to a rating. Checks for special cases of "----",
573
574    "++++", etc. Also strips ()'s */
575 int
576 string_to_rating(str)
577   char *str;
578 {
579   while(*str && !isdigit(*str)) ++str;
580   if (!*str)
581     return 0;   /* One of the special "no rating" cases */
582   else
583     return atoi(str);
584 }
585
586 void
587 ClearProgramStats()
588 {
589     /* Init programStats */
590     programStats.movelist[0] = 0;
591     programStats.depth = 0;
592     programStats.nr_moves = 0;
593     programStats.moves_left = 0;
594     programStats.nodes = 0;
595     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
596     programStats.score = 0;
597     programStats.got_only_move = 0;
598     programStats.got_fail = 0;
599     programStats.line_is_book = 0;
600 }
601
602 void
603 InitBackEnd1()
604 {
605     int matched, min, sec;
606
607     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
608
609     GetTimeMark(&programStartTime);
610     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
611
612     ClearProgramStats();
613     programStats.ok_to_send = 1;
614     programStats.seen_stat = 0;
615
616     /*
617      * Initialize game list
618      */
619     ListNew(&gameList);
620
621
622     /*
623      * Internet chess server status
624      */
625     if (appData.icsActive) {
626         appData.matchMode = FALSE;
627         appData.matchGames = 0;
628 #if ZIPPY       
629         appData.noChessProgram = !appData.zippyPlay;
630 #else
631         appData.zippyPlay = FALSE;
632         appData.zippyTalk = FALSE;
633         appData.noChessProgram = TRUE;
634 #endif
635         if (*appData.icsHelper != NULLCHAR) {
636             appData.useTelnet = TRUE;
637             appData.telnetProgram = appData.icsHelper;
638         }
639     } else {
640         appData.zippyTalk = appData.zippyPlay = FALSE;
641     }
642
643     /* [AS] Initialize pv info list [HGM] and game state */
644     {
645         int i, j;
646
647         for( i=0; i<MAX_MOVES; i++ ) {
648             pvInfoList[i].depth = -1;
649             epStatus[i]=EP_NONE;
650             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
651         }
652     }
653
654     /*
655      * Parse timeControl resource
656      */
657     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658                           appData.movesPerSession)) {
659         char buf[MSG_SIZ];
660         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661         DisplayFatalError(buf, 0, 2);
662     }
663
664     /*
665      * Parse searchTime resource
666      */
667     if (*appData.searchTime != NULLCHAR) {
668         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
669         if (matched == 1) {
670             searchTime = min * 60;
671         } else if (matched == 2) {
672             searchTime = min * 60 + sec;
673         } else {
674             char buf[MSG_SIZ];
675             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676             DisplayFatalError(buf, 0, 2);
677         }
678     }
679
680     /* [AS] Adjudication threshold */
681     adjudicateLossThreshold = appData.adjudicateLossThreshold;
682     
683     first.which = "first";
684     second.which = "second";
685     first.maybeThinking = second.maybeThinking = FALSE;
686     first.pr = second.pr = NoProc;
687     first.isr = second.isr = NULL;
688     first.sendTime = second.sendTime = 2;
689     first.sendDrawOffers = 1;
690     if (appData.firstPlaysBlack) {
691         first.twoMachinesColor = "black\n";
692         second.twoMachinesColor = "white\n";
693     } else {
694         first.twoMachinesColor = "white\n";
695         second.twoMachinesColor = "black\n";
696     }
697     first.program = appData.firstChessProgram;
698     second.program = appData.secondChessProgram;
699     first.host = appData.firstHost;
700     second.host = appData.secondHost;
701     first.dir = appData.firstDirectory;
702     second.dir = appData.secondDirectory;
703     first.other = &second;
704     second.other = &first;
705     first.initString = appData.initString;
706     second.initString = appData.secondInitString;
707     first.computerString = appData.firstComputerString;
708     second.computerString = appData.secondComputerString;
709     first.useSigint = second.useSigint = TRUE;
710     first.useSigterm = second.useSigterm = TRUE;
711     first.reuse = appData.reuseFirst;
712     second.reuse = appData.reuseSecond;
713     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
714     second.nps = appData.secondNPS;
715     first.useSetboard = second.useSetboard = FALSE;
716     first.useSAN = second.useSAN = FALSE;
717     first.usePing = second.usePing = FALSE;
718     first.lastPing = second.lastPing = 0;
719     first.lastPong = second.lastPong = 0;
720     first.usePlayother = second.usePlayother = FALSE;
721     first.useColors = second.useColors = TRUE;
722     first.useUsermove = second.useUsermove = FALSE;
723     first.sendICS = second.sendICS = FALSE;
724     first.sendName = second.sendName = appData.icsActive;
725     first.sdKludge = second.sdKludge = FALSE;
726     first.stKludge = second.stKludge = FALSE;
727     TidyProgramName(first.program, first.host, first.tidy);
728     TidyProgramName(second.program, second.host, second.tidy);
729     first.matchWins = second.matchWins = 0;
730     strcpy(first.variants, appData.variant);
731     strcpy(second.variants, appData.variant);
732     first.analysisSupport = second.analysisSupport = 2; /* detect */
733     first.analyzing = second.analyzing = FALSE;
734     first.initDone = second.initDone = FALSE;
735
736     /* New features added by Tord: */
737     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739     /* End of new features added by Tord. */
740     first.fenOverride  = appData.fenOverride1;
741     second.fenOverride = appData.fenOverride2;
742
743     /* [HGM] time odds: set factor for each machine */
744     first.timeOdds  = appData.firstTimeOdds;
745     second.timeOdds = appData.secondTimeOdds;
746     { int norm = 1;
747         if(appData.timeOddsMode) {
748             norm = first.timeOdds;
749             if(norm > second.timeOdds) norm = second.timeOdds;
750         }
751         first.timeOdds /= norm;
752         second.timeOdds /= norm;
753     }
754
755     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756     first.accumulateTC = appData.firstAccumulateTC;
757     second.accumulateTC = appData.secondAccumulateTC;
758     first.maxNrOfSessions = second.maxNrOfSessions = 1;
759
760     /* [HGM] debug */
761     first.debug = second.debug = FALSE;
762     first.supportsNPS = second.supportsNPS = UNKNOWN;
763
764     /* [HGM] options */
765     first.optionSettings  = appData.firstOptions;
766     second.optionSettings = appData.secondOptions;
767
768     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770     first.isUCI = appData.firstIsUCI; /* [AS] */
771     second.isUCI = appData.secondIsUCI; /* [AS] */
772     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
774
775     if (appData.firstProtocolVersion > PROTOVER ||
776         appData.firstProtocolVersion < 1) {
777       char buf[MSG_SIZ];
778       sprintf(buf, _("protocol version %d not supported"),
779               appData.firstProtocolVersion);
780       DisplayFatalError(buf, 0, 2);
781     } else {
782       first.protocolVersion = appData.firstProtocolVersion;
783     }
784
785     if (appData.secondProtocolVersion > PROTOVER ||
786         appData.secondProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.secondProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       second.protocolVersion = appData.secondProtocolVersion;
793     }
794
795     if (appData.icsActive) {
796         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
797     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798         appData.clockMode = FALSE;
799         first.sendTime = second.sendTime = 0;
800     }
801     
802 #if ZIPPY
803     /* Override some settings from environment variables, for backward
804        compatibility.  Unfortunately it's not feasible to have the env
805        vars just set defaults, at least in xboard.  Ugh.
806     */
807     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
808       ZippyInit();
809     }
810 #endif
811     
812     if (appData.noChessProgram) {
813         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814         sprintf(programVersion, "%s", PACKAGE_STRING);
815     } else {
816       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
819     }
820
821     if (!appData.icsActive) {
822       char buf[MSG_SIZ];
823       /* Check for variants that are supported only in ICS mode,
824          or not at all.  Some that are accepted here nevertheless
825          have bugs; see comments below.
826       */
827       VariantClass variant = StringToVariant(appData.variant);
828       switch (variant) {
829       case VariantBughouse:     /* need four players and two boards */
830       case VariantKriegspiel:   /* need to hide pieces and move details */
831       /* case VariantFischeRandom: (Fabien: moved below) */
832         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833         DisplayFatalError(buf, 0, 2);
834         return;
835
836       case VariantUnknown:
837       case VariantLoadable:
838       case Variant29:
839       case Variant30:
840       case Variant31:
841       case Variant32:
842       case Variant33:
843       case Variant34:
844       case Variant35:
845       case Variant36:
846       default:
847         sprintf(buf, _("Unknown variant name %s"), appData.variant);
848         DisplayFatalError(buf, 0, 2);
849         return;
850
851       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
852       case VariantFairy:      /* [HGM] TestLegality definitely off! */
853       case VariantGothic:     /* [HGM] should work */
854       case VariantCapablanca: /* [HGM] should work */
855       case VariantCourier:    /* [HGM] initial forced moves not implemented */
856       case VariantShogi:      /* [HGM] drops not tested for legality */
857       case VariantKnightmate: /* [HGM] should work */
858       case VariantCylinder:   /* [HGM] untested */
859       case VariantFalcon:     /* [HGM] untested */
860       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861                                  offboard interposition not understood */
862       case VariantNormal:     /* definitely works! */
863       case VariantWildCastle: /* pieces not automatically shuffled */
864       case VariantNoCastle:   /* pieces not automatically shuffled */
865       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866       case VariantLosers:     /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantSuicide:    /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantGiveaway:   /* should work except for win condition,
871                                  and doesn't know captures are mandatory */
872       case VariantTwoKings:   /* should work */
873       case VariantAtomic:     /* should work except for win condition */
874       case Variant3Check:     /* should work except for win condition */
875       case VariantShatranj:   /* should work except for all win conditions */
876       case VariantBerolina:   /* might work if TestLegality is off */
877       case VariantCapaRandom: /* should work */
878       case VariantJanus:      /* should work */
879       case VariantSuper:      /* experimental */
880       case VariantGreat:      /* experimental, requires legality testing to be off */
881         break;
882       }
883     }
884
885     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
886     InitEngineUCI( installDir, &second );
887 }
888
889 int NextIntegerFromString( char ** str, long * value )
890 {
891     int result = -1;
892     char * s = *str;
893
894     while( *s == ' ' || *s == '\t' ) {
895         s++;
896     }
897
898     *value = 0;
899
900     if( *s >= '0' && *s <= '9' ) {
901         while( *s >= '0' && *s <= '9' ) {
902             *value = *value * 10 + (*s - '0');
903             s++;
904         }
905
906         result = 0;
907     }
908
909     *str = s;
910
911     return result;
912 }
913
914 int NextTimeControlFromString( char ** str, long * value )
915 {
916     long temp;
917     int result = NextIntegerFromString( str, &temp );
918
919     if( result == 0 ) {
920         *value = temp * 60; /* Minutes */
921         if( **str == ':' ) {
922             (*str)++;
923             result = NextIntegerFromString( str, &temp );
924             *value += temp; /* Seconds */
925         }
926     }
927
928     return result;
929 }
930
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
933     int result = -1; long temp, temp2;
934
935     if(**str != '+') return -1; // old params remain in force!
936     (*str)++;
937     if( NextTimeControlFromString( str, &temp ) ) return -1;
938
939     if(**str != '/') {
940         /* time only: incremental or sudden-death time control */
941         if(**str == '+') { /* increment follows; read it */
942             (*str)++;
943             if(result = NextIntegerFromString( str, &temp2)) return -1;
944             *inc = temp2 * 1000;
945         } else *inc = 0;
946         *moves = 0; *tc = temp * 1000; 
947         return 0;
948     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
949
950     (*str)++; /* classical time control */
951     result = NextTimeControlFromString( str, &temp2);
952     if(result == 0) {
953         *moves = temp/60;
954         *tc    = temp2 * 1000;
955         *inc   = 0;
956     }
957     return result;
958 }
959
960 int GetTimeQuota(int movenr)
961 {   /* [HGM] get time to add from the multi-session time-control string */
962     int moves=1; /* kludge to force reading of first session */
963     long time, increment;
964     char *s = fullTimeControlString;
965
966     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
967     do {
968         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970         if(movenr == -1) return time;    /* last move before new session     */
971         if(!moves) return increment;     /* current session is incremental   */
972         if(movenr >= 0) movenr -= moves; /* we already finished this session */
973     } while(movenr >= -1);               /* try again for next session       */
974
975     return 0; // no new time quota on this move
976 }
977
978 int
979 ParseTimeControl(tc, ti, mps)
980      char *tc;
981      int ti;
982      int mps;
983 {
984   long tc1;
985   long tc2;
986   char buf[MSG_SIZ];
987   
988   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
989   if(ti > 0) {
990     if(mps)
991       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992     else sprintf(buf, "+%s+%d", tc, ti);
993   } else {
994     if(mps)
995              sprintf(buf, "+%d/%s", mps, tc);
996     else sprintf(buf, "+%s", tc);
997   }
998   fullTimeControlString = StrSave(buf);
999   
1000   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1001     return FALSE;
1002   }
1003   
1004   if( *tc == '/' ) {
1005     /* Parse second time control */
1006     tc++;
1007     
1008     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1009       return FALSE;
1010     }
1011     
1012     if( tc2 == 0 ) {
1013       return FALSE;
1014     }
1015     
1016     timeControl_2 = tc2 * 1000;
1017   }
1018   else {
1019     timeControl_2 = 0;
1020   }
1021   
1022   if( tc1 == 0 ) {
1023     return FALSE;
1024   }
1025   
1026   timeControl = tc1 * 1000;
1027   
1028   if (ti >= 0) {
1029     timeIncrement = ti * 1000;  /* convert to ms */
1030     movesPerSession = 0;
1031   } else {
1032     timeIncrement = 0;
1033     movesPerSession = mps;
1034   }
1035   return TRUE;
1036 }
1037
1038 void
1039 InitBackEnd2()
1040 {
1041     if (appData.debugMode) {
1042         fprintf(debugFP, "%s\n", programVersion);
1043     }
1044
1045     if (appData.matchGames > 0) {
1046         appData.matchMode = TRUE;
1047     } else if (appData.matchMode) {
1048         appData.matchGames = 1;
1049     }
1050     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1051         appData.matchGames = appData.sameColorGames;
1052     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1053         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1054         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1055     }
1056     Reset(TRUE, FALSE);
1057     if (appData.noChessProgram || first.protocolVersion == 1) {
1058       InitBackEnd3();
1059     } else {
1060       /* kludge: allow timeout for initial "feature" commands */
1061       FreezeUI();
1062       DisplayMessage("", _("Starting chess program"));
1063       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1064     }
1065 }
1066
1067 void
1068 InitBackEnd3 P((void))
1069 {
1070     GameMode initialMode;
1071     char buf[MSG_SIZ];
1072     int err;
1073
1074     InitChessProgram(&first, startedFromSetupPosition);
1075
1076
1077     if (appData.icsActive) {
1078 #ifdef WIN32
1079         /* [DM] Make a console window if needed [HGM] merged ifs */
1080         ConsoleCreate(); 
1081 #endif
1082         err = establish();
1083         if (err != 0) {
1084             if (*appData.icsCommPort != NULLCHAR) {
1085                 sprintf(buf, _("Could not open comm port %s"),  
1086                         appData.icsCommPort);
1087             } else {
1088                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1089                         appData.icsHost, appData.icsPort);
1090             }
1091             DisplayFatalError(buf, err, 1);
1092             return;
1093         }
1094         SetICSMode();
1095         telnetISR =
1096           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1097         fromUserISR =
1098           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1099     } else if (appData.noChessProgram) {
1100         SetNCPMode();
1101     } else {
1102         SetGNUMode();
1103     }
1104
1105     if (*appData.cmailGameName != NULLCHAR) {
1106         SetCmailMode();
1107         OpenLoopback(&cmailPR);
1108         cmailISR =
1109           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1110     }
1111     
1112     ThawUI();
1113     DisplayMessage("", "");
1114     if (StrCaseCmp(appData.initialMode, "") == 0) {
1115       initialMode = BeginningOfGame;
1116     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1117       initialMode = TwoMachinesPlay;
1118     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1119       initialMode = AnalyzeFile; 
1120     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1121       initialMode = AnalyzeMode;
1122     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1123       initialMode = MachinePlaysWhite;
1124     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1125       initialMode = MachinePlaysBlack;
1126     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1127       initialMode = EditGame;
1128     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1129       initialMode = EditPosition;
1130     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1131       initialMode = Training;
1132     } else {
1133       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1134       DisplayFatalError(buf, 0, 2);
1135       return;
1136     }
1137
1138     if (appData.matchMode) {
1139         /* Set up machine vs. machine match */
1140         if (appData.noChessProgram) {
1141             DisplayFatalError(_("Can't have a match with no chess programs"),
1142                               0, 2);
1143             return;
1144         }
1145         matchMode = TRUE;
1146         matchGame = 1;
1147         if (*appData.loadGameFile != NULLCHAR) {
1148             int index = appData.loadGameIndex; // [HGM] autoinc
1149             if(index<0) lastIndex = index = 1;
1150             if (!LoadGameFromFile(appData.loadGameFile,
1151                                   index,
1152                                   appData.loadGameFile, FALSE)) {
1153                 DisplayFatalError(_("Bad game file"), 0, 1);
1154                 return;
1155             }
1156         } else if (*appData.loadPositionFile != NULLCHAR) {
1157             int index = appData.loadPositionIndex; // [HGM] autoinc
1158             if(index<0) lastIndex = index = 1;
1159             if (!LoadPositionFromFile(appData.loadPositionFile,
1160                                       index,
1161                                       appData.loadPositionFile)) {
1162                 DisplayFatalError(_("Bad position file"), 0, 1);
1163                 return;
1164             }
1165         }
1166         TwoMachinesEvent();
1167     } else if (*appData.cmailGameName != NULLCHAR) {
1168         /* Set up cmail mode */
1169         ReloadCmailMsgEvent(TRUE);
1170     } else {
1171         /* Set up other modes */
1172         if (initialMode == AnalyzeFile) {
1173           if (*appData.loadGameFile == NULLCHAR) {
1174             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1175             return;
1176           }
1177         }
1178         if (*appData.loadGameFile != NULLCHAR) {
1179             (void) LoadGameFromFile(appData.loadGameFile,
1180                                     appData.loadGameIndex,
1181                                     appData.loadGameFile, TRUE);
1182         } else if (*appData.loadPositionFile != NULLCHAR) {
1183             (void) LoadPositionFromFile(appData.loadPositionFile,
1184                                         appData.loadPositionIndex,
1185                                         appData.loadPositionFile);
1186             /* [HGM] try to make self-starting even after FEN load */
1187             /* to allow automatic setup of fairy variants with wtm */
1188             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1189                 gameMode = BeginningOfGame;
1190                 setboardSpoiledMachineBlack = 1;
1191             }
1192             /* [HGM] loadPos: make that every new game uses the setup */
1193             /* from file as long as we do not switch variant          */
1194             if(!blackPlaysFirst) { int i;
1195                 startedFromPositionFile = TRUE;
1196                 CopyBoard(filePosition, boards[0]);
1197                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1198             }
1199         }
1200         if (initialMode == AnalyzeMode) {
1201           if (appData.noChessProgram) {
1202             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1203             return;
1204           }
1205           if (appData.icsActive) {
1206             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1207             return;
1208           }
1209           AnalyzeModeEvent();
1210         } else if (initialMode == AnalyzeFile) {
1211           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1212           ShowThinkingEvent();
1213           AnalyzeFileEvent();
1214           AnalysisPeriodicEvent(1);
1215         } else if (initialMode == MachinePlaysWhite) {
1216           if (appData.noChessProgram) {
1217             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1218                               0, 2);
1219             return;
1220           }
1221           if (appData.icsActive) {
1222             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1223                               0, 2);
1224             return;
1225           }
1226           MachineWhiteEvent();
1227         } else if (initialMode == MachinePlaysBlack) {
1228           if (appData.noChessProgram) {
1229             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1230                               0, 2);
1231             return;
1232           }
1233           if (appData.icsActive) {
1234             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1235                               0, 2);
1236             return;
1237           }
1238           MachineBlackEvent();
1239         } else if (initialMode == TwoMachinesPlay) {
1240           if (appData.noChessProgram) {
1241             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1242                               0, 2);
1243             return;
1244           }
1245           if (appData.icsActive) {
1246             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1247                               0, 2);
1248             return;
1249           }
1250           TwoMachinesEvent();
1251         } else if (initialMode == EditGame) {
1252           EditGameEvent();
1253         } else if (initialMode == EditPosition) {
1254           EditPositionEvent();
1255         } else if (initialMode == Training) {
1256           if (*appData.loadGameFile == NULLCHAR) {
1257             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1258             return;
1259           }
1260           TrainingEvent();
1261         }
1262     }
1263 }
1264
1265 /*
1266  * Establish will establish a contact to a remote host.port.
1267  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1268  *  used to talk to the host.
1269  * Returns 0 if okay, error code if not.
1270  */
1271 int
1272 establish()
1273 {
1274     char buf[MSG_SIZ];
1275
1276     if (*appData.icsCommPort != NULLCHAR) {
1277         /* Talk to the host through a serial comm port */
1278         return OpenCommPort(appData.icsCommPort, &icsPR);
1279
1280     } else if (*appData.gateway != NULLCHAR) {
1281         if (*appData.remoteShell == NULLCHAR) {
1282             /* Use the rcmd protocol to run telnet program on a gateway host */
1283             snprintf(buf, sizeof(buf), "%s %s %s",
1284                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1285             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1286
1287         } else {
1288             /* Use the rsh program to run telnet program on a gateway host */
1289             if (*appData.remoteUser == NULLCHAR) {
1290                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1291                         appData.gateway, appData.telnetProgram,
1292                         appData.icsHost, appData.icsPort);
1293             } else {
1294                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1295                         appData.remoteShell, appData.gateway, 
1296                         appData.remoteUser, appData.telnetProgram,
1297                         appData.icsHost, appData.icsPort);
1298             }
1299             return StartChildProcess(buf, "", &icsPR);
1300
1301         }
1302     } else if (appData.useTelnet) {
1303         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1304
1305     } else {
1306         /* TCP socket interface differs somewhat between
1307            Unix and NT; handle details in the front end.
1308            */
1309         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1310     }
1311 }
1312
1313 void
1314 show_bytes(fp, buf, count)
1315      FILE *fp;
1316      char *buf;
1317      int count;
1318 {
1319     while (count--) {
1320         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1321             fprintf(fp, "\\%03o", *buf & 0xff);
1322         } else {
1323             putc(*buf, fp);
1324         }
1325         buf++;
1326     }
1327     fflush(fp);
1328 }
1329
1330 /* Returns an errno value */
1331 int
1332 OutputMaybeTelnet(pr, message, count, outError)
1333      ProcRef pr;
1334      char *message;
1335      int count;
1336      int *outError;
1337 {
1338     char buf[8192], *p, *q, *buflim;
1339     int left, newcount, outcount;
1340
1341     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1342         *appData.gateway != NULLCHAR) {
1343         if (appData.debugMode) {
1344             fprintf(debugFP, ">ICS: ");
1345             show_bytes(debugFP, message, count);
1346             fprintf(debugFP, "\n");
1347         }
1348         return OutputToProcess(pr, message, count, outError);
1349     }
1350
1351     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1352     p = message;
1353     q = buf;
1354     left = count;
1355     newcount = 0;
1356     while (left) {
1357         if (q >= buflim) {
1358             if (appData.debugMode) {
1359                 fprintf(debugFP, ">ICS: ");
1360                 show_bytes(debugFP, buf, newcount);
1361                 fprintf(debugFP, "\n");
1362             }
1363             outcount = OutputToProcess(pr, buf, newcount, outError);
1364             if (outcount < newcount) return -1; /* to be sure */
1365             q = buf;
1366             newcount = 0;
1367         }
1368         if (*p == '\n') {
1369             *q++ = '\r';
1370             newcount++;
1371         } else if (((unsigned char) *p) == TN_IAC) {
1372             *q++ = (char) TN_IAC;
1373             newcount ++;
1374         }
1375         *q++ = *p++;
1376         newcount++;
1377         left--;
1378     }
1379     if (appData.debugMode) {
1380         fprintf(debugFP, ">ICS: ");
1381         show_bytes(debugFP, buf, newcount);
1382         fprintf(debugFP, "\n");
1383     }
1384     outcount = OutputToProcess(pr, buf, newcount, outError);
1385     if (outcount < newcount) return -1; /* to be sure */
1386     return count;
1387 }
1388
1389 void
1390 read_from_player(isr, closure, message, count, error)
1391      InputSourceRef isr;
1392      VOIDSTAR closure;
1393      char *message;
1394      int count;
1395      int error;
1396 {
1397     int outError, outCount;
1398     static int gotEof = 0;
1399
1400     /* Pass data read from player on to ICS */
1401     if (count > 0) {
1402         gotEof = 0;
1403         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1404         if (outCount < count) {
1405             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1406         }
1407     } else if (count < 0) {
1408         RemoveInputSource(isr);
1409         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1410     } else if (gotEof++ > 0) {
1411         RemoveInputSource(isr);
1412         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1413     }
1414 }
1415
1416 void
1417 KeepAlive()
1418 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1419     SendToICS("date\n");
1420     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1421 }
1422
1423 /* added routine for printf style output to ics */
1424 void ics_printf(char *format, ...)
1425 {
1426         char buffer[MSG_SIZ], *args;
1427         
1428         args = (char *)&format + sizeof(format);
1429         vsnprintf(buffer, sizeof(buffer), format, args);
1430         buffer[sizeof(buffer)-1] = '\0';
1431         SendToICS(buffer);
1432 }
1433
1434 void
1435 SendToICS(s)
1436      char *s;
1437 {
1438     int count, outCount, outError;
1439
1440     if (icsPR == NULL) return;
1441
1442     count = strlen(s);
1443     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1444     if (outCount < count) {
1445         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1446     }
1447 }
1448
1449 /* This is used for sending logon scripts to the ICS. Sending
1450    without a delay causes problems when using timestamp on ICC
1451    (at least on my machine). */
1452 void
1453 SendToICSDelayed(s,msdelay)
1454      char *s;
1455      long msdelay;
1456 {
1457     int count, outCount, outError;
1458
1459     if (icsPR == NULL) return;
1460
1461     count = strlen(s);
1462     if (appData.debugMode) {
1463         fprintf(debugFP, ">ICS: ");
1464         show_bytes(debugFP, s, count);
1465         fprintf(debugFP, "\n");
1466     }
1467     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1468                                       msdelay);
1469     if (outCount < count) {
1470         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1471     }
1472 }
1473
1474
1475 /* Remove all highlighting escape sequences in s
1476    Also deletes any suffix starting with '(' 
1477    */
1478 char *
1479 StripHighlightAndTitle(s)
1480      char *s;
1481 {
1482     static char retbuf[MSG_SIZ];
1483     char *p = retbuf;
1484
1485     while (*s != NULLCHAR) {
1486         while (*s == '\033') {
1487             while (*s != NULLCHAR && !isalpha(*s)) s++;
1488             if (*s != NULLCHAR) s++;
1489         }
1490         while (*s != NULLCHAR && *s != '\033') {
1491             if (*s == '(' || *s == '[') {
1492                 *p = NULLCHAR;
1493                 return retbuf;
1494             }
1495             *p++ = *s++;
1496         }
1497     }
1498     *p = NULLCHAR;
1499     return retbuf;
1500 }
1501
1502 /* Remove all highlighting escape sequences in s */
1503 char *
1504 StripHighlight(s)
1505      char *s;
1506 {
1507     static char retbuf[MSG_SIZ];
1508     char *p = retbuf;
1509
1510     while (*s != NULLCHAR) {
1511         while (*s == '\033') {
1512             while (*s != NULLCHAR && !isalpha(*s)) s++;
1513             if (*s != NULLCHAR) s++;
1514         }
1515         while (*s != NULLCHAR && *s != '\033') {
1516             *p++ = *s++;
1517         }
1518     }
1519     *p = NULLCHAR;
1520     return retbuf;
1521 }
1522
1523 char *variantNames[] = VARIANT_NAMES;
1524 char *
1525 VariantName(v)
1526      VariantClass v;
1527 {
1528     return variantNames[v];
1529 }
1530
1531
1532 /* Identify a variant from the strings the chess servers use or the
1533    PGN Variant tag names we use. */
1534 VariantClass
1535 StringToVariant(e)
1536      char *e;
1537 {
1538     char *p;
1539     int wnum = -1;
1540     VariantClass v = VariantNormal;
1541     int i, found = FALSE;
1542     char buf[MSG_SIZ];
1543
1544     if (!e) return v;
1545
1546     /* [HGM] skip over optional board-size prefixes */
1547     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1548         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1549         while( *e++ != '_');
1550     }
1551
1552     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1553       if (StrCaseStr(e, variantNames[i])) {
1554         v = (VariantClass) i;
1555         found = TRUE;
1556         break;
1557       }
1558     }
1559
1560     if (!found) {
1561       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1562           || StrCaseStr(e, "wild/fr") 
1563           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1564         v = VariantFischeRandom;
1565       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1566                  (i = 1, p = StrCaseStr(e, "w"))) {
1567         p += i;
1568         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1569         if (isdigit(*p)) {
1570           wnum = atoi(p);
1571         } else {
1572           wnum = -1;
1573         }
1574         switch (wnum) {
1575         case 0: /* FICS only, actually */
1576         case 1:
1577           /* Castling legal even if K starts on d-file */
1578           v = VariantWildCastle;
1579           break;
1580         case 2:
1581         case 3:
1582         case 4:
1583           /* Castling illegal even if K & R happen to start in
1584              normal positions. */
1585           v = VariantNoCastle;
1586           break;
1587         case 5:
1588         case 7:
1589         case 8:
1590         case 10:
1591         case 11:
1592         case 12:
1593         case 13:
1594         case 14:
1595         case 15:
1596         case 18:
1597         case 19:
1598           /* Castling legal iff K & R start in normal positions */
1599           v = VariantNormal;
1600           break;
1601         case 6:
1602         case 20:
1603         case 21:
1604           /* Special wilds for position setup; unclear what to do here */
1605           v = VariantLoadable;
1606           break;
1607         case 9:
1608           /* Bizarre ICC game */
1609           v = VariantTwoKings;
1610           break;
1611         case 16:
1612           v = VariantKriegspiel;
1613           break;
1614         case 17:
1615           v = VariantLosers;
1616           break;
1617         case 22:
1618           v = VariantFischeRandom;
1619           break;
1620         case 23:
1621           v = VariantCrazyhouse;
1622           break;
1623         case 24:
1624           v = VariantBughouse;
1625           break;
1626         case 25:
1627           v = Variant3Check;
1628           break;
1629         case 26:
1630           /* Not quite the same as FICS suicide! */
1631           v = VariantGiveaway;
1632           break;
1633         case 27:
1634           v = VariantAtomic;
1635           break;
1636         case 28:
1637           v = VariantShatranj;
1638           break;
1639
1640         /* Temporary names for future ICC types.  The name *will* change in 
1641            the next xboard/WinBoard release after ICC defines it. */
1642         case 29:
1643           v = Variant29;
1644           break;
1645         case 30:
1646           v = Variant30;
1647           break;
1648         case 31:
1649           v = Variant31;
1650           break;
1651         case 32:
1652           v = Variant32;
1653           break;
1654         case 33:
1655           v = Variant33;
1656           break;
1657         case 34:
1658           v = Variant34;
1659           break;
1660         case 35:
1661           v = Variant35;
1662           break;
1663         case 36:
1664           v = Variant36;
1665           break;
1666         case 37:
1667           v = VariantShogi;
1668           break;
1669         case 38:
1670           v = VariantXiangqi;
1671           break;
1672         case 39:
1673           v = VariantCourier;
1674           break;
1675         case 40:
1676           v = VariantGothic;
1677           break;
1678         case 41:
1679           v = VariantCapablanca;
1680           break;
1681         case 42:
1682           v = VariantKnightmate;
1683           break;
1684         case 43:
1685           v = VariantFairy;
1686           break;
1687         case 44:
1688           v = VariantCylinder;
1689           break;
1690         case 45:
1691           v = VariantFalcon;
1692           break;
1693         case 46:
1694           v = VariantCapaRandom;
1695           break;
1696         case 47:
1697           v = VariantBerolina;
1698           break;
1699         case 48:
1700           v = VariantJanus;
1701           break;
1702         case 49:
1703           v = VariantSuper;
1704           break;
1705         case 50:
1706           v = VariantGreat;
1707           break;
1708         case -1:
1709           /* Found "wild" or "w" in the string but no number;
1710              must assume it's normal chess. */
1711           v = VariantNormal;
1712           break;
1713         default:
1714           sprintf(buf, _("Unknown wild type %d"), wnum);
1715           DisplayError(buf, 0);
1716           v = VariantUnknown;
1717           break;
1718         }
1719       }
1720     }
1721     if (appData.debugMode) {
1722       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1723               e, wnum, VariantName(v));
1724     }
1725     return v;
1726 }
1727
1728 static int leftover_start = 0, leftover_len = 0;
1729 char star_match[STAR_MATCH_N][MSG_SIZ];
1730
1731 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1732    advance *index beyond it, and set leftover_start to the new value of
1733    *index; else return FALSE.  If pattern contains the character '*', it
1734    matches any sequence of characters not containing '\r', '\n', or the
1735    character following the '*' (if any), and the matched sequence(s) are
1736    copied into star_match.
1737    */
1738 int
1739 looking_at(buf, index, pattern)
1740      char *buf;
1741      int *index;
1742      char *pattern;
1743 {
1744     char *bufp = &buf[*index], *patternp = pattern;
1745     int star_count = 0;
1746     char *matchp = star_match[0];
1747     
1748     for (;;) {
1749         if (*patternp == NULLCHAR) {
1750             *index = leftover_start = bufp - buf;
1751             *matchp = NULLCHAR;
1752             return TRUE;
1753         }
1754         if (*bufp == NULLCHAR) return FALSE;
1755         if (*patternp == '*') {
1756             if (*bufp == *(patternp + 1)) {
1757                 *matchp = NULLCHAR;
1758                 matchp = star_match[++star_count];
1759                 patternp += 2;
1760                 bufp++;
1761                 continue;
1762             } else if (*bufp == '\n' || *bufp == '\r') {
1763                 patternp++;
1764                 if (*patternp == NULLCHAR)
1765                   continue;
1766                 else
1767                   return FALSE;
1768             } else {
1769                 *matchp++ = *bufp++;
1770                 continue;
1771             }
1772         }
1773         if (*patternp != *bufp) return FALSE;
1774         patternp++;
1775         bufp++;
1776     }
1777 }
1778
1779 void
1780 SendToPlayer(data, length)
1781      char *data;
1782      int length;
1783 {
1784     int error, outCount;
1785     outCount = OutputToProcess(NoProc, data, length, &error);
1786     if (outCount < length) {
1787         DisplayFatalError(_("Error writing to display"), error, 1);
1788     }
1789 }
1790
1791 void
1792 PackHolding(packed, holding)
1793      char packed[];
1794      char *holding;
1795 {
1796     char *p = holding;
1797     char *q = packed;
1798     int runlength = 0;
1799     int curr = 9999;
1800     do {
1801         if (*p == curr) {
1802             runlength++;
1803         } else {
1804             switch (runlength) {
1805               case 0:
1806                 break;
1807               case 1:
1808                 *q++ = curr;
1809                 break;
1810               case 2:
1811                 *q++ = curr;
1812                 *q++ = curr;
1813                 break;
1814               default:
1815                 sprintf(q, "%d", runlength);
1816                 while (*q) q++;
1817                 *q++ = curr;
1818                 break;
1819             }
1820             runlength = 1;
1821             curr = *p;
1822         }
1823     } while (*p++);
1824     *q = NULLCHAR;
1825 }
1826
1827 /* Telnet protocol requests from the front end */
1828 void
1829 TelnetRequest(ddww, option)
1830      unsigned char ddww, option;
1831 {
1832     unsigned char msg[3];
1833     int outCount, outError;
1834
1835     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1836
1837     if (appData.debugMode) {
1838         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1839         switch (ddww) {
1840           case TN_DO:
1841             ddwwStr = "DO";
1842             break;
1843           case TN_DONT:
1844             ddwwStr = "DONT";
1845             break;
1846           case TN_WILL:
1847             ddwwStr = "WILL";
1848             break;
1849           case TN_WONT:
1850             ddwwStr = "WONT";
1851             break;
1852           default:
1853             ddwwStr = buf1;
1854             sprintf(buf1, "%d", ddww);
1855             break;
1856         }
1857         switch (option) {
1858           case TN_ECHO:
1859             optionStr = "ECHO";
1860             break;
1861           default:
1862             optionStr = buf2;
1863             sprintf(buf2, "%d", option);
1864             break;
1865         }
1866         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1867     }
1868     msg[0] = TN_IAC;
1869     msg[1] = ddww;
1870     msg[2] = option;
1871     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1872     if (outCount < 3) {
1873         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1874     }
1875 }
1876
1877 void
1878 DoEcho()
1879 {
1880     if (!appData.icsActive) return;
1881     TelnetRequest(TN_DO, TN_ECHO);
1882 }
1883
1884 void
1885 DontEcho()
1886 {
1887     if (!appData.icsActive) return;
1888     TelnetRequest(TN_DONT, TN_ECHO);
1889 }
1890
1891 void
1892 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1893 {
1894     /* put the holdings sent to us by the server on the board holdings area */
1895     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1896     char p;
1897     ChessSquare piece;
1898
1899     if(gameInfo.holdingsWidth < 2)  return;
1900
1901     if( (int)lowestPiece >= BlackPawn ) {
1902         holdingsColumn = 0;
1903         countsColumn = 1;
1904         holdingsStartRow = BOARD_HEIGHT-1;
1905         direction = -1;
1906     } else {
1907         holdingsColumn = BOARD_WIDTH-1;
1908         countsColumn = BOARD_WIDTH-2;
1909         holdingsStartRow = 0;
1910         direction = 1;
1911     }
1912
1913     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1914         board[i][holdingsColumn] = EmptySquare;
1915         board[i][countsColumn]   = (ChessSquare) 0;
1916     }
1917     while( (p=*holdings++) != NULLCHAR ) {
1918         piece = CharToPiece( ToUpper(p) );
1919         if(piece == EmptySquare) continue;
1920         /*j = (int) piece - (int) WhitePawn;*/
1921         j = PieceToNumber(piece);
1922         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1923         if(j < 0) continue;               /* should not happen */
1924         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1925         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1926         board[holdingsStartRow+j*direction][countsColumn]++;
1927     }
1928
1929 }
1930
1931
1932 void
1933 VariantSwitch(Board board, VariantClass newVariant)
1934 {
1935    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1936    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1937 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1938
1939    startedFromPositionFile = FALSE;
1940    if(gameInfo.variant == newVariant) return;
1941
1942    /* [HGM] This routine is called each time an assignment is made to
1943     * gameInfo.variant during a game, to make sure the board sizes
1944     * are set to match the new variant. If that means adding or deleting
1945     * holdings, we shift the playing board accordingly
1946     * This kludge is needed because in ICS observe mode, we get boards
1947     * of an ongoing game without knowing the variant, and learn about the
1948     * latter only later. This can be because of the move list we requested,
1949     * in which case the game history is refilled from the beginning anyway,
1950     * but also when receiving holdings of a crazyhouse game. In the latter
1951     * case we want to add those holdings to the already received position.
1952     */
1953
1954
1955   if (appData.debugMode) {
1956     fprintf(debugFP, "Switch board from %s to %s\n",
1957                VariantName(gameInfo.variant), VariantName(newVariant));
1958     setbuf(debugFP, NULL);
1959   }
1960     shuffleOpenings = 0;       /* [HGM] shuffle */
1961     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1962     switch(newVariant) {
1963             case VariantShogi:
1964               newWidth = 9;  newHeight = 9;
1965               gameInfo.holdingsSize = 7;
1966             case VariantBughouse:
1967             case VariantCrazyhouse:
1968               newHoldingsWidth = 2; break;
1969             default:
1970               newHoldingsWidth = gameInfo.holdingsSize = 0;
1971     }
1972
1973     if(newWidth  != gameInfo.boardWidth  ||
1974        newHeight != gameInfo.boardHeight ||
1975        newHoldingsWidth != gameInfo.holdingsWidth ) {
1976
1977         /* shift position to new playing area, if needed */
1978         if(newHoldingsWidth > gameInfo.holdingsWidth) {
1979            for(i=0; i<BOARD_HEIGHT; i++) 
1980                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1981                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1982                                                      board[i][j];
1983            for(i=0; i<newHeight; i++) {
1984                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1985                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1986            }
1987         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1988            for(i=0; i<BOARD_HEIGHT; i++)
1989                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1990                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1991                                                  board[i][j];
1992         }
1993
1994         gameInfo.boardWidth  = newWidth;
1995         gameInfo.boardHeight = newHeight;
1996         gameInfo.holdingsWidth = newHoldingsWidth;
1997         gameInfo.variant = newVariant;
1998         InitDrawingSizes(-2, 0);
1999
2000         /* [HGM] The following should definitely be solved in a better way */
2001         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2002     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2003
2004     forwardMostMove = oldForwardMostMove;
2005     backwardMostMove = oldBackwardMostMove;
2006     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2007 }
2008
2009 static int loggedOn = FALSE;
2010
2011 /*-- Game start info cache: --*/
2012 int gs_gamenum;
2013 char gs_kind[MSG_SIZ];
2014 static char player1Name[128] = "";
2015 static char player2Name[128] = "";
2016 static int player1Rating = -1;
2017 static int player2Rating = -1;
2018 /*----------------------------*/
2019
2020 ColorClass curColor = ColorNormal;
2021 int suppressKibitz = 0;
2022
2023 void
2024 read_from_ics(isr, closure, data, count, error)
2025      InputSourceRef isr;
2026      VOIDSTAR closure;
2027      char *data;
2028      int count;
2029      int error;
2030 {
2031 #define BUF_SIZE 8192
2032 #define STARTED_NONE 0
2033 #define STARTED_MOVES 1
2034 #define STARTED_BOARD 2
2035 #define STARTED_OBSERVE 3
2036 #define STARTED_HOLDINGS 4
2037 #define STARTED_CHATTER 5
2038 #define STARTED_COMMENT 6
2039 #define STARTED_MOVES_NOHIDE 7
2040     
2041     static int started = STARTED_NONE;
2042     static char parse[20000];
2043     static int parse_pos = 0;
2044     static char buf[BUF_SIZE + 1];
2045     static int firstTime = TRUE, intfSet = FALSE;
2046     static ColorClass prevColor = ColorNormal;
2047     static int savingComment = FALSE;
2048     char str[500];
2049     int i, oldi;
2050     int buf_len;
2051     int next_out;
2052     int tkind;
2053     int backup;    /* [DM] For zippy color lines */
2054     char *p;
2055     char talker[MSG_SIZ]; // [HGM] chat
2056     int channel;
2057
2058     if (appData.debugMode) {
2059       if (!error) {
2060         fprintf(debugFP, "<ICS: ");
2061         show_bytes(debugFP, data, count);
2062         fprintf(debugFP, "\n");
2063       }
2064     }
2065
2066     if (appData.debugMode) { int f = forwardMostMove;
2067         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2068                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2069     }
2070     if (count > 0) {
2071         /* If last read ended with a partial line that we couldn't parse,
2072            prepend it to the new read and try again. */
2073         if (leftover_len > 0) {
2074             for (i=0; i<leftover_len; i++)
2075               buf[i] = buf[leftover_start + i];
2076         }
2077
2078         /* Copy in new characters, removing nulls and \r's */
2079         buf_len = leftover_len;
2080         for (i = 0; i < count; i++) {
2081             if (data[i] != NULLCHAR && data[i] != '\r')
2082               buf[buf_len++] = data[i];
2083             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2084                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2085                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2086                 if(buf_len == 0 || buf[buf_len-1] != ' ')
2087                    buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2088             }
2089         }
2090
2091         buf[buf_len] = NULLCHAR;
2092         next_out = leftover_len;
2093         leftover_start = 0;
2094         
2095         i = 0;
2096         while (i < buf_len) {
2097             /* Deal with part of the TELNET option negotiation
2098                protocol.  We refuse to do anything beyond the
2099                defaults, except that we allow the WILL ECHO option,
2100                which ICS uses to turn off password echoing when we are
2101                directly connected to it.  We reject this option
2102                if localLineEditing mode is on (always on in xboard)
2103                and we are talking to port 23, which might be a real
2104                telnet server that will try to keep WILL ECHO on permanently.
2105              */
2106             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2107                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2108                 unsigned char option;
2109                 oldi = i;
2110                 switch ((unsigned char) buf[++i]) {
2111                   case TN_WILL:
2112                     if (appData.debugMode)
2113                       fprintf(debugFP, "\n<WILL ");
2114                     switch (option = (unsigned char) buf[++i]) {
2115                       case TN_ECHO:
2116                         if (appData.debugMode)
2117                           fprintf(debugFP, "ECHO ");
2118                         /* Reply only if this is a change, according
2119                            to the protocol rules. */
2120                         if (remoteEchoOption) break;
2121                         if (appData.localLineEditing &&
2122                             atoi(appData.icsPort) == TN_PORT) {
2123                             TelnetRequest(TN_DONT, TN_ECHO);
2124                         } else {
2125                             EchoOff();
2126                             TelnetRequest(TN_DO, TN_ECHO);
2127                             remoteEchoOption = TRUE;
2128                         }
2129                         break;
2130                       default:
2131                         if (appData.debugMode)
2132                           fprintf(debugFP, "%d ", option);
2133                         /* Whatever this is, we don't want it. */
2134                         TelnetRequest(TN_DONT, option);
2135                         break;
2136                     }
2137                     break;
2138                   case TN_WONT:
2139                     if (appData.debugMode)
2140                       fprintf(debugFP, "\n<WONT ");
2141                     switch (option = (unsigned char) buf[++i]) {
2142                       case TN_ECHO:
2143                         if (appData.debugMode)
2144                           fprintf(debugFP, "ECHO ");
2145                         /* Reply only if this is a change, according
2146                            to the protocol rules. */
2147                         if (!remoteEchoOption) break;
2148                         EchoOn();
2149                         TelnetRequest(TN_DONT, TN_ECHO);
2150                         remoteEchoOption = FALSE;
2151                         break;
2152                       default:
2153                         if (appData.debugMode)
2154                           fprintf(debugFP, "%d ", (unsigned char) option);
2155                         /* Whatever this is, it must already be turned
2156                            off, because we never agree to turn on
2157                            anything non-default, so according to the
2158                            protocol rules, we don't reply. */
2159                         break;
2160                     }
2161                     break;
2162                   case TN_DO:
2163                     if (appData.debugMode)
2164                       fprintf(debugFP, "\n<DO ");
2165                     switch (option = (unsigned char) buf[++i]) {
2166                       default:
2167                         /* Whatever this is, we refuse to do it. */
2168                         if (appData.debugMode)
2169                           fprintf(debugFP, "%d ", option);
2170                         TelnetRequest(TN_WONT, option);
2171                         break;
2172                     }
2173                     break;
2174                   case TN_DONT:
2175                     if (appData.debugMode)
2176                       fprintf(debugFP, "\n<DONT ");
2177                     switch (option = (unsigned char) buf[++i]) {
2178                       default:
2179                         if (appData.debugMode)
2180                           fprintf(debugFP, "%d ", option);
2181                         /* Whatever this is, we are already not doing
2182                            it, because we never agree to do anything
2183                            non-default, so according to the protocol
2184                            rules, we don't reply. */
2185                         break;
2186                     }
2187                     break;
2188                   case TN_IAC:
2189                     if (appData.debugMode)
2190                       fprintf(debugFP, "\n<IAC ");
2191                     /* Doubled IAC; pass it through */
2192                     i--;
2193                     break;
2194                   default:
2195                     if (appData.debugMode)
2196                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2197                     /* Drop all other telnet commands on the floor */
2198                     break;
2199                 }
2200                 if (oldi > next_out)
2201                   SendToPlayer(&buf[next_out], oldi - next_out);
2202                 if (++i > next_out)
2203                   next_out = i;
2204                 continue;
2205             }
2206                 
2207             /* OK, this at least will *usually* work */
2208             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2209                 loggedOn = TRUE;
2210             }
2211             
2212             if (loggedOn && !intfSet) {
2213                 if (ics_type == ICS_ICC) {
2214                   sprintf(str,
2215                           "/set-quietly interface %s\n/set-quietly style 12\n",
2216                           programVersion);
2217
2218                 } else if (ics_type == ICS_CHESSNET) {
2219                   sprintf(str, "/style 12\n");
2220                 } else {
2221                   strcpy(str, "alias $ @\n$set interface ");
2222                   strcat(str, programVersion);
2223                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2224 #ifdef WIN32
2225                   strcat(str, "$iset nohighlight 1\n");
2226 #endif
2227                   strcat(str, "$iset lock 1\n$style 12\n");
2228                   NotifyFrontendLogin();
2229                 }
2230                 SendToICS(str);
2231                 intfSet = TRUE;
2232             }
2233
2234             if (started == STARTED_COMMENT) {
2235                 /* Accumulate characters in comment */
2236                 parse[parse_pos++] = buf[i];
2237                 if (buf[i] == '\n') {
2238                     parse[parse_pos] = NULLCHAR;
2239                     if(chattingPartner>=0) {
2240                         char mess[MSG_SIZ];
2241                         sprintf(mess, "%s%s", talker, parse);
2242                         OutputChatMessage(chattingPartner, mess);
2243                         chattingPartner = -1;
2244                     } else
2245                     if(!suppressKibitz) // [HGM] kibitz
2246                         AppendComment(forwardMostMove, StripHighlight(parse));
2247                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2248                         int nrDigit = 0, nrAlph = 0, i;
2249                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2250                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2251                         parse[parse_pos] = NULLCHAR;
2252                         // try to be smart: if it does not look like search info, it should go to
2253                         // ICS interaction window after all, not to engine-output window.
2254                         for(i=0; i<parse_pos; i++) { // count letters and digits
2255                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2256                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2257                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2258                         }
2259                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2260                             int depth=0; float score;
2261                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2262                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2263                                 pvInfoList[forwardMostMove-1].depth = depth;
2264                                 pvInfoList[forwardMostMove-1].score = 100*score;
2265                             }
2266                             OutputKibitz(suppressKibitz, parse);
2267                         } else {
2268                             char tmp[MSG_SIZ];
2269                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2270                             SendToPlayer(tmp, strlen(tmp));
2271                         }
2272                     }
2273                     started = STARTED_NONE;
2274                 } else {
2275                     /* Don't match patterns against characters in chatter */
2276                     i++;
2277                     continue;
2278                 }
2279             }
2280             if (started == STARTED_CHATTER) {
2281                 if (buf[i] != '\n') {
2282                     /* Don't match patterns against characters in chatter */
2283                     i++;
2284                     continue;
2285                 }
2286                 started = STARTED_NONE;
2287             }
2288
2289             /* Kludge to deal with rcmd protocol */
2290             if (firstTime && looking_at(buf, &i, "\001*")) {
2291                 DisplayFatalError(&buf[1], 0, 1);
2292                 continue;
2293             } else {
2294                 firstTime = FALSE;
2295             }
2296
2297             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2298                 ics_type = ICS_ICC;
2299                 ics_prefix = "/";
2300                 if (appData.debugMode)
2301                   fprintf(debugFP, "ics_type %d\n", ics_type);
2302                 continue;
2303             }
2304             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2305                 ics_type = ICS_FICS;
2306                 ics_prefix = "$";
2307                 if (appData.debugMode)
2308                   fprintf(debugFP, "ics_type %d\n", ics_type);
2309                 continue;
2310             }
2311             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2312                 ics_type = ICS_CHESSNET;
2313                 ics_prefix = "/";
2314                 if (appData.debugMode)
2315                   fprintf(debugFP, "ics_type %d\n", ics_type);
2316                 continue;
2317             }
2318
2319             if (!loggedOn &&
2320                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2321                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2322                  looking_at(buf, &i, "will be \"*\""))) {
2323               strcpy(ics_handle, star_match[0]);
2324               continue;
2325             }
2326
2327             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2328               char buf[MSG_SIZ];
2329               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2330               DisplayIcsInteractionTitle(buf);
2331               have_set_title = TRUE;
2332             }
2333
2334             /* skip finger notes */
2335             if (started == STARTED_NONE &&
2336                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2337                  (buf[i] == '1' && buf[i+1] == '0')) &&
2338                 buf[i+2] == ':' && buf[i+3] == ' ') {
2339               started = STARTED_CHATTER;
2340               i += 3;
2341               continue;
2342             }
2343
2344             /* skip formula vars */
2345             if (started == STARTED_NONE &&
2346                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2347               started = STARTED_CHATTER;
2348               i += 3;
2349               continue;
2350             }
2351
2352             oldi = i;
2353             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2354             if (appData.autoKibitz && started == STARTED_NONE && 
2355                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2356                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2357                 if(looking_at(buf, &i, "* kibitzes: ") &&
2358                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2359                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2360                         suppressKibitz = TRUE;
2361                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2362                                 && (gameMode == IcsPlayingWhite)) ||
2363                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2364                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2365                             started = STARTED_CHATTER; // own kibitz we simply discard
2366                         else {
2367                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2368                             parse_pos = 0; parse[0] = NULLCHAR;
2369                             savingComment = TRUE;
2370                             suppressKibitz = gameMode != IcsObserving ? 2 :
2371                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2372                         } 
2373                         continue;
2374                 } else
2375                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2376                     started = STARTED_CHATTER;
2377                     suppressKibitz = TRUE;
2378                 }
2379             } // [HGM] kibitz: end of patch
2380
2381 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2382
2383             // [HGM] chat: intercept tells by users for which we have an open chat window
2384             channel = -1;
2385             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2386                                            looking_at(buf, &i, "* whispers:") ||
2387                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2388                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2389                 int p;
2390                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2391                 chattingPartner = -1;
2392
2393                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2394                 for(p=0; p<MAX_CHAT; p++) {
2395                     if(channel == atoi(chatPartner[p])) {
2396                     talker[0] = '['; strcat(talker, "]");
2397                     chattingPartner = p; break;
2398                     }
2399                 } else
2400                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2401                 for(p=0; p<MAX_CHAT; p++) {
2402                     if(!strcmp("WHISPER", chatPartner[p])) {
2403                         talker[0] = '['; strcat(talker, "]");
2404                         chattingPartner = p; break;
2405                     }
2406                 }
2407                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2408                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2409                     talker[0] = 0;
2410                     chattingPartner = p; break;
2411                 }
2412                 if(chattingPartner<0) i = oldi; else {
2413                     started = STARTED_COMMENT;
2414                     parse_pos = 0; parse[0] = NULLCHAR;
2415                     savingComment = TRUE;
2416                     suppressKibitz = TRUE;
2417                 }
2418             } // [HGM] chat: end of patch
2419
2420             if (appData.zippyTalk || appData.zippyPlay) {
2421                 /* [DM] Backup address for color zippy lines */
2422                 backup = i;
2423 #if ZIPPY
2424        #ifdef WIN32
2425                if (loggedOn == TRUE)
2426                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2427                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2428        #else
2429                 if (ZippyControl(buf, &i) ||
2430                     ZippyConverse(buf, &i) ||
2431                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2432                       loggedOn = TRUE;
2433                       if (!appData.colorize) continue;
2434                 }
2435        #endif
2436 #endif
2437             } // [DM] 'else { ' deleted
2438                 if (
2439                     /* Regular tells and says */
2440                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2441                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2442                     looking_at(buf, &i, "* says: ") ||
2443                     /* Don't color "message" or "messages" output */
2444                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2445                     looking_at(buf, &i, "*. * at *:*: ") ||
2446                     looking_at(buf, &i, "--* (*:*): ") ||
2447                     /* Message notifications (same color as tells) */
2448                     looking_at(buf, &i, "* has left a message ") ||
2449                     looking_at(buf, &i, "* just sent you a message:\n") ||
2450                     /* Whispers and kibitzes */
2451                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2452                     looking_at(buf, &i, "* kibitzes: ") ||
2453                     /* Channel tells */
2454                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2455
2456                   if (tkind == 1 && strchr(star_match[0], ':')) {
2457                       /* Avoid "tells you:" spoofs in channels */
2458                      tkind = 3;
2459                   }
2460                   if (star_match[0][0] == NULLCHAR ||
2461                       strchr(star_match[0], ' ') ||
2462                       (tkind == 3 && strchr(star_match[1], ' '))) {
2463                     /* Reject bogus matches */
2464                     i = oldi;
2465                   } else {
2466                     if (appData.colorize) {
2467                       if (oldi > next_out) {
2468                         SendToPlayer(&buf[next_out], oldi - next_out);
2469                         next_out = oldi;
2470                       }
2471                       switch (tkind) {
2472                       case 1:
2473                         Colorize(ColorTell, FALSE);
2474                         curColor = ColorTell;
2475                         break;
2476                       case 2:
2477                         Colorize(ColorKibitz, FALSE);
2478                         curColor = ColorKibitz;
2479                         break;
2480                       case 3:
2481                         p = strrchr(star_match[1], '(');
2482                         if (p == NULL) {
2483                           p = star_match[1];
2484                         } else {
2485                           p++;
2486                         }
2487                         if (atoi(p) == 1) {
2488                           Colorize(ColorChannel1, FALSE);
2489                           curColor = ColorChannel1;
2490                         } else {
2491                           Colorize(ColorChannel, FALSE);
2492                           curColor = ColorChannel;
2493                         }
2494                         break;
2495                       case 5:
2496                         curColor = ColorNormal;
2497                         break;
2498                       }
2499                     }
2500                     if (started == STARTED_NONE && appData.autoComment &&
2501                         (gameMode == IcsObserving ||
2502                          gameMode == IcsPlayingWhite ||
2503                          gameMode == IcsPlayingBlack)) {
2504                       parse_pos = i - oldi;
2505                       memcpy(parse, &buf[oldi], parse_pos);
2506                       parse[parse_pos] = NULLCHAR;
2507                       started = STARTED_COMMENT;
2508                       savingComment = TRUE;
2509                     } else {
2510                       started = STARTED_CHATTER;
2511                       savingComment = FALSE;
2512                     }
2513                     loggedOn = TRUE;
2514                     continue;
2515                   }
2516                 }
2517
2518                 if (looking_at(buf, &i, "* s-shouts: ") ||
2519                     looking_at(buf, &i, "* c-shouts: ")) {
2520                     if (appData.colorize) {
2521                         if (oldi > next_out) {
2522                             SendToPlayer(&buf[next_out], oldi - next_out);
2523                             next_out = oldi;
2524                         }
2525                         Colorize(ColorSShout, FALSE);
2526                         curColor = ColorSShout;
2527                     }
2528                     loggedOn = TRUE;
2529                     started = STARTED_CHATTER;
2530                     continue;
2531                 }
2532
2533                 if (looking_at(buf, &i, "--->")) {
2534                     loggedOn = TRUE;
2535                     continue;
2536                 }
2537
2538                 if (looking_at(buf, &i, "* shouts: ") ||
2539                     looking_at(buf, &i, "--> ")) {
2540                     if (appData.colorize) {
2541                         if (oldi > next_out) {
2542                             SendToPlayer(&buf[next_out], oldi - next_out);
2543                             next_out = oldi;
2544                         }
2545                         Colorize(ColorShout, FALSE);
2546                         curColor = ColorShout;
2547                     }
2548                     loggedOn = TRUE;
2549                     started = STARTED_CHATTER;
2550                     continue;
2551                 }
2552
2553                 if (looking_at( buf, &i, "Challenge:")) {
2554                     if (appData.colorize) {
2555                         if (oldi > next_out) {
2556                             SendToPlayer(&buf[next_out], oldi - next_out);
2557                             next_out = oldi;
2558                         }
2559                         Colorize(ColorChallenge, FALSE);
2560                         curColor = ColorChallenge;
2561                     }
2562                     loggedOn = TRUE;
2563                     continue;
2564                 }
2565
2566                 if (looking_at(buf, &i, "* offers you") ||
2567                     looking_at(buf, &i, "* offers to be") ||
2568                     looking_at(buf, &i, "* would like to") ||
2569                     looking_at(buf, &i, "* requests to") ||
2570                     looking_at(buf, &i, "Your opponent offers") ||
2571                     looking_at(buf, &i, "Your opponent requests")) {
2572
2573                     if (appData.colorize) {
2574                         if (oldi > next_out) {
2575                             SendToPlayer(&buf[next_out], oldi - next_out);
2576                             next_out = oldi;
2577                         }
2578                         Colorize(ColorRequest, FALSE);
2579                         curColor = ColorRequest;
2580                     }
2581                     continue;
2582                 }
2583
2584                 if (looking_at(buf, &i, "* (*) seeking")) {
2585                     if (appData.colorize) {
2586                         if (oldi > next_out) {
2587                             SendToPlayer(&buf[next_out], oldi - next_out);
2588                             next_out = oldi;
2589                         }
2590                         Colorize(ColorSeek, FALSE);
2591                         curColor = ColorSeek;
2592                     }
2593                     continue;
2594             }
2595
2596             if (looking_at(buf, &i, "\\   ")) {
2597                 if (prevColor != ColorNormal) {
2598                     if (oldi > next_out) {
2599                         SendToPlayer(&buf[next_out], oldi - next_out);
2600                         next_out = oldi;
2601                     }
2602                     Colorize(prevColor, TRUE);
2603                     curColor = prevColor;
2604                 }
2605                 if (savingComment) {
2606                     parse_pos = i - oldi;
2607                     memcpy(parse, &buf[oldi], parse_pos);
2608                     parse[parse_pos] = NULLCHAR;
2609                     started = STARTED_COMMENT;
2610                 } else {
2611                     started = STARTED_CHATTER;
2612                 }
2613                 continue;
2614             }
2615
2616             if (looking_at(buf, &i, "Black Strength :") ||
2617                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2618                 looking_at(buf, &i, "<10>") ||
2619                 looking_at(buf, &i, "#@#")) {
2620                 /* Wrong board style */
2621                 loggedOn = TRUE;
2622                 SendToICS(ics_prefix);
2623                 SendToICS("set style 12\n");
2624                 SendToICS(ics_prefix);
2625                 SendToICS("refresh\n");
2626                 continue;
2627             }
2628             
2629             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2630                 ICSInitScript();
2631                 have_sent_ICS_logon = 1;
2632                 continue;
2633             }
2634               
2635             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2636                 (looking_at(buf, &i, "\n<12> ") ||
2637                  looking_at(buf, &i, "<12> "))) {
2638                 loggedOn = TRUE;
2639                 if (oldi > next_out) {
2640                     SendToPlayer(&buf[next_out], oldi - next_out);
2641                 }
2642                 next_out = i;
2643                 started = STARTED_BOARD;
2644                 parse_pos = 0;
2645                 continue;
2646             }
2647
2648             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2649                 looking_at(buf, &i, "<b1> ")) {
2650                 if (oldi > next_out) {
2651                     SendToPlayer(&buf[next_out], oldi - next_out);
2652                 }
2653                 next_out = i;
2654                 started = STARTED_HOLDINGS;
2655                 parse_pos = 0;
2656                 continue;
2657             }
2658
2659             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2660                 loggedOn = TRUE;
2661                 /* Header for a move list -- first line */
2662
2663                 switch (ics_getting_history) {
2664                   case H_FALSE:
2665                     switch (gameMode) {
2666                       case IcsIdle:
2667                       case BeginningOfGame:
2668                         /* User typed "moves" or "oldmoves" while we
2669                            were idle.  Pretend we asked for these
2670                            moves and soak them up so user can step
2671                            through them and/or save them.
2672                            */
2673                         Reset(FALSE, TRUE);
2674                         gameMode = IcsObserving;
2675                         ModeHighlight();
2676                         ics_gamenum = -1;
2677                         ics_getting_history = H_GOT_UNREQ_HEADER;
2678                         break;
2679                       case EditGame: /*?*/
2680                       case EditPosition: /*?*/
2681                         /* Should above feature work in these modes too? */
2682                         /* For now it doesn't */
2683                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2684                         break;
2685                       default:
2686                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2687                         break;
2688                     }
2689                     break;
2690                   case H_REQUESTED:
2691                     /* Is this the right one? */
2692                     if (gameInfo.white && gameInfo.black &&
2693                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2694                         strcmp(gameInfo.black, star_match[2]) == 0) {
2695                         /* All is well */
2696                         ics_getting_history = H_GOT_REQ_HEADER;
2697                     }
2698                     break;
2699                   case H_GOT_REQ_HEADER:
2700                   case H_GOT_UNREQ_HEADER:
2701                   case H_GOT_UNWANTED_HEADER:
2702                   case H_GETTING_MOVES:
2703                     /* Should not happen */
2704                     DisplayError(_("Error gathering move list: two headers"), 0);
2705                     ics_getting_history = H_FALSE;
2706                     break;
2707                 }
2708
2709                 /* Save player ratings into gameInfo if needed */
2710                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2711                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2712                     (gameInfo.whiteRating == -1 ||
2713                      gameInfo.blackRating == -1)) {
2714
2715                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2716                     gameInfo.blackRating = string_to_rating(star_match[3]);
2717                     if (appData.debugMode)
2718                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2719                               gameInfo.whiteRating, gameInfo.blackRating);
2720                 }
2721                 continue;
2722             }
2723
2724             if (looking_at(buf, &i,
2725               "* * match, initial time: * minute*, increment: * second")) {
2726                 /* Header for a move list -- second line */
2727                 /* Initial board will follow if this is a wild game */
2728                 if (gameInfo.event != NULL) free(gameInfo.event);
2729                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2730                 gameInfo.event = StrSave(str);
2731                 /* [HGM] we switched variant. Translate boards if needed. */
2732                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2733                 continue;
2734             }
2735
2736             if (looking_at(buf, &i, "Move  ")) {
2737                 /* Beginning of a move list */
2738                 switch (ics_getting_history) {
2739                   case H_FALSE:
2740                     /* Normally should not happen */
2741                     /* Maybe user hit reset while we were parsing */
2742                     break;
2743                   case H_REQUESTED:
2744                     /* Happens if we are ignoring a move list that is not
2745                      * the one we just requested.  Common if the user
2746                      * tries to observe two games without turning off
2747                      * getMoveList */
2748                     break;
2749                   case H_GETTING_MOVES:
2750                     /* Should not happen */
2751                     DisplayError(_("Error gathering move list: nested"), 0);
2752                     ics_getting_history = H_FALSE;
2753                     break;
2754                   case H_GOT_REQ_HEADER:
2755                     ics_getting_history = H_GETTING_MOVES;
2756                     started = STARTED_MOVES;
2757                     parse_pos = 0;
2758                     if (oldi > next_out) {
2759                         SendToPlayer(&buf[next_out], oldi - next_out);
2760                     }
2761                     break;
2762                   case H_GOT_UNREQ_HEADER:
2763                     ics_getting_history = H_GETTING_MOVES;
2764                     started = STARTED_MOVES_NOHIDE;
2765                     parse_pos = 0;
2766                     break;
2767                   case H_GOT_UNWANTED_HEADER:
2768                     ics_getting_history = H_FALSE;
2769                     break;
2770                 }
2771                 continue;
2772             }                           
2773             
2774             if (looking_at(buf, &i, "% ") ||
2775                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2776                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2777                 savingComment = FALSE;
2778                 switch (started) {
2779                   case STARTED_MOVES:
2780                   case STARTED_MOVES_NOHIDE:
2781                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2782                     parse[parse_pos + i - oldi] = NULLCHAR;
2783                     ParseGameHistory(parse);
2784 #if ZIPPY
2785                     if (appData.zippyPlay && first.initDone) {
2786                         FeedMovesToProgram(&first, forwardMostMove);
2787                         if (gameMode == IcsPlayingWhite) {
2788                             if (WhiteOnMove(forwardMostMove)) {
2789                                 if (first.sendTime) {
2790                                   if (first.useColors) {
2791                                     SendToProgram("black\n", &first); 
2792                                   }
2793                                   SendTimeRemaining(&first, TRUE);
2794                                 }
2795                                 if (first.useColors) {
2796                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2797                                 }
2798                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2799                                 first.maybeThinking = TRUE;
2800                             } else {
2801                                 if (first.usePlayother) {
2802                                   if (first.sendTime) {
2803                                     SendTimeRemaining(&first, TRUE);
2804                                   }
2805                                   SendToProgram("playother\n", &first);
2806                                   firstMove = FALSE;
2807                                 } else {
2808                                   firstMove = TRUE;
2809                                 }
2810                             }
2811                         } else if (gameMode == IcsPlayingBlack) {
2812                             if (!WhiteOnMove(forwardMostMove)) {
2813                                 if (first.sendTime) {
2814                                   if (first.useColors) {
2815                                     SendToProgram("white\n", &first);
2816                                   }
2817                                   SendTimeRemaining(&first, FALSE);
2818                                 }
2819                                 if (first.useColors) {
2820                                   SendToProgram("black\n", &first);
2821                                 }
2822                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2823                                 first.maybeThinking = TRUE;
2824                             } else {
2825                                 if (first.usePlayother) {
2826                                   if (first.sendTime) {
2827                                     SendTimeRemaining(&first, FALSE);
2828                                   }
2829                                   SendToProgram("playother\n", &first);
2830                                   firstMove = FALSE;
2831                                 } else {
2832                                   firstMove = TRUE;
2833                                 }
2834                             }
2835                         }                       
2836                     }
2837 #endif
2838                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2839                         /* Moves came from oldmoves or moves command
2840                            while we weren't doing anything else.
2841                            */
2842                         currentMove = forwardMostMove;
2843                         ClearHighlights();/*!!could figure this out*/
2844                         flipView = appData.flipView;
2845                         DrawPosition(FALSE, boards[currentMove]);
2846                         DisplayBothClocks();
2847                         sprintf(str, "%s vs. %s",
2848                                 gameInfo.white, gameInfo.black);
2849                         DisplayTitle(str);
2850                         gameMode = IcsIdle;
2851                     } else {
2852                         /* Moves were history of an active game */
2853                         if (gameInfo.resultDetails != NULL) {
2854                             free(gameInfo.resultDetails);
2855                             gameInfo.resultDetails = NULL;
2856                         }
2857                     }
2858                     HistorySet(parseList, backwardMostMove,
2859                                forwardMostMove, currentMove-1);
2860                     DisplayMove(currentMove - 1);
2861                     if (started == STARTED_MOVES) next_out = i;
2862                     started = STARTED_NONE;
2863                     ics_getting_history = H_FALSE;
2864                     break;
2865
2866                   case STARTED_OBSERVE:
2867                     started = STARTED_NONE;
2868                     SendToICS(ics_prefix);
2869                     SendToICS("refresh\n");
2870                     break;
2871
2872                   default:
2873                     break;
2874                 }
2875                 if(bookHit) { // [HGM] book: simulate book reply
2876                     static char bookMove[MSG_SIZ]; // a bit generous?
2877
2878                     programStats.nodes = programStats.depth = programStats.time = 
2879                     programStats.score = programStats.got_only_move = 0;
2880                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2881
2882                     strcpy(bookMove, "move ");
2883                     strcat(bookMove, bookHit);
2884                     HandleMachineMove(bookMove, &first);
2885                 }
2886                 continue;
2887             }
2888             
2889             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2890                  started == STARTED_HOLDINGS ||
2891                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2892                 /* Accumulate characters in move list or board */
2893                 parse[parse_pos++] = buf[i];
2894             }
2895             
2896             /* Start of game messages.  Mostly we detect start of game
2897                when the first board image arrives.  On some versions
2898                of the ICS, though, we need to do a "refresh" after starting
2899                to observe in order to get the current board right away. */
2900             if (looking_at(buf, &i, "Adding game * to observation list")) {
2901                 started = STARTED_OBSERVE;
2902                 continue;
2903             }
2904
2905             /* Handle auto-observe */
2906             if (appData.autoObserve &&
2907                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2908                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2909                 char *player;
2910                 /* Choose the player that was highlighted, if any. */
2911                 if (star_match[0][0] == '\033' ||
2912                     star_match[1][0] != '\033') {
2913                     player = star_match[0];
2914                 } else {
2915                     player = star_match[2];
2916                 }
2917                 sprintf(str, "%sobserve %s\n",
2918                         ics_prefix, StripHighlightAndTitle(player));
2919                 SendToICS(str);
2920
2921                 /* Save ratings from notify string */
2922                 strcpy(player1Name, star_match[0]);
2923                 player1Rating = string_to_rating(star_match[1]);
2924                 strcpy(player2Name, star_match[2]);
2925                 player2Rating = string_to_rating(star_match[3]);
2926
2927                 if (appData.debugMode)
2928                   fprintf(debugFP, 
2929                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2930                           player1Name, player1Rating,
2931                           player2Name, player2Rating);
2932
2933                 continue;
2934             }
2935
2936             /* Deal with automatic examine mode after a game,
2937                and with IcsObserving -> IcsExamining transition */
2938             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2939                 looking_at(buf, &i, "has made you an examiner of game *")) {
2940
2941                 int gamenum = atoi(star_match[0]);
2942                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2943                     gamenum == ics_gamenum) {
2944                     /* We were already playing or observing this game;
2945                        no need to refetch history */
2946                     gameMode = IcsExamining;
2947                     if (pausing) {
2948                         pauseExamForwardMostMove = forwardMostMove;
2949                     } else if (currentMove < forwardMostMove) {
2950                         ForwardInner(forwardMostMove);
2951                     }
2952                 } else {
2953                     /* I don't think this case really can happen */
2954                     SendToICS(ics_prefix);
2955                     SendToICS("refresh\n");
2956                 }
2957                 continue;
2958             }    
2959             
2960             /* Error messages */
2961 //          if (ics_user_moved) {
2962             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2963                 if (looking_at(buf, &i, "Illegal move") ||
2964                     looking_at(buf, &i, "Not a legal move") ||
2965                     looking_at(buf, &i, "Your king is in check") ||
2966                     looking_at(buf, &i, "It isn't your turn") ||
2967                     looking_at(buf, &i, "It is not your move")) {
2968                     /* Illegal move */
2969                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2970                         currentMove = --forwardMostMove;
2971                         DisplayMove(currentMove - 1); /* before DMError */
2972                         DrawPosition(FALSE, boards[currentMove]);
2973                         SwitchClocks();
2974                         DisplayBothClocks();
2975                     }
2976                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2977                     ics_user_moved = 0;
2978                     continue;
2979                 }
2980             }
2981
2982             if (looking_at(buf, &i, "still have time") ||
2983                 looking_at(buf, &i, "not out of time") ||
2984                 looking_at(buf, &i, "either player is out of time") ||
2985                 looking_at(buf, &i, "has timeseal; checking")) {
2986                 /* We must have called his flag a little too soon */
2987                 whiteFlag = blackFlag = FALSE;
2988                 continue;
2989             }
2990
2991             if (looking_at(buf, &i, "added * seconds to") ||
2992                 looking_at(buf, &i, "seconds were added to")) {
2993                 /* Update the clocks */
2994                 SendToICS(ics_prefix);
2995                 SendToICS("refresh\n");
2996                 continue;
2997             }
2998
2999             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3000                 ics_clock_paused = TRUE;
3001                 StopClocks();
3002                 continue;
3003             }
3004
3005             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3006                 ics_clock_paused = FALSE;
3007                 StartClocks();
3008                 continue;
3009             }
3010
3011             /* Grab player ratings from the Creating: message.
3012                Note we have to check for the special case when
3013                the ICS inserts things like [white] or [black]. */
3014             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3015                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3016                 /* star_matches:
3017                    0    player 1 name (not necessarily white)
3018                    1    player 1 rating
3019                    2    empty, white, or black (IGNORED)
3020                    3    player 2 name (not necessarily black)
3021                    4    player 2 rating
3022                    
3023                    The names/ratings are sorted out when the game
3024                    actually starts (below).
3025                 */
3026                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3027                 player1Rating = string_to_rating(star_match[1]);
3028                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3029                 player2Rating = string_to_rating(star_match[4]);
3030
3031                 if (appData.debugMode)
3032                   fprintf(debugFP, 
3033                           "Ratings from 'Creating:' %s %d, %s %d\n",
3034                           player1Name, player1Rating,
3035                           player2Name, player2Rating);
3036
3037                 continue;
3038             }
3039             
3040             /* Improved generic start/end-of-game messages */
3041             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3042                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3043                 /* If tkind == 0: */
3044                 /* star_match[0] is the game number */
3045                 /*           [1] is the white player's name */
3046                 /*           [2] is the black player's name */
3047                 /* For end-of-game: */
3048                 /*           [3] is the reason for the game end */
3049                 /*           [4] is a PGN end game-token, preceded by " " */
3050                 /* For start-of-game: */
3051                 /*           [3] begins with "Creating" or "Continuing" */
3052                 /*           [4] is " *" or empty (don't care). */
3053                 int gamenum = atoi(star_match[0]);
3054                 char *whitename, *blackname, *why, *endtoken;
3055                 ChessMove endtype = (ChessMove) 0;
3056
3057                 if (tkind == 0) {
3058                   whitename = star_match[1];
3059                   blackname = star_match[2];
3060                   why = star_match[3];
3061                   endtoken = star_match[4];
3062                 } else {
3063                   whitename = star_match[1];
3064                   blackname = star_match[3];
3065                   why = star_match[5];
3066                   endtoken = star_match[6];
3067                 }
3068
3069                 /* Game start messages */
3070                 if (strncmp(why, "Creating ", 9) == 0 ||
3071                     strncmp(why, "Continuing ", 11) == 0) {
3072                     gs_gamenum = gamenum;
3073                     strcpy(gs_kind, strchr(why, ' ') + 1);
3074 #if ZIPPY
3075                     if (appData.zippyPlay) {
3076                         ZippyGameStart(whitename, blackname);
3077                     }
3078 #endif /*ZIPPY*/
3079                     continue;
3080                 }
3081
3082                 /* Game end messages */
3083                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3084                     ics_gamenum != gamenum) {
3085                     continue;
3086                 }
3087                 while (endtoken[0] == ' ') endtoken++;
3088                 switch (endtoken[0]) {
3089                   case '*':
3090                   default:
3091                     endtype = GameUnfinished;
3092                     break;
3093                   case '0':
3094                     endtype = BlackWins;
3095                     break;
3096                   case '1':
3097                     if (endtoken[1] == '/')
3098                       endtype = GameIsDrawn;
3099                     else
3100                       endtype = WhiteWins;
3101                     break;
3102                 }
3103                 GameEnds(endtype, why, GE_ICS);
3104 #if ZIPPY
3105                 if (appData.zippyPlay && first.initDone) {
3106                     ZippyGameEnd(endtype, why);
3107                     if (first.pr == NULL) {
3108                       /* Start the next process early so that we'll
3109                          be ready for the next challenge */
3110                       StartChessProgram(&first);
3111                     }
3112                     /* Send "new" early, in case this command takes
3113                        a long time to finish, so that we'll be ready
3114                        for the next challenge. */
3115                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3116                     Reset(TRUE, TRUE);
3117                 }
3118 #endif /*ZIPPY*/
3119                 continue;
3120             }
3121
3122             if (looking_at(buf, &i, "Removing game * from observation") ||
3123                 looking_at(buf, &i, "no longer observing game *") ||
3124                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3125                 if (gameMode == IcsObserving &&
3126                     atoi(star_match[0]) == ics_gamenum)
3127                   {
3128                       /* icsEngineAnalyze */
3129                       if (appData.icsEngineAnalyze) {
3130                             ExitAnalyzeMode();
3131                             ModeHighlight();
3132                       }
3133                       StopClocks();
3134                       gameMode = IcsIdle;
3135                       ics_gamenum = -1;
3136                       ics_user_moved = FALSE;
3137                   }
3138                 continue;
3139             }
3140
3141             if (looking_at(buf, &i, "no longer examining game *")) {
3142                 if (gameMode == IcsExamining &&
3143                     atoi(star_match[0]) == ics_gamenum)
3144                   {
3145                       gameMode = IcsIdle;
3146                       ics_gamenum = -1;
3147                       ics_user_moved = FALSE;
3148                   }
3149                 continue;
3150             }
3151
3152             /* Advance leftover_start past any newlines we find,
3153                so only partial lines can get reparsed */
3154             if (looking_at(buf, &i, "\n")) {
3155                 prevColor = curColor;
3156                 if (curColor != ColorNormal) {
3157                     if (oldi > next_out) {
3158                         SendToPlayer(&buf[next_out], oldi - next_out);
3159                         next_out = oldi;
3160                     }
3161                     Colorize(ColorNormal, FALSE);
3162                     curColor = ColorNormal;
3163                 }
3164                 if (started == STARTED_BOARD) {
3165                     started = STARTED_NONE;
3166                     parse[parse_pos] = NULLCHAR;
3167                     ParseBoard12(parse);
3168                     ics_user_moved = 0;
3169
3170                     /* Send premove here */
3171                     if (appData.premove) {
3172                       char str[MSG_SIZ];
3173                       if (currentMove == 0 &&
3174                           gameMode == IcsPlayingWhite &&
3175                           appData.premoveWhite) {
3176                         sprintf(str, "%s%s\n", ics_prefix,
3177                                 appData.premoveWhiteText);
3178                         if (appData.debugMode)
3179                           fprintf(debugFP, "Sending premove:\n");
3180                         SendToICS(str);
3181                       } else if (currentMove == 1 &&
3182                                  gameMode == IcsPlayingBlack &&
3183                                  appData.premoveBlack) {
3184                         sprintf(str, "%s%s\n", ics_prefix,
3185                                 appData.premoveBlackText);
3186                         if (appData.debugMode)
3187                           fprintf(debugFP, "Sending premove:\n");
3188                         SendToICS(str);
3189                       } else if (gotPremove) {
3190                         gotPremove = 0;
3191                         ClearPremoveHighlights();
3192                         if (appData.debugMode)
3193                           fprintf(debugFP, "Sending premove:\n");
3194                           UserMoveEvent(premoveFromX, premoveFromY, 
3195                                         premoveToX, premoveToY, 
3196                                         premovePromoChar);
3197                       }
3198                     }
3199
3200                     /* Usually suppress following prompt */
3201                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3202                         if (looking_at(buf, &i, "*% ")) {
3203                             savingComment = FALSE;
3204                         }
3205                     }
3206                     next_out = i;
3207                 } else if (started == STARTED_HOLDINGS) {
3208                     int gamenum;
3209                     char new_piece[MSG_SIZ];
3210                     started = STARTED_NONE;
3211                     parse[parse_pos] = NULLCHAR;
3212                     if (appData.debugMode)
3213                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3214                                                         parse, currentMove);
3215                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3216                         gamenum == ics_gamenum) {
3217                         if (gameInfo.variant == VariantNormal) {
3218                           /* [HGM] We seem to switch variant during a game!
3219                            * Presumably no holdings were displayed, so we have
3220                            * to move the position two files to the right to
3221                            * create room for them!
3222                            */
3223                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3224                           /* Get a move list just to see the header, which
3225                              will tell us whether this is really bug or zh */
3226                           if (ics_getting_history == H_FALSE) {
3227                             ics_getting_history = H_REQUESTED;
3228                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3229                             SendToICS(str);
3230                           }
3231                         }
3232                         new_piece[0] = NULLCHAR;
3233                         sscanf(parse, "game %d white [%s black [%s <- %s",
3234                                &gamenum, white_holding, black_holding,
3235                                new_piece);
3236                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3237                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3238                         /* [HGM] copy holdings to board holdings area */
3239                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3240                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3241 #if ZIPPY
3242                         if (appData.zippyPlay && first.initDone) {
3243                             ZippyHoldings(white_holding, black_holding,
3244                                           new_piece);
3245                         }
3246 #endif /*ZIPPY*/
3247                         if (tinyLayout || smallLayout) {
3248                             char wh[16], bh[16];
3249                             PackHolding(wh, white_holding);
3250                             PackHolding(bh, black_holding);
3251                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3252                                     gameInfo.white, gameInfo.black);
3253                         } else {
3254                             sprintf(str, "%s [%s] vs. %s [%s]",
3255                                     gameInfo.white, white_holding,
3256                                     gameInfo.black, black_holding);
3257                         }
3258
3259                         DrawPosition(FALSE, boards[currentMove]);
3260                         DisplayTitle(str);
3261                     }
3262                     /* Suppress following prompt */
3263                     if (looking_at(buf, &i, "*% ")) {
3264                         savingComment = FALSE;
3265                     }
3266                     next_out = i;
3267                 }
3268                 continue;
3269             }
3270
3271             i++;                /* skip unparsed character and loop back */
3272         }
3273         
3274         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3275             started != STARTED_HOLDINGS && i > next_out) {
3276             SendToPlayer(&buf[next_out], i - next_out);
3277             next_out = i;
3278         }
3279         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3280         
3281         leftover_len = buf_len - leftover_start;
3282         /* if buffer ends with something we couldn't parse,
3283            reparse it after appending the next read */
3284         
3285     } else if (count == 0) {
3286         RemoveInputSource(isr);
3287         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3288     } else {
3289         DisplayFatalError(_("Error reading from ICS"), error, 1);
3290     }
3291 }
3292
3293
3294 /* Board style 12 looks like this:
3295    
3296    <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
3297    
3298  * The "<12> " is stripped before it gets to this routine.  The two
3299  * trailing 0's (flip state and clock ticking) are later addition, and
3300  * some chess servers may not have them, or may have only the first.
3301  * Additional trailing fields may be added in the future.  
3302  */
3303
3304 #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"
3305
3306 #define RELATION_OBSERVING_PLAYED    0
3307 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3308 #define RELATION_PLAYING_MYMOVE      1
3309 #define RELATION_PLAYING_NOTMYMOVE  -1
3310 #define RELATION_EXAMINING           2
3311 #define RELATION_ISOLATED_BOARD     -3
3312 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3313
3314 void
3315 ParseBoard12(string)
3316      char *string;
3317
3318     GameMode newGameMode;
3319     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3320     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3321     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3322     char to_play, board_chars[200];
3323     char move_str[500], str[500], elapsed_time[500];
3324     char black[32], white[32];
3325     Board board;
3326     int prevMove = currentMove;
3327     int ticking = 2;
3328     ChessMove moveType;
3329     int fromX, fromY, toX, toY;
3330     char promoChar;
3331     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3332     char *bookHit = NULL; // [HGM] book
3333
3334     fromX = fromY = toX = toY = -1;
3335     
3336     newGame = FALSE;
3337
3338     if (appData.debugMode)
3339       fprintf(debugFP, _("Parsing board: %s\n"), string);
3340
3341     move_str[0] = NULLCHAR;
3342     elapsed_time[0] = NULLCHAR;
3343     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3344         int  i = 0, j;
3345         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3346             if(string[i] == ' ') { ranks++; files = 0; }
3347             else files++;
3348             i++;
3349         }
3350         for(j = 0; j <i; j++) board_chars[j] = string[j];
3351         board_chars[i] = '\0';
3352         string += i + 1;
3353     }
3354     n = sscanf(string, PATTERN, &to_play, &double_push,
3355                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3356                &gamenum, white, black, &relation, &basetime, &increment,
3357                &white_stren, &black_stren, &white_time, &black_time,
3358                &moveNum, str, elapsed_time, move_str, &ics_flip,
3359                &ticking);
3360
3361     if (n < 21) {
3362         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3363         DisplayError(str, 0);
3364         return;
3365     }
3366
3367     /* Convert the move number to internal form */
3368     moveNum = (moveNum - 1) * 2;
3369     if (to_play == 'B') moveNum++;
3370     if (moveNum >= MAX_MOVES) {
3371       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3372                         0, 1);
3373       return;
3374     }
3375     
3376     switch (relation) {
3377       case RELATION_OBSERVING_PLAYED:
3378       case RELATION_OBSERVING_STATIC:
3379         if (gamenum == -1) {
3380             /* Old ICC buglet */
3381             relation = RELATION_OBSERVING_STATIC;
3382         }
3383         newGameMode = IcsObserving;
3384         break;
3385       case RELATION_PLAYING_MYMOVE:
3386       case RELATION_PLAYING_NOTMYMOVE:
3387         newGameMode =
3388           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3389             IcsPlayingWhite : IcsPlayingBlack;
3390         break;
3391       case RELATION_EXAMINING:
3392         newGameMode = IcsExamining;
3393         break;
3394       case RELATION_ISOLATED_BOARD:
3395       default:
3396         /* Just display this board.  If user was doing something else,
3397            we will forget about it until the next board comes. */ 
3398         newGameMode = IcsIdle;
3399         break;
3400       case RELATION_STARTING_POSITION:
3401         newGameMode = gameMode;
3402         break;
3403     }
3404     
3405     /* Modify behavior for initial board display on move listing
3406        of wild games.
3407        */
3408     switch (ics_getting_history) {
3409       case H_FALSE:
3410       case H_REQUESTED:
3411         break;
3412       case H_GOT_REQ_HEADER:
3413       case H_GOT_UNREQ_HEADER:
3414         /* This is the initial position of the current game */
3415         gamenum = ics_gamenum;
3416         moveNum = 0;            /* old ICS bug workaround */
3417         if (to_play == 'B') {
3418           startedFromSetupPosition = TRUE;
3419           blackPlaysFirst = TRUE;
3420           moveNum = 1;
3421           if (forwardMostMove == 0) forwardMostMove = 1;
3422           if (backwardMostMove == 0) backwardMostMove = 1;
3423           if (currentMove == 0) currentMove = 1;
3424         }
3425         newGameMode = gameMode;
3426         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3427         break;
3428       case H_GOT_UNWANTED_HEADER:
3429         /* This is an initial board that we don't want */
3430         return;
3431       case H_GETTING_MOVES:
3432         /* Should not happen */
3433         DisplayError(_("Error gathering move list: extra board"), 0);
3434         ics_getting_history = H_FALSE;
3435         return;
3436     }
3437     
3438     /* Take action if this is the first board of a new game, or of a
3439        different game than is currently being displayed.  */
3440     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3441         relation == RELATION_ISOLATED_BOARD) {
3442         
3443         /* Forget the old game and get the history (if any) of the new one */
3444         if (gameMode != BeginningOfGame) {
3445           Reset(FALSE, TRUE);
3446         }
3447         newGame = TRUE;
3448         if (appData.autoRaiseBoard) BoardToTop();
3449         prevMove = -3;
3450         if (gamenum == -1) {
3451             newGameMode = IcsIdle;
3452         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3453                    appData.getMoveList) {
3454             /* Need to get game history */
3455             ics_getting_history = H_REQUESTED;
3456             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3457             SendToICS(str);
3458         }
3459         
3460         /* Initially flip the board to have black on the bottom if playing
3461            black or if the ICS flip flag is set, but let the user change
3462            it with the Flip View button. */
3463         flipView = appData.autoFlipView ? 
3464           (newGameMode == IcsPlayingBlack) || ics_flip :
3465           appData.flipView;
3466         
3467         /* Done with values from previous mode; copy in new ones */
3468         gameMode = newGameMode;
3469         ModeHighlight();
3470         ics_gamenum = gamenum;
3471         if (gamenum == gs_gamenum) {
3472             int klen = strlen(gs_kind);
3473             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3474             sprintf(str, "ICS %s", gs_kind);
3475             gameInfo.event = StrSave(str);
3476         } else {
3477             gameInfo.event = StrSave("ICS game");
3478         }
3479         gameInfo.site = StrSave(appData.icsHost);
3480         gameInfo.date = PGNDate();
3481         gameInfo.round = StrSave("-");
3482         gameInfo.white = StrSave(white);
3483         gameInfo.black = StrSave(black);
3484         timeControl = basetime * 60 * 1000;
3485         timeControl_2 = 0;
3486         timeIncrement = increment * 1000;
3487         movesPerSession = 0;
3488         gameInfo.timeControl = TimeControlTagValue();
3489         VariantSwitch(board, StringToVariant(gameInfo.event) );
3490   if (appData.debugMode) {
3491     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3492     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3493     setbuf(debugFP, NULL);
3494   }
3495
3496         gameInfo.outOfBook = NULL;
3497         
3498         /* Do we have the ratings? */
3499         if (strcmp(player1Name, white) == 0 &&
3500             strcmp(player2Name, black) == 0) {
3501             if (appData.debugMode)
3502               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3503                       player1Rating, player2Rating);
3504             gameInfo.whiteRating = player1Rating;
3505             gameInfo.blackRating = player2Rating;
3506         } else if (strcmp(player2Name, white) == 0 &&
3507                    strcmp(player1Name, black) == 0) {
3508             if (appData.debugMode)
3509               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3510                       player2Rating, player1Rating);
3511             gameInfo.whiteRating = player2Rating;
3512             gameInfo.blackRating = player1Rating;
3513         }
3514         player1Name[0] = player2Name[0] = NULLCHAR;
3515
3516         /* Silence shouts if requested */
3517         if (appData.quietPlay &&
3518             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3519             SendToICS(ics_prefix);
3520             SendToICS("set shout 0\n");
3521         }
3522     }
3523     
3524     /* Deal with midgame name changes */
3525     if (!newGame) {
3526         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3527             if (gameInfo.white) free(gameInfo.white);
3528             gameInfo.white = StrSave(white);
3529         }
3530         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3531             if (gameInfo.black) free(gameInfo.black);
3532             gameInfo.black = StrSave(black);
3533         }
3534     }
3535     
3536     /* Throw away game result if anything actually changes in examine mode */
3537     if (gameMode == IcsExamining && !newGame) {
3538         gameInfo.result = GameUnfinished;
3539         if (gameInfo.resultDetails != NULL) {
3540             free(gameInfo.resultDetails);
3541             gameInfo.resultDetails = NULL;
3542         }
3543     }
3544     
3545     /* In pausing && IcsExamining mode, we ignore boards coming
3546        in if they are in a different variation than we are. */
3547     if (pauseExamInvalid) return;
3548     if (pausing && gameMode == IcsExamining) {
3549         if (moveNum <= pauseExamForwardMostMove) {
3550             pauseExamInvalid = TRUE;
3551             forwardMostMove = pauseExamForwardMostMove;
3552             return;
3553         }
3554     }
3555     
3556   if (appData.debugMode) {
3557     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3558   }
3559     /* Parse the board */
3560     for (k = 0; k < ranks; k++) {
3561       for (j = 0; j < files; j++)
3562         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3563       if(gameInfo.holdingsWidth > 1) {
3564            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3565            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3566       }
3567     }
3568     CopyBoard(boards[moveNum], board);
3569     if (moveNum == 0) {
3570         startedFromSetupPosition =
3571           !CompareBoards(board, initialPosition);
3572         if(startedFromSetupPosition)
3573             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3574     }
3575
3576     /* [HGM] Set castling rights. Take the outermost Rooks,
3577        to make it also work for FRC opening positions. Note that board12
3578        is really defective for later FRC positions, as it has no way to
3579        indicate which Rook can castle if they are on the same side of King.
3580        For the initial position we grant rights to the outermost Rooks,
3581        and remember thos rights, and we then copy them on positions
3582        later in an FRC game. This means WB might not recognize castlings with
3583        Rooks that have moved back to their original position as illegal,
3584        but in ICS mode that is not its job anyway.
3585     */
3586     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3587     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3588
3589         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3590             if(board[0][i] == WhiteRook) j = i;
3591         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3593             if(board[0][i] == WhiteRook) j = i;
3594         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3595         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3596             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3597         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3598         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3599             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3600         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3601
3602         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3603         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3604             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3605         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3606             if(board[BOARD_HEIGHT-1][k] == bKing)
3607                 initialRights[5] = castlingRights[moveNum][5] = k;
3608     } else { int r;
3609         r = castlingRights[moveNum][0] = initialRights[0];
3610         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3611         r = castlingRights[moveNum][1] = initialRights[1];
3612         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3613         r = castlingRights[moveNum][3] = initialRights[3];
3614         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3615         r = castlingRights[moveNum][4] = initialRights[4];
3616         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3617         /* wildcastle kludge: always assume King has rights */
3618         r = castlingRights[moveNum][2] = initialRights[2];
3619         r = castlingRights[moveNum][5] = initialRights[5];
3620     }
3621     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3622     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3623
3624     
3625     if (ics_getting_history == H_GOT_REQ_HEADER ||
3626         ics_getting_history == H_GOT_UNREQ_HEADER) {
3627         /* This was an initial position from a move list, not
3628            the current position */
3629         return;
3630     }
3631     
3632     /* Update currentMove and known move number limits */
3633     newMove = newGame || moveNum > forwardMostMove;
3634
3635     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3636     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3637         takeback = forwardMostMove - moveNum;
3638         for (i = 0; i < takeback; i++) {
3639              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3640              SendToProgram("undo\n", &first);
3641         }
3642     }
3643
3644     if (newGame) {
3645         forwardMostMove = backwardMostMove = currentMove = moveNum;
3646         if (gameMode == IcsExamining && moveNum == 0) {
3647           /* Workaround for ICS limitation: we are not told the wild
3648              type when starting to examine a game.  But if we ask for
3649              the move list, the move list header will tell us */
3650             ics_getting_history = H_REQUESTED;
3651             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3652             SendToICS(str);
3653         }
3654     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3655                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3656         forwardMostMove = moveNum;
3657         if (!pausing || currentMove > forwardMostMove)
3658           currentMove = forwardMostMove;
3659     } else {
3660         /* New part of history that is not contiguous with old part */ 
3661         if (pausing && gameMode == IcsExamining) {
3662             pauseExamInvalid = TRUE;
3663             forwardMostMove = pauseExamForwardMostMove;
3664             return;
3665         }
3666         forwardMostMove = backwardMostMove = currentMove = moveNum;
3667         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3668             ics_getting_history = H_REQUESTED;
3669             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3670             SendToICS(str);
3671         }
3672     }
3673     
3674     /* Update the clocks */
3675     if (strchr(elapsed_time, '.')) {
3676       /* Time is in ms */
3677       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3678       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3679     } else {
3680       /* Time is in seconds */
3681       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3682       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3683     }
3684       
3685
3686 #if ZIPPY
3687     if (appData.zippyPlay && newGame &&
3688         gameMode != IcsObserving && gameMode != IcsIdle &&
3689         gameMode != IcsExamining)
3690       ZippyFirstBoard(moveNum, basetime, increment);
3691 #endif
3692     
3693     /* Put the move on the move list, first converting
3694        to canonical algebraic form. */
3695     if (moveNum > 0) {
3696   if (appData.debugMode) {
3697     if (appData.debugMode) { int f = forwardMostMove;
3698         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3699                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3700     }
3701     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3702     fprintf(debugFP, "moveNum = %d\n", moveNum);
3703     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3704     setbuf(debugFP, NULL);
3705   }
3706         if (moveNum <= backwardMostMove) {
3707             /* We don't know what the board looked like before
3708                this move.  Punt. */
3709             strcpy(parseList[moveNum - 1], move_str);
3710             strcat(parseList[moveNum - 1], " ");
3711             strcat(parseList[moveNum - 1], elapsed_time);
3712             moveList[moveNum - 1][0] = NULLCHAR;
3713         } else if (strcmp(move_str, "none") == 0) {
3714             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3715             /* Again, we don't know what the board looked like;
3716                this is really the start of the game. */
3717             parseList[moveNum - 1][0] = NULLCHAR;
3718             moveList[moveNum - 1][0] = NULLCHAR;
3719             backwardMostMove = moveNum;
3720             startedFromSetupPosition = TRUE;
3721             fromX = fromY = toX = toY = -1;
3722         } else {
3723           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3724           //                 So we parse the long-algebraic move string in stead of the SAN move
3725           int valid; char buf[MSG_SIZ], *prom;
3726
3727           // str looks something like "Q/a1-a2"; kill the slash
3728           if(str[1] == '/') 
3729                 sprintf(buf, "%c%s", str[0], str+2);
3730           else  strcpy(buf, str); // might be castling
3731           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3732                 strcat(buf, prom); // long move lacks promo specification!
3733           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3734                 if(appData.debugMode) 
3735                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3736                 strcpy(move_str, buf);
3737           }
3738           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3739                                 &fromX, &fromY, &toX, &toY, &promoChar)
3740                || ParseOneMove(buf, moveNum - 1, &moveType,
3741                                 &fromX, &fromY, &toX, &toY, &promoChar);
3742           // end of long SAN patch
3743           if (valid) {
3744             (void) CoordsToAlgebraic(boards[moveNum - 1],
3745                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3746                                      fromY, fromX, toY, toX, promoChar,
3747                                      parseList[moveNum-1]);
3748             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3749                              castlingRights[moveNum]) ) {
3750               case MT_NONE:
3751               case MT_STALEMATE:
3752               default:
3753                 break;
3754               case MT_CHECK:
3755                 if(gameInfo.variant != VariantShogi)
3756                     strcat(parseList[moveNum - 1], "+");
3757                 break;
3758               case MT_CHECKMATE:
3759               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3760                 strcat(parseList[moveNum - 1], "#");
3761                 break;
3762             }
3763             strcat(parseList[moveNum - 1], " ");
3764             strcat(parseList[moveNum - 1], elapsed_time);
3765             /* currentMoveString is set as a side-effect of ParseOneMove */
3766             strcpy(moveList[moveNum - 1], currentMoveString);
3767             strcat(moveList[moveNum - 1], "\n");
3768           } else {
3769             /* Move from ICS was illegal!?  Punt. */
3770   if (appData.debugMode) {
3771     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3772     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3773   }
3774             strcpy(parseList[moveNum - 1], move_str);
3775             strcat(parseList[moveNum - 1], " ");
3776             strcat(parseList[moveNum - 1], elapsed_time);
3777             moveList[moveNum - 1][0] = NULLCHAR;
3778             fromX = fromY = toX = toY = -1;
3779           }
3780         }
3781   if (appData.debugMode) {
3782     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3783     setbuf(debugFP, NULL);
3784   }
3785
3786 #if ZIPPY
3787         /* Send move to chess program (BEFORE animating it). */
3788         if (appData.zippyPlay && !newGame && newMove && 
3789            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3790
3791             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3792                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3793                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3794                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3795                             move_str);
3796                     DisplayError(str, 0);
3797                 } else {
3798                     if (first.sendTime) {
3799                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3800                     }
3801                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3802                     if (firstMove && !bookHit) {
3803                         firstMove = FALSE;
3804                         if (first.useColors) {
3805                           SendToProgram(gameMode == IcsPlayingWhite ?
3806                                         "white\ngo\n" :
3807                                         "black\ngo\n", &first);
3808                         } else {
3809                           SendToProgram("go\n", &first);
3810                         }
3811                         first.maybeThinking = TRUE;
3812                     }
3813                 }
3814             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3815               if (moveList[moveNum - 1][0] == NULLCHAR) {
3816                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3817                 DisplayError(str, 0);
3818               } else {
3819                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3820                 SendMoveToProgram(moveNum - 1, &first);
3821               }
3822             }
3823         }
3824 #endif
3825     }
3826
3827     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3828         /* If move comes from a remote source, animate it.  If it
3829            isn't remote, it will have already been animated. */
3830         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3831             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3832         }
3833         if (!pausing && appData.highlightLastMove) {
3834             SetHighlights(fromX, fromY, toX, toY);
3835         }
3836     }
3837     
3838     /* Start the clocks */
3839     whiteFlag = blackFlag = FALSE;
3840     appData.clockMode = !(basetime == 0 && increment == 0);
3841     if (ticking == 0) {
3842       ics_clock_paused = TRUE;
3843       StopClocks();
3844     } else if (ticking == 1) {
3845       ics_clock_paused = FALSE;
3846     }
3847     if (gameMode == IcsIdle ||
3848         relation == RELATION_OBSERVING_STATIC ||
3849         relation == RELATION_EXAMINING ||
3850         ics_clock_paused)
3851       DisplayBothClocks();
3852     else
3853       StartClocks();
3854     
3855     /* Display opponents and material strengths */
3856     if (gameInfo.variant != VariantBughouse &&
3857         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3858         if (tinyLayout || smallLayout) {
3859             if(gameInfo.variant == VariantNormal)
3860                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3861                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3862                     basetime, increment);
3863             else
3864                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3865                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3866                     basetime, increment, (int) gameInfo.variant);
3867         } else {
3868             if(gameInfo.variant == VariantNormal)
3869                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3870                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3871                     basetime, increment);
3872             else
3873                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3874                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3875                     basetime, increment, VariantName(gameInfo.variant));
3876         }
3877         DisplayTitle(str);
3878   if (appData.debugMode) {
3879     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3880   }
3881     }
3882
3883    
3884     /* Display the board */
3885     if (!pausing && !appData.noGUI) {
3886       
3887       if (appData.premove)
3888           if (!gotPremove || 
3889              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3890              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3891               ClearPremoveHighlights();
3892
3893       DrawPosition(FALSE, boards[currentMove]);
3894       DisplayMove(moveNum - 1);
3895       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3896             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3897               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3898         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3899       }
3900     }
3901
3902     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3903 #if ZIPPY
3904     if(bookHit) { // [HGM] book: simulate book reply
3905         static char bookMove[MSG_SIZ]; // a bit generous?
3906
3907         programStats.nodes = programStats.depth = programStats.time = 
3908         programStats.score = programStats.got_only_move = 0;
3909         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3910
3911         strcpy(bookMove, "move ");
3912         strcat(bookMove, bookHit);
3913         HandleMachineMove(bookMove, &first);
3914     }
3915 #endif
3916 }
3917
3918 void
3919 GetMoveListEvent()
3920 {
3921     char buf[MSG_SIZ];
3922     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3923         ics_getting_history = H_REQUESTED;
3924         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3925         SendToICS(buf);
3926     }
3927 }
3928
3929 void
3930 AnalysisPeriodicEvent(force)
3931      int force;
3932 {
3933     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3934          && !force) || !appData.periodicUpdates)
3935       return;
3936
3937     /* Send . command to Crafty to collect stats */
3938     SendToProgram(".\n", &first);
3939
3940     /* Don't send another until we get a response (this makes
3941        us stop sending to old Crafty's which don't understand
3942        the "." command (sending illegal cmds resets node count & time,
3943        which looks bad)) */
3944     programStats.ok_to_send = 0;
3945 }
3946
3947 void ics_update_width(new_width)
3948         int new_width;
3949 {
3950         ics_printf("set width %d\n", new_width);
3951 }
3952
3953 void
3954 SendMoveToProgram(moveNum, cps)
3955      int moveNum;
3956      ChessProgramState *cps;
3957 {
3958     char buf[MSG_SIZ];
3959
3960     if (cps->useUsermove) {
3961       SendToProgram("usermove ", cps);
3962     }
3963     if (cps->useSAN) {
3964       char *space;
3965       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3966         int len = space - parseList[moveNum];
3967         memcpy(buf, parseList[moveNum], len);
3968         buf[len++] = '\n';
3969         buf[len] = NULLCHAR;
3970       } else {
3971         sprintf(buf, "%s\n", parseList[moveNum]);
3972       }
3973       SendToProgram(buf, cps);
3974     } else {
3975       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3976         AlphaRank(moveList[moveNum], 4);
3977         SendToProgram(moveList[moveNum], cps);
3978         AlphaRank(moveList[moveNum], 4); // and back
3979       } else
3980       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3981        * the engine. It would be nice to have a better way to identify castle 
3982        * moves here. */
3983       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3984                                                                          && cps->useOOCastle) {
3985         int fromX = moveList[moveNum][0] - AAA; 
3986         int fromY = moveList[moveNum][1] - ONE;
3987         int toX = moveList[moveNum][2] - AAA; 
3988         int toY = moveList[moveNum][3] - ONE;
3989         if((boards[moveNum][fromY][fromX] == WhiteKing 
3990             && boards[moveNum][toY][toX] == WhiteRook)
3991            || (boards[moveNum][fromY][fromX] == BlackKing 
3992                && boards[moveNum][toY][toX] == BlackRook)) {
3993           if(toX > fromX) SendToProgram("O-O\n", cps);
3994           else SendToProgram("O-O-O\n", cps);
3995         }
3996         else SendToProgram(moveList[moveNum], cps);
3997       }
3998       else SendToProgram(moveList[moveNum], cps);
3999       /* End of additions by Tord */
4000     }
4001
4002     /* [HGM] setting up the opening has brought engine in force mode! */
4003     /*       Send 'go' if we are in a mode where machine should play. */
4004     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4005         (gameMode == TwoMachinesPlay   ||
4006 #ifdef ZIPPY
4007          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4008 #endif
4009          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4010         SendToProgram("go\n", cps);
4011   if (appData.debugMode) {
4012     fprintf(debugFP, "(extra)\n");
4013   }
4014     }
4015     setboardSpoiledMachineBlack = 0;
4016 }
4017
4018 void
4019 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4020      ChessMove moveType;
4021      int fromX, fromY, toX, toY;
4022 {
4023     char user_move[MSG_SIZ];
4024
4025     switch (moveType) {
4026       default:
4027         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4028                 (int)moveType, fromX, fromY, toX, toY);
4029         DisplayError(user_move + strlen("say "), 0);
4030         break;
4031       case WhiteKingSideCastle:
4032       case BlackKingSideCastle:
4033       case WhiteQueenSideCastleWild:
4034       case BlackQueenSideCastleWild:
4035       /* PUSH Fabien */
4036       case WhiteHSideCastleFR:
4037       case BlackHSideCastleFR:
4038       /* POP Fabien */
4039         sprintf(user_move, "o-o\n");
4040         break;
4041       case WhiteQueenSideCastle:
4042       case BlackQueenSideCastle:
4043       case WhiteKingSideCastleWild:
4044       case BlackKingSideCastleWild:
4045       /* PUSH Fabien */
4046       case WhiteASideCastleFR:
4047       case BlackASideCastleFR:
4048       /* POP Fabien */
4049         sprintf(user_move, "o-o-o\n");
4050         break;
4051       case WhitePromotionQueen:
4052       case BlackPromotionQueen:
4053       case WhitePromotionRook:
4054       case BlackPromotionRook:
4055       case WhitePromotionBishop:
4056       case BlackPromotionBishop:
4057       case WhitePromotionKnight:
4058       case BlackPromotionKnight:
4059       case WhitePromotionKing:
4060       case BlackPromotionKing:
4061       case WhitePromotionChancellor:
4062       case BlackPromotionChancellor:
4063       case WhitePromotionArchbishop:
4064       case BlackPromotionArchbishop:
4065         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4066             sprintf(user_move, "%c%c%c%c=%c\n",
4067                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4068                 PieceToChar(WhiteFerz));
4069         else if(gameInfo.variant == VariantGreat)
4070             sprintf(user_move, "%c%c%c%c=%c\n",
4071                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4072                 PieceToChar(WhiteMan));
4073         else
4074             sprintf(user_move, "%c%c%c%c=%c\n",
4075                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4076                 PieceToChar(PromoPiece(moveType)));
4077         break;
4078       case WhiteDrop:
4079       case BlackDrop:
4080         sprintf(user_move, "%c@%c%c\n",
4081                 ToUpper(PieceToChar((ChessSquare) fromX)),
4082                 AAA + toX, ONE + toY);
4083         break;
4084       case NormalMove:
4085       case WhiteCapturesEnPassant:
4086       case BlackCapturesEnPassant:
4087       case IllegalMove:  /* could be a variant we don't quite understand */
4088         sprintf(user_move, "%c%c%c%c\n",
4089                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4090         break;
4091     }
4092     SendToICS(user_move);
4093     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4094         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4095 }
4096
4097 void
4098 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4099      int rf, ff, rt, ft;
4100      char promoChar;
4101      char move[7];
4102 {
4103     if (rf == DROP_RANK) {
4104         sprintf(move, "%c@%c%c\n",
4105                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4106     } else {
4107         if (promoChar == 'x' || promoChar == NULLCHAR) {
4108             sprintf(move, "%c%c%c%c\n",
4109                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4110         } else {
4111             sprintf(move, "%c%c%c%c%c\n",
4112                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4113         }
4114     }
4115 }
4116
4117 void
4118 ProcessICSInitScript(f)
4119      FILE *f;
4120 {
4121     char buf[MSG_SIZ];
4122
4123     while (fgets(buf, MSG_SIZ, f)) {
4124         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4125     }
4126
4127     fclose(f);
4128 }
4129
4130
4131 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4132 void
4133 AlphaRank(char *move, int n)
4134 {
4135 //    char *p = move, c; int x, y;
4136
4137     if (appData.debugMode) {
4138         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4139     }
4140
4141     if(move[1]=='*' && 
4142        move[2]>='0' && move[2]<='9' &&
4143        move[3]>='a' && move[3]<='x'    ) {
4144         move[1] = '@';
4145         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4146         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4147     } else
4148     if(move[0]>='0' && move[0]<='9' &&
4149        move[1]>='a' && move[1]<='x' &&
4150        move[2]>='0' && move[2]<='9' &&
4151        move[3]>='a' && move[3]<='x'    ) {
4152         /* input move, Shogi -> normal */
4153         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4154         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4155         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4156         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4157     } else
4158     if(move[1]=='@' &&
4159        move[3]>='0' && move[3]<='9' &&
4160        move[2]>='a' && move[2]<='x'    ) {
4161         move[1] = '*';
4162         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4163         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4164     } else
4165     if(
4166        move[0]>='a' && move[0]<='x' &&
4167        move[3]>='0' && move[3]<='9' &&
4168        move[2]>='a' && move[2]<='x'    ) {
4169          /* output move, normal -> Shogi */
4170         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4171         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4172         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4173         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4174         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4175     }
4176     if (appData.debugMode) {
4177         fprintf(debugFP, "   out = '%s'\n", move);
4178     }
4179 }
4180
4181 /* Parser for moves from gnuchess, ICS, or user typein box */
4182 Boolean
4183 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4184      char *move;
4185      int moveNum;
4186      ChessMove *moveType;
4187      int *fromX, *fromY, *toX, *toY;
4188      char *promoChar;
4189 {       
4190     if (appData.debugMode) {
4191         fprintf(debugFP, "move to parse: %s\n", move);
4192     }
4193     *moveType = yylexstr(moveNum, move);
4194
4195     switch (*moveType) {
4196       case WhitePromotionChancellor:
4197       case BlackPromotionChancellor:
4198       case WhitePromotionArchbishop:
4199       case BlackPromotionArchbishop:
4200       case WhitePromotionQueen:
4201       case BlackPromotionQueen:
4202       case WhitePromotionRook:
4203       case BlackPromotionRook:
4204       case WhitePromotionBishop:
4205       case BlackPromotionBishop:
4206       case WhitePromotionKnight:
4207       case BlackPromotionKnight:
4208       case WhitePromotionKing:
4209       case BlackPromotionKing:
4210       case NormalMove:
4211       case WhiteCapturesEnPassant:
4212       case BlackCapturesEnPassant:
4213       case WhiteKingSideCastle:
4214       case WhiteQueenSideCastle:
4215       case BlackKingSideCastle:
4216       case BlackQueenSideCastle:
4217       case WhiteKingSideCastleWild:
4218       case WhiteQueenSideCastleWild:
4219       case BlackKingSideCastleWild:
4220       case BlackQueenSideCastleWild:
4221       /* Code added by Tord: */
4222       case WhiteHSideCastleFR:
4223       case WhiteASideCastleFR:
4224       case BlackHSideCastleFR:
4225       case BlackASideCastleFR:
4226       /* End of code added by Tord */
4227       case IllegalMove:         /* bug or odd chess variant */
4228         *fromX = currentMoveString[0] - AAA;
4229         *fromY = currentMoveString[1] - ONE;
4230         *toX = currentMoveString[2] - AAA;
4231         *toY = currentMoveString[3] - ONE;
4232         *promoChar = currentMoveString[4];
4233         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4234             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4235     if (appData.debugMode) {
4236         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4237     }
4238             *fromX = *fromY = *toX = *toY = 0;
4239             return FALSE;
4240         }
4241         if (appData.testLegality) {
4242           return (*moveType != IllegalMove);
4243         } else {
4244           return !(fromX == fromY && toX == toY);
4245         }
4246
4247       case WhiteDrop:
4248       case BlackDrop:
4249         *fromX = *moveType == WhiteDrop ?
4250           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4251           (int) CharToPiece(ToLower(currentMoveString[0]));
4252         *fromY = DROP_RANK;
4253         *toX = currentMoveString[2] - AAA;
4254         *toY = currentMoveString[3] - ONE;
4255         *promoChar = NULLCHAR;
4256         return TRUE;
4257
4258       case AmbiguousMove:
4259       case ImpossibleMove:
4260       case (ChessMove) 0:       /* end of file */
4261       case ElapsedTime:
4262       case Comment:
4263       case PGNTag:
4264       case NAG:
4265       case WhiteWins:
4266       case BlackWins:
4267       case GameIsDrawn:
4268       default:
4269     if (appData.debugMode) {
4270         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4271     }
4272         /* bug? */
4273         *fromX = *fromY = *toX = *toY = 0;
4274         *promoChar = NULLCHAR;
4275         return FALSE;
4276     }
4277 }
4278
4279 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4280 // All positions will have equal probability, but the current method will not provide a unique
4281 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4282 #define DARK 1
4283 #define LITE 2
4284 #define ANY 3
4285
4286 int squaresLeft[4];
4287 int piecesLeft[(int)BlackPawn];
4288 int seed, nrOfShuffles;
4289
4290 void GetPositionNumber()
4291 {       // sets global variable seed
4292         int i;
4293
4294         seed = appData.defaultFrcPosition;
4295         if(seed < 0) { // randomize based on time for negative FRC position numbers
4296                 for(i=0; i<50; i++) seed += random();
4297                 seed = random() ^ random() >> 8 ^ random() << 8;
4298                 if(seed<0) seed = -seed;
4299         }
4300 }
4301
4302 int put(Board board, int pieceType, int rank, int n, int shade)
4303 // put the piece on the (n-1)-th empty squares of the given shade
4304 {
4305         int i;
4306
4307         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4308                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4309                         board[rank][i] = (ChessSquare) pieceType;
4310                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4311                         squaresLeft[ANY]--;
4312                         piecesLeft[pieceType]--; 
4313                         return i;
4314                 }
4315         }
4316         return -1;
4317 }
4318
4319
4320 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4321 // calculate where the next piece goes, (any empty square), and put it there
4322 {
4323         int i;
4324
4325         i = seed % squaresLeft[shade];
4326         nrOfShuffles *= squaresLeft[shade];
4327         seed /= squaresLeft[shade];
4328         put(board, pieceType, rank, i, shade);
4329 }
4330
4331 void AddTwoPieces(Board board, int pieceType, int rank)
4332 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4333 {
4334         int i, n=squaresLeft[ANY], j=n-1, k;
4335
4336         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4337         i = seed % k;  // pick one
4338         nrOfShuffles *= k;
4339         seed /= k;
4340         while(i >= j) i -= j--;
4341         j = n - 1 - j; i += j;
4342         put(board, pieceType, rank, j, ANY);
4343         put(board, pieceType, rank, i, ANY);
4344 }
4345
4346 void SetUpShuffle(Board board, int number)
4347 {
4348         int i, p, first=1;
4349
4350         GetPositionNumber(); nrOfShuffles = 1;
4351
4352         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4353         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4354         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4355
4356         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4357
4358         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4359             p = (int) board[0][i];
4360             if(p < (int) BlackPawn) piecesLeft[p] ++;
4361             board[0][i] = EmptySquare;
4362         }
4363
4364         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4365             // shuffles restricted to allow normal castling put KRR first
4366             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4367                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4368             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4369                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4370             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4371                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4372             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4373                 put(board, WhiteRook, 0, 0, ANY);
4374             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4375         }
4376
4377         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4378             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4379             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4380                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4381                 while(piecesLeft[p] >= 2) {
4382                     AddOnePiece(board, p, 0, LITE);
4383                     AddOnePiece(board, p, 0, DARK);
4384                 }
4385                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4386             }
4387
4388         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4389             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4390             // but we leave King and Rooks for last, to possibly obey FRC restriction
4391             if(p == (int)WhiteRook) continue;
4392             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4393             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4394         }
4395
4396         // now everything is placed, except perhaps King (Unicorn) and Rooks
4397
4398         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4399             // Last King gets castling rights
4400             while(piecesLeft[(int)WhiteUnicorn]) {
4401                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4402                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4403             }
4404
4405             while(piecesLeft[(int)WhiteKing]) {
4406                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4407                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4408             }
4409
4410
4411         } else {
4412             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4413             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4414         }
4415
4416         // Only Rooks can be left; simply place them all
4417         while(piecesLeft[(int)WhiteRook]) {
4418                 i = put(board, WhiteRook, 0, 0, ANY);
4419                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4420                         if(first) {
4421                                 first=0;
4422                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4423                         }
4424                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4425                 }
4426         }
4427         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4428             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4429         }
4430
4431         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4432 }
4433
4434 int SetCharTable( char *table, const char * map )
4435 /* [HGM] moved here from winboard.c because of its general usefulness */
4436 /*       Basically a safe strcpy that uses the last character as King */
4437 {
4438     int result = FALSE; int NrPieces;
4439
4440     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4441                     && NrPieces >= 12 && !(NrPieces&1)) {
4442         int i; /* [HGM] Accept even length from 12 to 34 */
4443
4444         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4445         for( i=0; i<NrPieces/2-1; i++ ) {
4446             table[i] = map[i];
4447             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4448         }
4449         table[(int) WhiteKing]  = map[NrPieces/2-1];
4450         table[(int) BlackKing]  = map[NrPieces-1];
4451
4452         result = TRUE;
4453     }
4454
4455     return result;
4456 }
4457
4458 void Prelude(Board board)
4459 {       // [HGM] superchess: random selection of exo-pieces
4460         int i, j, k; ChessSquare p; 
4461         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4462
4463         GetPositionNumber(); // use FRC position number
4464
4465         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4466             SetCharTable(pieceToChar, appData.pieceToCharTable);
4467             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4468                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4469         }
4470
4471         j = seed%4;                 seed /= 4; 
4472         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4473         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4474         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4475         j = seed%3 + (seed%3 >= j); seed /= 3; 
4476         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4477         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4478         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4479         j = seed%3;                 seed /= 3; 
4480         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4481         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4482         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4483         j = seed%2 + (seed%2 >= j); seed /= 2; 
4484         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4485         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4486         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4487         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4488         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4489         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4490         put(board, exoPieces[0],    0, 0, ANY);
4491         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4492 }
4493
4494 void
4495 InitPosition(redraw)
4496      int redraw;
4497 {
4498     ChessSquare (* pieces)[BOARD_SIZE];
4499     int i, j, pawnRow, overrule,
4500     oldx = gameInfo.boardWidth,
4501     oldy = gameInfo.boardHeight,
4502     oldh = gameInfo.holdingsWidth,
4503     oldv = gameInfo.variant;
4504
4505     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4506
4507     /* [AS] Initialize pv info list [HGM] and game status */
4508     {
4509         for( i=0; i<MAX_MOVES; i++ ) {
4510             pvInfoList[i].depth = 0;
4511             epStatus[i]=EP_NONE;
4512             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4513         }
4514
4515         initialRulePlies = 0; /* 50-move counter start */
4516
4517         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4518         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4519     }
4520
4521     
4522     /* [HGM] logic here is completely changed. In stead of full positions */
4523     /* the initialized data only consist of the two backranks. The switch */
4524     /* selects which one we will use, which is than copied to the Board   */
4525     /* initialPosition, which for the rest is initialized by Pawns and    */
4526     /* empty squares. This initial position is then copied to boards[0],  */
4527     /* possibly after shuffling, so that it remains available.            */
4528
4529     gameInfo.holdingsWidth = 0; /* default board sizes */
4530     gameInfo.boardWidth    = 8;
4531     gameInfo.boardHeight   = 8;
4532     gameInfo.holdingsSize  = 0;
4533     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4534     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4535     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4536
4537     switch (gameInfo.variant) {
4538     case VariantFischeRandom:
4539       shuffleOpenings = TRUE;
4540     default:
4541       pieces = FIDEArray;
4542       break;
4543     case VariantShatranj:
4544       pieces = ShatranjArray;
4545       nrCastlingRights = 0;
4546       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4547       break;
4548     case VariantTwoKings:
4549       pieces = twoKingsArray;
4550       break;
4551     case VariantCapaRandom:
4552       shuffleOpenings = TRUE;
4553     case VariantCapablanca:
4554       pieces = CapablancaArray;
4555       gameInfo.boardWidth = 10;
4556       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4557       break;
4558     case VariantGothic:
4559       pieces = GothicArray;
4560       gameInfo.boardWidth = 10;
4561       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4562       break;
4563     case VariantJanus:
4564       pieces = JanusArray;
4565       gameInfo.boardWidth = 10;
4566       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4567       nrCastlingRights = 6;
4568         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4569         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4570         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4571         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4572         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4573         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4574       break;
4575     case VariantFalcon:
4576       pieces = FalconArray;
4577       gameInfo.boardWidth = 10;
4578       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4579       break;
4580     case VariantXiangqi:
4581       pieces = XiangqiArray;
4582       gameInfo.boardWidth  = 9;
4583       gameInfo.boardHeight = 10;
4584       nrCastlingRights = 0;
4585       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4586       break;
4587     case VariantShogi:
4588       pieces = ShogiArray;
4589       gameInfo.boardWidth  = 9;
4590       gameInfo.boardHeight = 9;
4591       gameInfo.holdingsSize = 7;
4592       nrCastlingRights = 0;
4593       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4594       break;
4595     case VariantCourier:
4596       pieces = CourierArray;
4597       gameInfo.boardWidth  = 12;
4598       nrCastlingRights = 0;
4599       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4600       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4601       break;
4602     case VariantKnightmate:
4603       pieces = KnightmateArray;
4604       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4605       break;
4606     case VariantFairy:
4607       pieces = fairyArray;
4608       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4609       break;
4610     case VariantGreat:
4611       pieces = GreatArray;
4612       gameInfo.boardWidth = 10;
4613       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4614       gameInfo.holdingsSize = 8;
4615       break;
4616     case VariantSuper:
4617       pieces = FIDEArray;
4618       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4619       gameInfo.holdingsSize = 8;
4620       startedFromSetupPosition = TRUE;
4621       break;
4622     case VariantCrazyhouse:
4623     case VariantBughouse:
4624       pieces = FIDEArray;
4625       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4626       gameInfo.holdingsSize = 5;
4627       break;
4628     case VariantWildCastle:
4629       pieces = FIDEArray;
4630       /* !!?shuffle with kings guaranteed to be on d or e file */
4631       shuffleOpenings = 1;
4632       break;
4633     case VariantNoCastle:
4634       pieces = FIDEArray;
4635       nrCastlingRights = 0;
4636       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4637       /* !!?unconstrained back-rank shuffle */
4638       shuffleOpenings = 1;
4639       break;
4640     }
4641
4642     overrule = 0;
4643     if(appData.NrFiles >= 0) {
4644         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4645         gameInfo.boardWidth = appData.NrFiles;
4646     }
4647     if(appData.NrRanks >= 0) {
4648         gameInfo.boardHeight = appData.NrRanks;
4649     }
4650     if(appData.holdingsSize >= 0) {
4651         i = appData.holdingsSize;
4652         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4653         gameInfo.holdingsSize = i;
4654     }
4655     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4656     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4657         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4658
4659     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4660     if(pawnRow < 1) pawnRow = 1;
4661
4662     /* User pieceToChar list overrules defaults */
4663     if(appData.pieceToCharTable != NULL)
4664         SetCharTable(pieceToChar, appData.pieceToCharTable);
4665
4666     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4667
4668         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4669             s = (ChessSquare) 0; /* account holding counts in guard band */
4670         for( i=0; i<BOARD_HEIGHT; i++ )
4671             initialPosition[i][j] = s;
4672
4673         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4674         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4675         initialPosition[pawnRow][j] = WhitePawn;
4676         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4677         if(gameInfo.variant == VariantXiangqi) {
4678             if(j&1) {
4679                 initialPosition[pawnRow][j] = 
4680                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4681                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4682                    initialPosition[2][j] = WhiteCannon;
4683                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4684                 }
4685             }
4686         }
4687         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4688     }
4689     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4690
4691             j=BOARD_LEFT+1;
4692             initialPosition[1][j] = WhiteBishop;
4693             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4694             j=BOARD_RGHT-2;
4695             initialPosition[1][j] = WhiteRook;
4696             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4697     }
4698
4699     if( nrCastlingRights == -1) {
4700         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4701         /*       This sets default castling rights from none to normal corners   */
4702         /* Variants with other castling rights must set them themselves above    */
4703         nrCastlingRights = 6;
4704        
4705         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4706         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4707         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4708         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4709         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4710         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4711      }
4712
4713      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4714      if(gameInfo.variant == VariantGreat) { // promotion commoners
4715         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4716         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4717         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4718         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4719      }
4720   if (appData.debugMode) {
4721     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4722   }
4723     if(shuffleOpenings) {
4724         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4725         startedFromSetupPosition = TRUE;
4726     }
4727     if(startedFromPositionFile) {
4728       /* [HGM] loadPos: use PositionFile for every new game */
4729       CopyBoard(initialPosition, filePosition);
4730       for(i=0; i<nrCastlingRights; i++)
4731           castlingRights[0][i] = initialRights[i] = fileRights[i];
4732       startedFromSetupPosition = TRUE;
4733     }
4734
4735     CopyBoard(boards[0], initialPosition);
4736
4737     if(oldx != gameInfo.boardWidth ||
4738        oldy != gameInfo.boardHeight ||
4739        oldh != gameInfo.holdingsWidth
4740 #ifdef GOTHIC
4741        || oldv == VariantGothic ||        // For licensing popups
4742        gameInfo.variant == VariantGothic
4743 #endif
4744 #ifdef FALCON
4745        || oldv == VariantFalcon ||
4746        gameInfo.variant == VariantFalcon
4747 #endif
4748                                          )
4749             InitDrawingSizes(-2 ,0);
4750
4751     if (redraw)
4752       DrawPosition(TRUE, boards[currentMove]);
4753 }
4754
4755 void
4756 SendBoard(cps, moveNum)
4757      ChessProgramState *cps;
4758      int moveNum;
4759 {
4760     char message[MSG_SIZ];
4761     
4762     if (cps->useSetboard) {
4763       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4764       sprintf(message, "setboard %s\n", fen);
4765       SendToProgram(message, cps);
4766       free(fen);
4767
4768     } else {
4769       ChessSquare *bp;
4770       int i, j;
4771       /* Kludge to set black to move, avoiding the troublesome and now
4772        * deprecated "black" command.
4773        */
4774       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4775
4776       SendToProgram("edit\n", cps);
4777       SendToProgram("#\n", cps);
4778       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4779         bp = &boards[moveNum][i][BOARD_LEFT];
4780         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4781           if ((int) *bp < (int) BlackPawn) {
4782             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4783                     AAA + j, ONE + i);
4784             if(message[0] == '+' || message[0] == '~') {
4785                 sprintf(message, "%c%c%c+\n",
4786                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4787                         AAA + j, ONE + i);
4788             }
4789             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4790                 message[1] = BOARD_RGHT   - 1 - j + '1';
4791                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4792             }
4793             SendToProgram(message, cps);
4794           }
4795         }
4796       }
4797     
4798       SendToProgram("c\n", cps);
4799       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4800         bp = &boards[moveNum][i][BOARD_LEFT];
4801         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4802           if (((int) *bp != (int) EmptySquare)
4803               && ((int) *bp >= (int) BlackPawn)) {
4804             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4805                     AAA + j, ONE + i);
4806             if(message[0] == '+' || message[0] == '~') {
4807                 sprintf(message, "%c%c%c+\n",
4808                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4809                         AAA + j, ONE + i);
4810             }
4811             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4812                 message[1] = BOARD_RGHT   - 1 - j + '1';
4813                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4814             }
4815             SendToProgram(message, cps);
4816           }
4817         }
4818       }
4819     
4820       SendToProgram(".\n", cps);
4821     }
4822     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4823 }
4824
4825 int
4826 IsPromotion(fromX, fromY, toX, toY)
4827      int fromX, fromY, toX, toY;
4828 {
4829     /* [HGM] add Shogi promotions */
4830     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4831     ChessSquare piece;
4832
4833     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4834       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4835    /* [HGM] Note to self: line above also weeds out drops */
4836     piece = boards[currentMove][fromY][fromX];
4837     if(gameInfo.variant == VariantShogi) {
4838         promotionZoneSize = 3;
4839         highestPromotingPiece = (int)WhiteKing;
4840         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4841            and if in normal chess we then allow promotion to King, why not
4842            allow promotion of other piece in Shogi?                         */
4843     }
4844     if((int)piece >= BlackPawn) {
4845         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4846              return FALSE;
4847         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4848     } else {
4849         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4850            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4851     }
4852     return ( (int)piece <= highestPromotingPiece );
4853 }
4854
4855 int
4856 InPalace(row, column)
4857      int row, column;
4858 {   /* [HGM] for Xiangqi */
4859     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4860          column < (BOARD_WIDTH + 4)/2 &&
4861          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4862     return FALSE;
4863 }
4864
4865 int
4866 PieceForSquare (x, y)
4867      int x;
4868      int y;
4869 {
4870   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4871      return -1;
4872   else
4873      return boards[currentMove][y][x];
4874 }
4875
4876 int
4877 OKToStartUserMove(x, y)
4878      int x, y;
4879 {
4880     ChessSquare from_piece;
4881     int white_piece;
4882
4883     if (matchMode) return FALSE;
4884     if (gameMode == EditPosition) return TRUE;
4885
4886     if (x >= 0 && y >= 0)
4887       from_piece = boards[currentMove][y][x];
4888     else
4889       from_piece = EmptySquare;
4890
4891     if (from_piece == EmptySquare) return FALSE;
4892
4893     white_piece = (int)from_piece >= (int)WhitePawn &&
4894       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4895
4896     switch (gameMode) {
4897       case PlayFromGameFile:
4898       case AnalyzeFile:
4899       case TwoMachinesPlay:
4900       case EndOfGame:
4901         return FALSE;
4902
4903       case IcsObserving:
4904       case IcsIdle:
4905         return FALSE;
4906
4907       case MachinePlaysWhite:
4908       case IcsPlayingBlack:
4909         if (appData.zippyPlay) return FALSE;
4910         if (white_piece) {
4911             DisplayMoveError(_("You are playing Black"));
4912             return FALSE;
4913         }
4914         break;
4915
4916       case MachinePlaysBlack:
4917       case IcsPlayingWhite:
4918         if (appData.zippyPlay) return FALSE;
4919         if (!white_piece) {
4920             DisplayMoveError(_("You are playing White"));
4921             return FALSE;
4922         }
4923         break;
4924
4925       case EditGame:
4926         if (!white_piece && WhiteOnMove(currentMove)) {
4927             DisplayMoveError(_("It is White's turn"));
4928             return FALSE;
4929         }           
4930         if (white_piece && !WhiteOnMove(currentMove)) {
4931             DisplayMoveError(_("It is Black's turn"));
4932             return FALSE;
4933         }           
4934         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4935             /* Editing correspondence game history */
4936             /* Could disallow this or prompt for confirmation */
4937             cmailOldMove = -1;
4938         }
4939         if (currentMove < forwardMostMove) {
4940             /* Discarding moves */
4941             /* Could prompt for confirmation here,
4942                but I don't think that's such a good idea */
4943             forwardMostMove = currentMove;
4944         }
4945         break;
4946
4947       case BeginningOfGame:
4948         if (appData.icsActive) return FALSE;
4949         if (!appData.noChessProgram) {
4950             if (!white_piece) {
4951                 DisplayMoveError(_("You are playing White"));
4952                 return FALSE;
4953             }
4954         }
4955         break;
4956         
4957       case Training:
4958         if (!white_piece && WhiteOnMove(currentMove)) {
4959             DisplayMoveError(_("It is White's turn"));
4960             return FALSE;
4961         }           
4962         if (white_piece && !WhiteOnMove(currentMove)) {
4963             DisplayMoveError(_("It is Black's turn"));
4964             return FALSE;
4965         }           
4966         break;
4967
4968       default:
4969       case IcsExamining:
4970         break;
4971     }
4972     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4973         && gameMode != AnalyzeFile && gameMode != Training) {
4974         DisplayMoveError(_("Displayed position is not current"));
4975         return FALSE;
4976     }
4977     return TRUE;
4978 }
4979
4980 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4981 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4982 int lastLoadGameUseList = FALSE;
4983 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4984 ChessMove lastLoadGameStart = (ChessMove) 0;
4985
4986 ChessMove
4987 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
4988      int fromX, fromY, toX, toY;
4989      int promoChar;
4990      Boolean captureOwn;
4991 {
4992     ChessMove moveType;
4993     ChessSquare pdown, pup;
4994
4995     if (fromX < 0 || fromY < 0) return ImpossibleMove;
4996
4997     /* [HGM] suppress all moves into holdings area and guard band */
4998     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
4999             return ImpossibleMove;
5000
5001     /* [HGM] <sameColor> moved to here from winboard.c */
5002     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5003     pdown = boards[currentMove][fromY][fromX];
5004     pup = boards[currentMove][toY][toX];
5005     if (    gameMode != EditPosition && !captureOwn &&
5006             (WhitePawn <= pdown && pdown < BlackPawn &&
5007              WhitePawn <= pup && pup < BlackPawn  ||
5008              BlackPawn <= pdown && pdown < EmptySquare &&
5009              BlackPawn <= pup && pup < EmptySquare 
5010             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5011                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5012                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5013                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5014                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5015         )           )
5016          return Comment;
5017
5018     /* Check if the user is playing in turn.  This is complicated because we
5019        let the user "pick up" a piece before it is his turn.  So the piece he
5020        tried to pick up may have been captured by the time he puts it down!
5021        Therefore we use the color the user is supposed to be playing in this
5022        test, not the color of the piece that is currently on the starting
5023        square---except in EditGame mode, where the user is playing both
5024        sides; fortunately there the capture race can't happen.  (It can
5025        now happen in IcsExamining mode, but that's just too bad.  The user
5026        will get a somewhat confusing message in that case.)
5027        */
5028
5029     switch (gameMode) {
5030       case PlayFromGameFile:
5031       case AnalyzeFile:
5032       case TwoMachinesPlay:
5033       case EndOfGame:
5034       case IcsObserving:
5035       case IcsIdle:
5036         /* We switched into a game mode where moves are not accepted,
5037            perhaps while the mouse button was down. */
5038         return ImpossibleMove;
5039
5040       case MachinePlaysWhite:
5041         /* User is moving for Black */
5042         if (WhiteOnMove(currentMove)) {
5043             DisplayMoveError(_("It is White's turn"));
5044             return ImpossibleMove;
5045         }
5046         break;
5047
5048       case MachinePlaysBlack:
5049         /* User is moving for White */
5050         if (!WhiteOnMove(currentMove)) {
5051             DisplayMoveError(_("It is Black's turn"));
5052             return ImpossibleMove;
5053         }
5054         break;
5055
5056       case EditGame:
5057       case IcsExamining:
5058       case BeginningOfGame:
5059       case AnalyzeMode:
5060       case Training:
5061         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5062             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5063             /* User is moving for Black */
5064             if (WhiteOnMove(currentMove)) {
5065                 DisplayMoveError(_("It is White's turn"));
5066                 return ImpossibleMove;
5067             }
5068         } else {
5069             /* User is moving for White */
5070             if (!WhiteOnMove(currentMove)) {
5071                 DisplayMoveError(_("It is Black's turn"));
5072                 return ImpossibleMove;
5073             }
5074         }
5075         break;
5076
5077       case IcsPlayingBlack:
5078         /* User is moving for Black */
5079         if (WhiteOnMove(currentMove)) {
5080             if (!appData.premove) {
5081                 DisplayMoveError(_("It is White's turn"));
5082             } else if (toX >= 0 && toY >= 0) {
5083                 premoveToX = toX;
5084                 premoveToY = toY;
5085                 premoveFromX = fromX;
5086                 premoveFromY = fromY;
5087                 premovePromoChar = promoChar;
5088                 gotPremove = 1;
5089                 if (appData.debugMode) 
5090                     fprintf(debugFP, "Got premove: fromX %d,"
5091                             "fromY %d, toX %d, toY %d\n",
5092                             fromX, fromY, toX, toY);
5093             }
5094             return ImpossibleMove;
5095         }
5096         break;
5097
5098       case IcsPlayingWhite:
5099         /* User is moving for White */
5100         if (!WhiteOnMove(currentMove)) {
5101             if (!appData.premove) {
5102                 DisplayMoveError(_("It is Black's turn"));
5103             } else if (toX >= 0 && toY >= 0) {
5104                 premoveToX = toX;
5105                 premoveToY = toY;
5106                 premoveFromX = fromX;
5107                 premoveFromY = fromY;
5108                 premovePromoChar = promoChar;
5109                 gotPremove = 1;
5110                 if (appData.debugMode) 
5111                     fprintf(debugFP, "Got premove: fromX %d,"
5112                             "fromY %d, toX %d, toY %d\n",
5113                             fromX, fromY, toX, toY);
5114             }
5115             return ImpossibleMove;
5116         }
5117         break;
5118
5119       default:
5120         break;
5121
5122       case EditPosition:
5123         /* EditPosition, empty square, or different color piece;
5124            click-click move is possible */
5125         if (toX == -2 || toY == -2) {
5126             boards[0][fromY][fromX] = EmptySquare;
5127             return AmbiguousMove;
5128         } else if (toX >= 0 && toY >= 0) {
5129             boards[0][toY][toX] = boards[0][fromY][fromX];
5130             boards[0][fromY][fromX] = EmptySquare;
5131             return AmbiguousMove;
5132         }
5133         return ImpossibleMove;
5134     }
5135
5136     /* [HGM] If move started in holdings, it means a drop */
5137     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5138          if( pup != EmptySquare ) return ImpossibleMove;
5139          if(appData.testLegality) {
5140              /* it would be more logical if LegalityTest() also figured out
5141               * which drops are legal. For now we forbid pawns on back rank.
5142               * Shogi is on its own here...
5143               */
5144              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5145                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5146                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5147          }
5148          return WhiteDrop; /* Not needed to specify white or black yet */
5149     }
5150
5151     userOfferedDraw = FALSE;
5152         
5153     /* [HGM] always test for legality, to get promotion info */
5154     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5155                           epStatus[currentMove], castlingRights[currentMove],
5156                                          fromY, fromX, toY, toX, promoChar);
5157     /* [HGM] but possibly ignore an IllegalMove result */
5158     if (appData.testLegality) {
5159         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5160             DisplayMoveError(_("Illegal move"));
5161             return ImpossibleMove;
5162         }
5163     }
5164 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5165     return moveType;
5166     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5167        function is made into one that returns an OK move type if FinishMove
5168        should be called. This to give the calling driver routine the
5169        opportunity to finish the userMove input with a promotion popup,
5170        without bothering the user with this for invalid or illegal moves */
5171
5172 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5173 }
5174
5175 /* Common tail of UserMoveEvent and DropMenuEvent */
5176 int
5177 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5178      ChessMove moveType;
5179      int fromX, fromY, toX, toY;
5180      /*char*/int promoChar;
5181 {
5182     char *bookHit = 0;
5183 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5184     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5185         // [HGM] superchess: suppress promotions to non-available piece
5186         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5187         if(WhiteOnMove(currentMove)) {
5188             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5189         } else {
5190             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5191         }
5192     }
5193
5194     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5195        move type in caller when we know the move is a legal promotion */
5196     if(moveType == NormalMove && promoChar)
5197         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5198 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5199     /* [HGM] convert drag-and-drop piece drops to standard form */
5200     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5201          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5202            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5203                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5204 //         fromX = boards[currentMove][fromY][fromX];
5205            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5206            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5207            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5208            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5209          fromY = DROP_RANK;
5210     }
5211
5212     /* [HGM] <popupFix> The following if has been moved here from
5213        UserMoveEvent(). Because it seemed to belon here (why not allow
5214        piece drops in training games?), and because it can only be
5215        performed after it is known to what we promote. */
5216     if (gameMode == Training) {
5217       /* compare the move played on the board to the next move in the
5218        * game. If they match, display the move and the opponent's response. 
5219        * If they don't match, display an error message.
5220        */
5221       int saveAnimate;
5222       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5223       CopyBoard(testBoard, boards[currentMove]);
5224       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5225
5226       if (CompareBoards(testBoard, boards[currentMove+1])) {
5227         ForwardInner(currentMove+1);
5228
5229         /* Autoplay the opponent's response.
5230          * if appData.animate was TRUE when Training mode was entered,
5231          * the response will be animated.
5232          */
5233         saveAnimate = appData.animate;
5234         appData.animate = animateTraining;
5235         ForwardInner(currentMove+1);
5236         appData.animate = saveAnimate;
5237
5238         /* check for the end of the game */
5239         if (currentMove >= forwardMostMove) {
5240           gameMode = PlayFromGameFile;
5241           ModeHighlight();
5242           SetTrainingModeOff();
5243           DisplayInformation(_("End of game"));
5244         }
5245       } else {
5246         DisplayError(_("Incorrect move"), 0);
5247       }
5248       return 1;
5249     }
5250
5251   /* Ok, now we know that the move is good, so we can kill
5252      the previous line in Analysis Mode */
5253   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5254     forwardMostMove = currentMove;
5255   }
5256
5257   /* If we need the chess program but it's dead, restart it */
5258   ResurrectChessProgram();
5259
5260   /* A user move restarts a paused game*/
5261   if (pausing)
5262     PauseEvent();
5263
5264   thinkOutput[0] = NULLCHAR;
5265
5266   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5267
5268   if (gameMode == BeginningOfGame) {
5269     if (appData.noChessProgram) {
5270       gameMode = EditGame;
5271       SetGameInfo();
5272     } else {
5273       char buf[MSG_SIZ];
5274       gameMode = MachinePlaysBlack;
5275       StartClocks();
5276       SetGameInfo();
5277       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5278       DisplayTitle(buf);
5279       if (first.sendName) {
5280         sprintf(buf, "name %s\n", gameInfo.white);
5281         SendToProgram(buf, &first);
5282       }
5283       StartClocks();
5284     }
5285     ModeHighlight();
5286   }
5287 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5288   /* Relay move to ICS or chess engine */
5289   if (appData.icsActive) {
5290     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5291         gameMode == IcsExamining) {
5292       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5293       ics_user_moved = 1;
5294     }
5295   } else {
5296     if (first.sendTime && (gameMode == BeginningOfGame ||
5297                            gameMode == MachinePlaysWhite ||
5298                            gameMode == MachinePlaysBlack)) {
5299       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5300     }
5301     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5302          // [HGM] book: if program might be playing, let it use book
5303         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5304         first.maybeThinking = TRUE;
5305     } else SendMoveToProgram(forwardMostMove-1, &first);
5306     if (currentMove == cmailOldMove + 1) {
5307       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5308     }
5309   }
5310
5311   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5312
5313   switch (gameMode) {
5314   case EditGame:
5315     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5316                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5317     case MT_NONE:
5318     case MT_CHECK:
5319       break;
5320     case MT_CHECKMATE:
5321     case MT_STAINMATE:
5322       if (WhiteOnMove(currentMove)) {
5323         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5324       } else {
5325         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5326       }
5327       break;
5328     case MT_STALEMATE:
5329       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5330       break;
5331     }
5332     break;
5333     
5334   case MachinePlaysBlack:
5335   case MachinePlaysWhite:
5336     /* disable certain menu options while machine is thinking */
5337     SetMachineThinkingEnables();
5338     break;
5339
5340   default:
5341     break;
5342   }
5343
5344   if(bookHit) { // [HGM] book: simulate book reply
5345         static char bookMove[MSG_SIZ]; // a bit generous?
5346
5347         programStats.nodes = programStats.depth = programStats.time = 
5348         programStats.score = programStats.got_only_move = 0;
5349         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5350
5351         strcpy(bookMove, "move ");
5352         strcat(bookMove, bookHit);
5353         HandleMachineMove(bookMove, &first);
5354   }
5355   return 1;
5356 }
5357
5358 void
5359 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5360      int fromX, fromY, toX, toY;
5361      int promoChar;
5362 {
5363     /* [HGM] This routine was added to allow calling of its two logical
5364        parts from other modules in the old way. Before, UserMoveEvent()
5365        automatically called FinishMove() if the move was OK, and returned
5366        otherwise. I separated the two, in order to make it possible to
5367        slip a promotion popup in between. But that it always needs two
5368        calls, to the first part, (now called UserMoveTest() ), and to
5369        FinishMove if the first part succeeded. Calls that do not need
5370        to do anything in between, can call this routine the old way. 
5371     */
5372     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5373 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5374     if(moveType == AmbiguousMove)
5375         DrawPosition(FALSE, boards[currentMove]);
5376     else if(moveType != ImpossibleMove && moveType != Comment)
5377         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5378 }
5379
5380 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5381 {
5382 //    char * hint = lastHint;
5383     FrontEndProgramStats stats;
5384
5385     stats.which = cps == &first ? 0 : 1;
5386     stats.depth = cpstats->depth;
5387     stats.nodes = cpstats->nodes;
5388     stats.score = cpstats->score;
5389     stats.time = cpstats->time;
5390     stats.pv = cpstats->movelist;
5391     stats.hint = lastHint;
5392     stats.an_move_index = 0;
5393     stats.an_move_count = 0;
5394
5395     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5396         stats.hint = cpstats->move_name;
5397         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5398         stats.an_move_count = cpstats->nr_moves;
5399     }
5400
5401     SetProgramStats( &stats );
5402 }
5403
5404 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5405 {   // [HGM] book: this routine intercepts moves to simulate book replies
5406     char *bookHit = NULL;
5407
5408     //first determine if the incoming move brings opponent into his book
5409     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5410         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5411     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5412     if(bookHit != NULL && !cps->bookSuspend) {
5413         // make sure opponent is not going to reply after receiving move to book position
5414         SendToProgram("force\n", cps);
5415         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5416     }
5417     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5418     // now arrange restart after book miss
5419     if(bookHit) {
5420         // after a book hit we never send 'go', and the code after the call to this routine
5421         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5422         char buf[MSG_SIZ];
5423         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5424         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5425         SendToProgram(buf, cps);
5426         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5427     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5428         SendToProgram("go\n", cps);
5429         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5430     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5431         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5432             SendToProgram("go\n", cps); 
5433         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5434     }
5435     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5436 }
5437
5438 char *savedMessage;
5439 ChessProgramState *savedState;
5440 void DeferredBookMove(void)
5441 {
5442         if(savedState->lastPing != savedState->lastPong)
5443                     ScheduleDelayedEvent(DeferredBookMove, 10);
5444         else
5445         HandleMachineMove(savedMessage, savedState);
5446 }
5447
5448 void
5449 HandleMachineMove(message, cps)
5450      char *message;
5451      ChessProgramState *cps;
5452 {
5453     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5454     char realname[MSG_SIZ];
5455     int fromX, fromY, toX, toY;
5456     ChessMove moveType;
5457     char promoChar;
5458     char *p;
5459     int machineWhite;
5460     char *bookHit;
5461
5462 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5463     /*
5464      * Kludge to ignore BEL characters
5465      */
5466     while (*message == '\007') message++;
5467
5468     /*
5469      * [HGM] engine debug message: ignore lines starting with '#' character
5470      */
5471     if(cps->debug && *message == '#') return;
5472
5473     /*
5474      * Look for book output
5475      */
5476     if (cps == &first && bookRequested) {
5477         if (message[0] == '\t' || message[0] == ' ') {
5478             /* Part of the book output is here; append it */
5479             strcat(bookOutput, message);
5480             strcat(bookOutput, "  \n");
5481             return;
5482         } else if (bookOutput[0] != NULLCHAR) {
5483             /* All of book output has arrived; display it */
5484             char *p = bookOutput;
5485             while (*p != NULLCHAR) {
5486                 if (*p == '\t') *p = ' ';
5487                 p++;
5488             }
5489             DisplayInformation(bookOutput);
5490             bookRequested = FALSE;
5491             /* Fall through to parse the current output */
5492         }
5493     }
5494
5495     /*
5496      * Look for machine move.
5497      */
5498     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5499         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5500     {
5501         /* This method is only useful on engines that support ping */
5502         if (cps->lastPing != cps->lastPong) {
5503           if (gameMode == BeginningOfGame) {
5504             /* Extra move from before last new; ignore */
5505             if (appData.debugMode) {
5506                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5507             }
5508           } else {
5509             if (appData.debugMode) {
5510                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5511                         cps->which, gameMode);
5512             }
5513
5514             SendToProgram("undo\n", cps);
5515           }
5516           return;
5517         }
5518
5519         switch (gameMode) {
5520           case BeginningOfGame:
5521             /* Extra move from before last reset; ignore */
5522             if (appData.debugMode) {
5523                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5524             }
5525             return;
5526
5527           case EndOfGame:
5528           case IcsIdle:
5529           default:
5530             /* Extra move after we tried to stop.  The mode test is
5531                not a reliable way of detecting this problem, but it's
5532                the best we can do on engines that don't support ping.
5533             */
5534             if (appData.debugMode) {
5535                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5536                         cps->which, gameMode);
5537             }
5538             SendToProgram("undo\n", cps);
5539             return;
5540
5541           case MachinePlaysWhite:
5542           case IcsPlayingWhite:
5543             machineWhite = TRUE;
5544             break;
5545
5546           case MachinePlaysBlack:
5547           case IcsPlayingBlack:
5548             machineWhite = FALSE;
5549             break;
5550
5551           case TwoMachinesPlay:
5552             machineWhite = (cps->twoMachinesColor[0] == 'w');
5553             break;
5554         }
5555         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5556             if (appData.debugMode) {
5557                 fprintf(debugFP,
5558                         "Ignoring move out of turn by %s, gameMode %d"
5559                         ", forwardMost %d\n",
5560                         cps->which, gameMode, forwardMostMove);
5561             }
5562             return;
5563         }
5564
5565     if (appData.debugMode) { int f = forwardMostMove;
5566         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5567                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5568     }
5569         if(cps->alphaRank) AlphaRank(machineMove, 4);
5570         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5571                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5572             /* Machine move could not be parsed; ignore it. */
5573             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5574                     machineMove, cps->which);
5575             DisplayError(buf1, 0);
5576             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5577                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5578             if (gameMode == TwoMachinesPlay) {
5579               GameEnds(machineWhite ? BlackWins : WhiteWins,
5580                        buf1, GE_XBOARD);
5581             }
5582             return;
5583         }
5584
5585         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5586         /* So we have to redo legality test with true e.p. status here,  */
5587         /* to make sure an illegal e.p. capture does not slip through,   */
5588         /* to cause a forfeit on a justified illegal-move complaint      */
5589         /* of the opponent.                                              */
5590         if( gameMode==TwoMachinesPlay && appData.testLegality
5591             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5592                                                               ) {
5593            ChessMove moveType;
5594            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5595                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5596                              fromY, fromX, toY, toX, promoChar);
5597             if (appData.debugMode) {
5598                 int i;
5599                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5600                     castlingRights[forwardMostMove][i], castlingRank[i]);
5601                 fprintf(debugFP, "castling rights\n");
5602             }
5603             if(moveType == IllegalMove) {
5604                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5605                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5606                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5607                            buf1, GE_XBOARD);
5608                 return;
5609            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5610            /* [HGM] Kludge to handle engines that send FRC-style castling
5611               when they shouldn't (like TSCP-Gothic) */
5612            switch(moveType) {
5613              case WhiteASideCastleFR:
5614              case BlackASideCastleFR:
5615                toX+=2;
5616                currentMoveString[2]++;
5617                break;
5618              case WhiteHSideCastleFR:
5619              case BlackHSideCastleFR:
5620                toX--;
5621                currentMoveString[2]--;
5622                break;
5623              default: ; // nothing to do, but suppresses warning of pedantic compilers
5624            }
5625         }
5626         hintRequested = FALSE;
5627         lastHint[0] = NULLCHAR;
5628         bookRequested = FALSE;
5629         /* Program may be pondering now */
5630         cps->maybeThinking = TRUE;
5631         if (cps->sendTime == 2) cps->sendTime = 1;
5632         if (cps->offeredDraw) cps->offeredDraw--;
5633
5634 #if ZIPPY
5635         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5636             first.initDone) {
5637           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5638           ics_user_moved = 1;
5639           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5640                 char buf[3*MSG_SIZ];
5641
5642                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5643                         programStats.score / 100.,
5644                         programStats.depth,
5645                         programStats.time / 100.,
5646                         (unsigned int)programStats.nodes,
5647                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5648                         programStats.movelist);
5649                 SendToICS(buf);
5650 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5651           }
5652         }
5653 #endif
5654         /* currentMoveString is set as a side-effect of ParseOneMove */
5655         strcpy(machineMove, currentMoveString);
5656         strcat(machineMove, "\n");
5657         strcpy(moveList[forwardMostMove], machineMove);
5658
5659         /* [AS] Save move info and clear stats for next move */
5660         pvInfoList[ forwardMostMove ].score = programStats.score;
5661         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5662         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5663         ClearProgramStats();
5664         thinkOutput[0] = NULLCHAR;
5665         hiddenThinkOutputState = 0;
5666
5667         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5668
5669         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5670         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5671             int count = 0;
5672
5673             while( count < adjudicateLossPlies ) {
5674                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5675
5676                 if( count & 1 ) {
5677                     score = -score; /* Flip score for winning side */
5678                 }
5679
5680                 if( score > adjudicateLossThreshold ) {
5681                     break;
5682                 }
5683
5684                 count++;
5685             }
5686
5687             if( count >= adjudicateLossPlies ) {
5688                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5689
5690                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5691                     "Xboard adjudication", 
5692                     GE_XBOARD );
5693
5694                 return;
5695             }
5696         }
5697
5698         if( gameMode == TwoMachinesPlay ) {
5699           // [HGM] some adjudications useful with buggy engines
5700             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5701           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5702
5703
5704             if( appData.testLegality )
5705             {   /* [HGM] Some more adjudications for obstinate engines */
5706                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5707                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5708                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5709                 static int moveCount = 6;
5710                 ChessMove result;
5711                 char *reason = NULL;
5712
5713                 /* Count what is on board. */
5714                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5715                 {   ChessSquare p = boards[forwardMostMove][i][j];
5716                     int m=i;
5717
5718                     switch((int) p)
5719                     {   /* count B,N,R and other of each side */
5720                         case WhiteKing:
5721                         case BlackKing:
5722                              NrK++; break; // [HGM] atomic: count Kings
5723                         case WhiteKnight:
5724                              NrWN++; break;
5725                         case WhiteBishop:
5726                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5727                              bishopsColor |= 1 << ((i^j)&1);
5728                              NrWB++; break;
5729                         case BlackKnight:
5730                              NrBN++; break;
5731                         case BlackBishop:
5732                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5733                              bishopsColor |= 1 << ((i^j)&1);
5734                              NrBB++; break;
5735                         case WhiteRook:
5736                              NrWR++; break;
5737                         case BlackRook:
5738                              NrBR++; break;
5739                         case WhiteQueen:
5740                              NrWQ++; break;
5741                         case BlackQueen:
5742                              NrBQ++; break;
5743                         case EmptySquare: 
5744                              break;
5745                         case BlackPawn:
5746                              m = 7-i;
5747                         case WhitePawn:
5748                              PawnAdvance += m; NrPawns++;
5749                     }
5750                     NrPieces += (p != EmptySquare);
5751                     NrW += ((int)p < (int)BlackPawn);
5752                     if(gameInfo.variant == VariantXiangqi && 
5753                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5754                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5755                         NrW -= ((int)p < (int)BlackPawn);
5756                     }
5757                 }
5758
5759                 /* Some material-based adjudications that have to be made before stalemate test */
5760                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5761                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5762                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5763                      if(appData.checkMates) {
5764                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5765                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5766                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5767                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5768                          return;
5769                      }
5770                 }
5771
5772                 /* Bare King in Shatranj (loses) or Losers (wins) */
5773                 if( NrW == 1 || NrPieces - NrW == 1) {
5774                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5775                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5776                      if(appData.checkMates) {
5777                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5778                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5779                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5780                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5781                          return;
5782                      }
5783                   } else
5784                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5785                   {    /* bare King */
5786                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5787                         if(appData.checkMates) {
5788                             /* but only adjudicate if adjudication enabled */
5789                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5790                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5791                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5792                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5793                             return;
5794                         }
5795                   }
5796                 } else bare = 1;
5797
5798
5799             // don't wait for engine to announce game end if we can judge ourselves
5800             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5801                                        castlingRights[forwardMostMove]) ) {
5802               case MT_CHECK:
5803                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5804                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5805                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5806                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5807                             checkCnt++;
5808                         if(checkCnt >= 2) {
5809                             reason = "Xboard adjudication: 3rd check";
5810                             epStatus[forwardMostMove] = EP_CHECKMATE;
5811                             break;
5812                         }
5813                     }
5814                 }
5815               case MT_NONE:
5816               default:
5817                 break;
5818               case MT_STALEMATE:
5819               case MT_STAINMATE:
5820                 reason = "Xboard adjudication: Stalemate";
5821                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5822                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5823                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5824                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5825                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5826                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5827                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5828                                                                         EP_CHECKMATE : EP_WINS);
5829                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5830                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5831                 }
5832                 break;
5833               case MT_CHECKMATE:
5834                 reason = "Xboard adjudication: Checkmate";
5835                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5836                 break;
5837             }
5838
5839                 switch(i = epStatus[forwardMostMove]) {
5840                     case EP_STALEMATE:
5841                         result = GameIsDrawn; break;
5842                     case EP_CHECKMATE:
5843                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5844                     case EP_WINS:
5845                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5846                     default:
5847                         result = (ChessMove) 0;
5848                 }
5849                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5850                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5851                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5852                     GameEnds( result, reason, GE_XBOARD );
5853                     return;
5854                 }
5855
5856                 /* Next absolutely insufficient mating material. */
5857                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5858                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5859                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5860                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5861                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5862
5863                      /* always flag draws, for judging claims */
5864                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5865
5866                      if(appData.materialDraws) {
5867                          /* but only adjudicate them if adjudication enabled */
5868                          SendToProgram("force\n", cps->other); // suppress reply
5869                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5870                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5871                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5872                          return;
5873                      }
5874                 }
5875
5876                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5877                 if(NrPieces == 4 && 
5878                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5879                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5880                    || NrWN==2 || NrBN==2     /* KNNK */
5881                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5882                   ) ) {
5883                      if(--moveCount < 0 && appData.trivialDraws)
5884                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5885                           SendToProgram("force\n", cps->other); // suppress reply
5886                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5887                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5888                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5889                           return;
5890                      }
5891                 } else moveCount = 6;
5892             }
5893           }
5894           
5895           if (appData.debugMode) { int i;
5896             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5897                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5898                     appData.drawRepeats);
5899             for( i=forwardMostMove; i>=backwardMostMove; i-- )
5900               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5901             
5902           }
5903
5904                 /* Check for rep-draws */
5905                 count = 0;
5906                 for(k = forwardMostMove-2;
5907                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5908                         epStatus[k] < EP_UNKNOWN &&
5909                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5910                     k-=2)
5911                 {   int rights=0;
5912                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5913                         /* compare castling rights */
5914                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5915                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5916                                 rights++; /* King lost rights, while rook still had them */
5917                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5918                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5919                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5920                                    rights++; /* but at least one rook lost them */
5921                         }
5922                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5923                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5924                                 rights++; 
5925                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5926                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5927                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5928                                    rights++;
5929                         }
5930                         if( rights == 0 && ++count > appData.drawRepeats-2
5931                             && appData.drawRepeats > 1) {
5932                              /* adjudicate after user-specified nr of repeats */
5933                              SendToProgram("force\n", cps->other); // suppress reply
5934                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5935                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5936                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5937                                 // [HGM] xiangqi: check for forbidden perpetuals
5938                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5939                                 for(m=forwardMostMove; m>k; m-=2) {
5940                                     if(MateTest(boards[m], PosFlags(m), 
5941                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5942                                         ourPerpetual = 0; // the current mover did not always check
5943                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5944                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5945                                         hisPerpetual = 0; // the opponent did not always check
5946                                 }
5947                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5948                                                                         ourPerpetual, hisPerpetual);
5949                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5950                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5951                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5952                                     return;
5953                                 }
5954                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5955                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5956                                 // Now check for perpetual chases
5957                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5958                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5959                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5960                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5961                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5962                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5963                                         return;
5964                                     }
5965                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5966                                         break; // Abort repetition-checking loop.
5967                                 }
5968                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5969                              }
5970                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5971                              return;
5972                         }
5973                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5974                              epStatus[forwardMostMove] = EP_REP_DRAW;
5975                     }
5976                 }
5977
5978                 /* Now we test for 50-move draws. Determine ply count */
5979                 count = forwardMostMove;
5980                 /* look for last irreversble move */
5981                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5982                     count--;
5983                 /* if we hit starting position, add initial plies */
5984                 if( count == backwardMostMove )
5985                     count -= initialRulePlies;
5986                 count = forwardMostMove - count; 
5987                 if( count >= 100)
5988                          epStatus[forwardMostMove] = EP_RULE_DRAW;
5989                          /* this is used to judge if draw claims are legal */
5990                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
5991                          SendToProgram("force\n", cps->other); // suppress reply
5992                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5993                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5994                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
5995                          return;
5996                 }
5997
5998                 /* if draw offer is pending, treat it as a draw claim
5999                  * when draw condition present, to allow engines a way to
6000                  * claim draws before making their move to avoid a race
6001                  * condition occurring after their move
6002                  */
6003                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6004                          char *p = NULL;
6005                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6006                              p = "Draw claim: 50-move rule";
6007                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6008                              p = "Draw claim: 3-fold repetition";
6009                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6010                              p = "Draw claim: insufficient mating material";
6011                          if( p != NULL ) {
6012                              SendToProgram("force\n", cps->other); // suppress reply
6013                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6014                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6015                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6016                              return;
6017                          }
6018                 }
6019
6020
6021                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6022                     SendToProgram("force\n", cps->other); // suppress reply
6023                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6024                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6025
6026                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6027
6028                     return;
6029                 }
6030         }
6031
6032         bookHit = NULL;
6033         if (gameMode == TwoMachinesPlay) {
6034             /* [HGM] relaying draw offers moved to after reception of move */
6035             /* and interpreting offer as claim if it brings draw condition */
6036             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6037                 SendToProgram("draw\n", cps->other);
6038             }
6039             if (cps->other->sendTime) {
6040                 SendTimeRemaining(cps->other,
6041                                   cps->other->twoMachinesColor[0] == 'w');
6042             }
6043             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6044             if (firstMove && !bookHit) {
6045                 firstMove = FALSE;
6046                 if (cps->other->useColors) {
6047                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6048                 }
6049                 SendToProgram("go\n", cps->other);
6050             }
6051             cps->other->maybeThinking = TRUE;
6052         }
6053
6054         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6055         
6056         if (!pausing && appData.ringBellAfterMoves) {
6057             RingBell();
6058         }
6059
6060         /* 
6061          * Reenable menu items that were disabled while
6062          * machine was thinking
6063          */
6064         if (gameMode != TwoMachinesPlay)
6065             SetUserThinkingEnables();
6066
6067         // [HGM] book: after book hit opponent has received move and is now in force mode
6068         // force the book reply into it, and then fake that it outputted this move by jumping
6069         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6070         if(bookHit) {
6071                 static char bookMove[MSG_SIZ]; // a bit generous?
6072
6073                 strcpy(bookMove, "move ");
6074                 strcat(bookMove, bookHit);
6075                 message = bookMove;
6076                 cps = cps->other;
6077                 programStats.nodes = programStats.depth = programStats.time = 
6078                 programStats.score = programStats.got_only_move = 0;
6079                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6080
6081                 if(cps->lastPing != cps->lastPong) {
6082                     savedMessage = message; // args for deferred call
6083                     savedState = cps;
6084                     ScheduleDelayedEvent(DeferredBookMove, 10);
6085                     return;
6086                 }
6087                 goto FakeBookMove;
6088         }
6089
6090         return;
6091     }
6092
6093     /* Set special modes for chess engines.  Later something general
6094      *  could be added here; for now there is just one kludge feature,
6095      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6096      *  when "xboard" is given as an interactive command.
6097      */
6098     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6099         cps->useSigint = FALSE;
6100         cps->useSigterm = FALSE;
6101     }
6102     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6103       ParseFeatures(message+8, cps);
6104       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6105     }
6106
6107     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6108      * want this, I was asked to put it in, and obliged.
6109      */
6110     if (!strncmp(message, "setboard ", 9)) {
6111         Board initial_position; int i;
6112
6113         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6114
6115         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6116             DisplayError(_("Bad FEN received from engine"), 0);
6117             return ;
6118         } else {
6119            Reset(FALSE, FALSE);
6120            CopyBoard(boards[0], initial_position);
6121            initialRulePlies = FENrulePlies;
6122            epStatus[0] = FENepStatus;
6123            for( i=0; i<nrCastlingRights; i++ )
6124                 castlingRights[0][i] = FENcastlingRights[i];
6125            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6126            else gameMode = MachinePlaysBlack;                 
6127            DrawPosition(FALSE, boards[currentMove]);
6128         }
6129         return;
6130     }
6131
6132     /*
6133      * Look for communication commands
6134      */
6135     if (!strncmp(message, "telluser ", 9)) {
6136         DisplayNote(message + 9);
6137         return;
6138     }
6139     if (!strncmp(message, "tellusererror ", 14)) {
6140         DisplayError(message + 14, 0);
6141         return;
6142     }
6143     if (!strncmp(message, "tellopponent ", 13)) {
6144       if (appData.icsActive) {
6145         if (loggedOn) {
6146           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6147           SendToICS(buf1);
6148         }
6149       } else {
6150         DisplayNote(message + 13);
6151       }
6152       return;
6153     }
6154     if (!strncmp(message, "tellothers ", 11)) {
6155       if (appData.icsActive) {
6156         if (loggedOn) {
6157           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6158           SendToICS(buf1);
6159         }
6160       }
6161       return;
6162     }
6163     if (!strncmp(message, "tellall ", 8)) {
6164       if (appData.icsActive) {
6165         if (loggedOn) {
6166           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6167           SendToICS(buf1);
6168         }
6169       } else {
6170         DisplayNote(message + 8);
6171       }
6172       return;
6173     }
6174     if (strncmp(message, "warning", 7) == 0) {
6175         /* Undocumented feature, use tellusererror in new code */
6176         DisplayError(message, 0);
6177         return;
6178     }
6179     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6180         strcpy(realname, cps->tidy);
6181         strcat(realname, " query");
6182         AskQuestion(realname, buf2, buf1, cps->pr);
6183         return;
6184     }
6185     /* Commands from the engine directly to ICS.  We don't allow these to be 
6186      *  sent until we are logged on. Crafty kibitzes have been known to 
6187      *  interfere with the login process.
6188      */
6189     if (loggedOn) {
6190         if (!strncmp(message, "tellics ", 8)) {
6191             SendToICS(message + 8);
6192             SendToICS("\n");
6193             return;
6194         }
6195         if (!strncmp(message, "tellicsnoalias ", 15)) {
6196             SendToICS(ics_prefix);
6197             SendToICS(message + 15);
6198             SendToICS("\n");
6199             return;
6200         }
6201         /* The following are for backward compatibility only */
6202         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6203             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6204             SendToICS(ics_prefix);
6205             SendToICS(message);
6206             SendToICS("\n");
6207             return;
6208         }
6209     }
6210     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6211         return;
6212     }
6213     /*
6214      * If the move is illegal, cancel it and redraw the board.
6215      * Also deal with other error cases.  Matching is rather loose
6216      * here to accommodate engines written before the spec.
6217      */
6218     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6219         strncmp(message, "Error", 5) == 0) {
6220         if (StrStr(message, "name") || 
6221             StrStr(message, "rating") || StrStr(message, "?") ||
6222             StrStr(message, "result") || StrStr(message, "board") ||
6223             StrStr(message, "bk") || StrStr(message, "computer") ||
6224             StrStr(message, "variant") || StrStr(message, "hint") ||
6225             StrStr(message, "random") || StrStr(message, "depth") ||
6226             StrStr(message, "accepted")) {
6227             return;
6228         }
6229         if (StrStr(message, "protover")) {
6230           /* Program is responding to input, so it's apparently done
6231              initializing, and this error message indicates it is
6232              protocol version 1.  So we don't need to wait any longer
6233              for it to initialize and send feature commands. */
6234           FeatureDone(cps, 1);
6235           cps->protocolVersion = 1;
6236           return;
6237         }
6238         cps->maybeThinking = FALSE;
6239
6240         if (StrStr(message, "draw")) {
6241             /* Program doesn't have "draw" command */
6242             cps->sendDrawOffers = 0;
6243             return;
6244         }
6245         if (cps->sendTime != 1 &&
6246             (StrStr(message, "time") || StrStr(message, "otim"))) {
6247           /* Program apparently doesn't have "time" or "otim" command */
6248           cps->sendTime = 0;
6249           return;
6250         }
6251         if (StrStr(message, "analyze")) {
6252             cps->analysisSupport = FALSE;
6253             cps->analyzing = FALSE;
6254             Reset(FALSE, TRUE);
6255             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6256             DisplayError(buf2, 0);
6257             return;
6258         }
6259         if (StrStr(message, "(no matching move)st")) {
6260           /* Special kludge for GNU Chess 4 only */
6261           cps->stKludge = TRUE;
6262           SendTimeControl(cps, movesPerSession, timeControl,
6263                           timeIncrement, appData.searchDepth,
6264                           searchTime);
6265           return;
6266         }
6267         if (StrStr(message, "(no matching move)sd")) {
6268           /* Special kludge for GNU Chess 4 only */
6269           cps->sdKludge = TRUE;
6270           SendTimeControl(cps, movesPerSession, timeControl,
6271                           timeIncrement, appData.searchDepth,
6272                           searchTime);
6273           return;
6274         }
6275         if (!StrStr(message, "llegal")) {
6276             return;
6277         }
6278         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6279             gameMode == IcsIdle) return;
6280         if (forwardMostMove <= backwardMostMove) return;
6281         if (pausing) PauseEvent();
6282       if(appData.forceIllegal) {
6283             // [HGM] illegal: machine refused move; force position after move into it
6284           SendToProgram("force\n", cps);
6285           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6286                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6287                 // when black is to move, while there might be nothing on a2 or black
6288                 // might already have the move. So send the board as if white has the move.
6289                 // But first we must change the stm of the engine, as it refused the last move
6290                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6291                 if(WhiteOnMove(forwardMostMove)) {
6292                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6293                     SendBoard(cps, forwardMostMove); // kludgeless board
6294                 } else {
6295                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6296                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6297                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6298                 }
6299           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6300             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6301                  gameMode == TwoMachinesPlay)
6302               SendToProgram("go\n", cps);
6303             return;
6304       } else
6305         if (gameMode == PlayFromGameFile) {
6306             /* Stop reading this game file */
6307             gameMode = EditGame;
6308             ModeHighlight();
6309         }
6310         currentMove = --forwardMostMove;
6311         DisplayMove(currentMove-1); /* before DisplayMoveError */
6312         SwitchClocks();
6313         DisplayBothClocks();
6314         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6315                 parseList[currentMove], cps->which);
6316         DisplayMoveError(buf1);
6317         DrawPosition(FALSE, boards[currentMove]);
6318
6319         /* [HGM] illegal-move claim should forfeit game when Xboard */
6320         /* only passes fully legal moves                            */
6321         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6322             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6323                                 "False illegal-move claim", GE_XBOARD );
6324         }
6325         return;
6326     }
6327     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6328         /* Program has a broken "time" command that
6329            outputs a string not ending in newline.
6330            Don't use it. */
6331         cps->sendTime = 0;
6332     }
6333     
6334     /*
6335      * If chess program startup fails, exit with an error message.
6336      * Attempts to recover here are futile.
6337      */
6338     if ((StrStr(message, "unknown host") != NULL)
6339         || (StrStr(message, "No remote directory") != NULL)
6340         || (StrStr(message, "not found") != NULL)
6341         || (StrStr(message, "No such file") != NULL)
6342         || (StrStr(message, "can't alloc") != NULL)
6343         || (StrStr(message, "Permission denied") != NULL)) {
6344
6345         cps->maybeThinking = FALSE;
6346         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6347                 cps->which, cps->program, cps->host, message);
6348         RemoveInputSource(cps->isr);
6349         DisplayFatalError(buf1, 0, 1);
6350         return;
6351     }
6352     
6353     /* 
6354      * Look for hint output
6355      */
6356     if (sscanf(message, "Hint: %s", buf1) == 1) {
6357         if (cps == &first && hintRequested) {
6358             hintRequested = FALSE;
6359             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6360                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6361                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6362                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6363                                     fromY, fromX, toY, toX, promoChar, buf1);
6364                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6365                 DisplayInformation(buf2);
6366             } else {
6367                 /* Hint move could not be parsed!? */
6368               snprintf(buf2, sizeof(buf2),
6369                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6370                         buf1, cps->which);
6371                 DisplayError(buf2, 0);
6372             }
6373         } else {
6374             strcpy(lastHint, buf1);
6375         }
6376         return;
6377     }
6378
6379     /*
6380      * Ignore other messages if game is not in progress
6381      */
6382     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6383         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6384
6385     /*
6386      * look for win, lose, draw, or draw offer
6387      */
6388     if (strncmp(message, "1-0", 3) == 0) {
6389         char *p, *q, *r = "";
6390         p = strchr(message, '{');
6391         if (p) {
6392             q = strchr(p, '}');
6393             if (q) {
6394                 *q = NULLCHAR;
6395                 r = p + 1;
6396             }
6397         }
6398         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6399         return;
6400     } else if (strncmp(message, "0-1", 3) == 0) {
6401         char *p, *q, *r = "";
6402         p = strchr(message, '{');
6403         if (p) {
6404             q = strchr(p, '}');
6405             if (q) {
6406                 *q = NULLCHAR;
6407                 r = p + 1;
6408             }
6409         }
6410         /* Kludge for Arasan 4.1 bug */
6411         if (strcmp(r, "Black resigns") == 0) {
6412             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6413             return;
6414         }
6415         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6416         return;
6417     } else if (strncmp(message, "1/2", 3) == 0) {
6418         char *p, *q, *r = "";
6419         p = strchr(message, '{');
6420         if (p) {
6421             q = strchr(p, '}');
6422             if (q) {
6423                 *q = NULLCHAR;
6424                 r = p + 1;
6425             }
6426         }
6427             
6428         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6429         return;
6430
6431     } else if (strncmp(message, "White resign", 12) == 0) {
6432         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6433         return;
6434     } else if (strncmp(message, "Black resign", 12) == 0) {
6435         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6436         return;
6437     } else if (strncmp(message, "White matches", 13) == 0 ||
6438                strncmp(message, "Black matches", 13) == 0   ) {
6439         /* [HGM] ignore GNUShogi noises */
6440         return;
6441     } else if (strncmp(message, "White", 5) == 0 &&
6442                message[5] != '(' &&
6443                StrStr(message, "Black") == NULL) {
6444         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6445         return;
6446     } else if (strncmp(message, "Black", 5) == 0 &&
6447                message[5] != '(') {
6448         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6449         return;
6450     } else if (strcmp(message, "resign") == 0 ||
6451                strcmp(message, "computer resigns") == 0) {
6452         switch (gameMode) {
6453           case MachinePlaysBlack:
6454           case IcsPlayingBlack:
6455             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6456             break;
6457           case MachinePlaysWhite:
6458           case IcsPlayingWhite:
6459             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6460             break;
6461           case TwoMachinesPlay:
6462             if (cps->twoMachinesColor[0] == 'w')
6463               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6464             else
6465               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6466             break;
6467           default:
6468             /* can't happen */
6469             break;
6470         }
6471         return;
6472     } else if (strncmp(message, "opponent mates", 14) == 0) {
6473         switch (gameMode) {
6474           case MachinePlaysBlack:
6475           case IcsPlayingBlack:
6476             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6477             break;
6478           case MachinePlaysWhite:
6479           case IcsPlayingWhite:
6480             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6481             break;
6482           case TwoMachinesPlay:
6483             if (cps->twoMachinesColor[0] == 'w')
6484               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6485             else
6486               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6487             break;
6488           default:
6489             /* can't happen */
6490             break;
6491         }
6492         return;
6493     } else if (strncmp(message, "computer mates", 14) == 0) {
6494         switch (gameMode) {
6495           case MachinePlaysBlack:
6496           case IcsPlayingBlack:
6497             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6498             break;
6499           case MachinePlaysWhite:
6500           case IcsPlayingWhite:
6501             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6502             break;
6503           case TwoMachinesPlay:
6504             if (cps->twoMachinesColor[0] == 'w')
6505               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6506             else
6507               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6508             break;
6509           default:
6510             /* can't happen */
6511             break;
6512         }
6513         return;
6514     } else if (strncmp(message, "checkmate", 9) == 0) {
6515         if (WhiteOnMove(forwardMostMove)) {
6516             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6517         } else {
6518             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6519         }
6520         return;
6521     } else if (strstr(message, "Draw") != NULL ||
6522                strstr(message, "game is a draw") != NULL) {
6523         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6524         return;
6525     } else if (strstr(message, "offer") != NULL &&
6526                strstr(message, "draw") != NULL) {
6527 #if ZIPPY
6528         if (appData.zippyPlay && first.initDone) {
6529             /* Relay offer to ICS */
6530             SendToICS(ics_prefix);
6531             SendToICS("draw\n");
6532         }
6533 #endif
6534         cps->offeredDraw = 2; /* valid until this engine moves twice */
6535         if (gameMode == TwoMachinesPlay) {
6536             if (cps->other->offeredDraw) {
6537                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6538             /* [HGM] in two-machine mode we delay relaying draw offer      */
6539             /* until after we also have move, to see if it is really claim */
6540             }
6541         } else if (gameMode == MachinePlaysWhite ||
6542                    gameMode == MachinePlaysBlack) {
6543           if (userOfferedDraw) {
6544             DisplayInformation(_("Machine accepts your draw offer"));
6545             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6546           } else {
6547             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6548           }
6549         }
6550     }
6551
6552     
6553     /*
6554      * Look for thinking output
6555      */
6556     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6557           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6558                                 ) {
6559         int plylev, mvleft, mvtot, curscore, time;
6560         char mvname[MOVE_LEN];
6561         u64 nodes; // [DM]
6562         char plyext;
6563         int ignore = FALSE;
6564         int prefixHint = FALSE;
6565         mvname[0] = NULLCHAR;
6566
6567         switch (gameMode) {
6568           case MachinePlaysBlack:
6569           case IcsPlayingBlack:
6570             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6571             break;
6572           case MachinePlaysWhite:
6573           case IcsPlayingWhite:
6574             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6575             break;
6576           case AnalyzeMode:
6577           case AnalyzeFile:
6578             break;
6579           case IcsObserving: /* [DM] icsEngineAnalyze */
6580             if (!appData.icsEngineAnalyze) ignore = TRUE;
6581             break;
6582           case TwoMachinesPlay:
6583             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6584                 ignore = TRUE;
6585             }
6586             break;
6587           default:
6588             ignore = TRUE;
6589             break;
6590         }
6591
6592         if (!ignore) {
6593             buf1[0] = NULLCHAR;
6594             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6595                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6596
6597                 if (plyext != ' ' && plyext != '\t') {
6598                     time *= 100;
6599                 }
6600
6601                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6602                 if( cps->scoreIsAbsolute && 
6603                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6604                 {
6605                     curscore = -curscore;
6606                 }
6607
6608
6609                 programStats.depth = plylev;
6610                 programStats.nodes = nodes;
6611                 programStats.time = time;
6612                 programStats.score = curscore;
6613                 programStats.got_only_move = 0;
6614
6615                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6616                         int ticklen;
6617
6618                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6619                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6620                         if(WhiteOnMove(forwardMostMove)) 
6621                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6622                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6623                 }
6624
6625                 /* Buffer overflow protection */
6626                 if (buf1[0] != NULLCHAR) {
6627                     if (strlen(buf1) >= sizeof(programStats.movelist)
6628                         && appData.debugMode) {
6629                         fprintf(debugFP,
6630                                 "PV is too long; using the first %d bytes.\n",
6631                                 sizeof(programStats.movelist) - 1);
6632                     }
6633
6634                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6635                 } else {
6636                     sprintf(programStats.movelist, " no PV\n");
6637                 }
6638
6639                 if (programStats.seen_stat) {
6640                     programStats.ok_to_send = 1;
6641                 }
6642
6643                 if (strchr(programStats.movelist, '(') != NULL) {
6644                     programStats.line_is_book = 1;
6645                     programStats.nr_moves = 0;
6646                     programStats.moves_left = 0;
6647                 } else {
6648                     programStats.line_is_book = 0;
6649                 }
6650
6651                 SendProgramStatsToFrontend( cps, &programStats );
6652
6653                 /* 
6654                     [AS] Protect the thinkOutput buffer from overflow... this
6655                     is only useful if buf1 hasn't overflowed first!
6656                 */
6657                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6658                         plylev, 
6659                         (gameMode == TwoMachinesPlay ?
6660                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6661                         ((double) curscore) / 100.0,
6662                         prefixHint ? lastHint : "",
6663                         prefixHint ? " " : "" );
6664
6665                 if( buf1[0] != NULLCHAR ) {
6666                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6667
6668                     if( strlen(buf1) > max_len ) {
6669                         if( appData.debugMode) {
6670                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6671                         }
6672                         buf1[max_len+1] = '\0';
6673                     }
6674
6675                     strcat( thinkOutput, buf1 );
6676                 }
6677
6678                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6679                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6680                     DisplayMove(currentMove - 1);
6681                     DisplayAnalysis();
6682                 }
6683                 return;
6684
6685             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6686                 /* crafty (9.25+) says "(only move) <move>"
6687                  * if there is only 1 legal move
6688                  */
6689                 sscanf(p, "(only move) %s", buf1);
6690                 sprintf(thinkOutput, "%s (only move)", buf1);
6691                 sprintf(programStats.movelist, "%s (only move)", buf1);
6692                 programStats.depth = 1;
6693                 programStats.nr_moves = 1;
6694                 programStats.moves_left = 1;
6695                 programStats.nodes = 1;
6696                 programStats.time = 1;
6697                 programStats.got_only_move = 1;
6698
6699                 /* Not really, but we also use this member to
6700                    mean "line isn't going to change" (Crafty
6701                    isn't searching, so stats won't change) */
6702                 programStats.line_is_book = 1;
6703
6704                 SendProgramStatsToFrontend( cps, &programStats );
6705                 
6706                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6707                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6708                     DisplayMove(currentMove - 1);
6709                     DisplayAnalysis();
6710                 }
6711                 return;
6712             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6713                               &time, &nodes, &plylev, &mvleft,
6714                               &mvtot, mvname) >= 5) {
6715                 /* The stat01: line is from Crafty (9.29+) in response
6716                    to the "." command */
6717                 programStats.seen_stat = 1;
6718                 cps->maybeThinking = TRUE;
6719
6720                 if (programStats.got_only_move || !appData.periodicUpdates)
6721                   return;
6722
6723                 programStats.depth = plylev;
6724                 programStats.time = time;
6725                 programStats.nodes = nodes;
6726                 programStats.moves_left = mvleft;
6727                 programStats.nr_moves = mvtot;
6728                 strcpy(programStats.move_name, mvname);
6729                 programStats.ok_to_send = 1;
6730                 programStats.movelist[0] = '\0';
6731
6732                 SendProgramStatsToFrontend( cps, &programStats );
6733
6734                 DisplayAnalysis();
6735                 return;
6736
6737             } else if (strncmp(message,"++",2) == 0) {
6738                 /* Crafty 9.29+ outputs this */
6739                 programStats.got_fail = 2;
6740                 return;
6741
6742             } else if (strncmp(message,"--",2) == 0) {
6743                 /* Crafty 9.29+ outputs this */
6744                 programStats.got_fail = 1;
6745                 return;
6746
6747             } else if (thinkOutput[0] != NULLCHAR &&
6748                        strncmp(message, "    ", 4) == 0) {
6749                 unsigned message_len;
6750
6751                 p = message;
6752                 while (*p && *p == ' ') p++;
6753
6754                 message_len = strlen( p );
6755
6756                 /* [AS] Avoid buffer overflow */
6757                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6758                     strcat(thinkOutput, " ");
6759                     strcat(thinkOutput, p);
6760                 }
6761
6762                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6763                     strcat(programStats.movelist, " ");
6764                     strcat(programStats.movelist, p);
6765                 }
6766
6767                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6768                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6769                     DisplayMove(currentMove - 1);
6770                     DisplayAnalysis();
6771                 }
6772                 return;
6773             }
6774         }
6775         else {
6776             buf1[0] = NULLCHAR;
6777
6778             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6779                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6780             {
6781                 ChessProgramStats cpstats;
6782
6783                 if (plyext != ' ' && plyext != '\t') {
6784                     time *= 100;
6785                 }
6786
6787                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6788                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6789                     curscore = -curscore;
6790                 }
6791
6792                 cpstats.depth = plylev;
6793                 cpstats.nodes = nodes;
6794                 cpstats.time = time;
6795                 cpstats.score = curscore;
6796                 cpstats.got_only_move = 0;
6797                 cpstats.movelist[0] = '\0';
6798
6799                 if (buf1[0] != NULLCHAR) {
6800                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6801                 }
6802
6803                 cpstats.ok_to_send = 0;
6804                 cpstats.line_is_book = 0;
6805                 cpstats.nr_moves = 0;
6806                 cpstats.moves_left = 0;
6807
6808                 SendProgramStatsToFrontend( cps, &cpstats );
6809             }
6810         }
6811     }
6812 }
6813
6814
6815 /* Parse a game score from the character string "game", and
6816    record it as the history of the current game.  The game
6817    score is NOT assumed to start from the standard position. 
6818    The display is not updated in any way.
6819    */
6820 void
6821 ParseGameHistory(game)
6822      char *game;
6823 {
6824     ChessMove moveType;
6825     int fromX, fromY, toX, toY, boardIndex;
6826     char promoChar;
6827     char *p, *q;
6828     char buf[MSG_SIZ];
6829
6830     if (appData.debugMode)
6831       fprintf(debugFP, "Parsing game history: %s\n", game);
6832
6833     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6834     gameInfo.site = StrSave(appData.icsHost);
6835     gameInfo.date = PGNDate();
6836     gameInfo.round = StrSave("-");
6837
6838     /* Parse out names of players */
6839     while (*game == ' ') game++;
6840     p = buf;
6841     while (*game != ' ') *p++ = *game++;
6842     *p = NULLCHAR;
6843     gameInfo.white = StrSave(buf);
6844     while (*game == ' ') game++;
6845     p = buf;
6846     while (*game != ' ' && *game != '\n') *p++ = *game++;
6847     *p = NULLCHAR;
6848     gameInfo.black = StrSave(buf);
6849
6850     /* Parse moves */
6851     boardIndex = blackPlaysFirst ? 1 : 0;
6852     yynewstr(game);
6853     for (;;) {
6854         yyboardindex = boardIndex;
6855         moveType = (ChessMove) yylex();
6856         switch (moveType) {
6857           case IllegalMove:             /* maybe suicide chess, etc. */
6858   if (appData.debugMode) {
6859     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6860     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6861     setbuf(debugFP, NULL);
6862   }
6863           case WhitePromotionChancellor:
6864           case BlackPromotionChancellor:
6865           case WhitePromotionArchbishop:
6866           case BlackPromotionArchbishop:
6867           case WhitePromotionQueen:
6868           case BlackPromotionQueen:
6869           case WhitePromotionRook:
6870           case BlackPromotionRook:
6871           case WhitePromotionBishop:
6872           case BlackPromotionBishop:
6873           case WhitePromotionKnight:
6874           case BlackPromotionKnight:
6875           case WhitePromotionKing:
6876           case BlackPromotionKing:
6877           case NormalMove:
6878           case WhiteCapturesEnPassant:
6879           case BlackCapturesEnPassant:
6880           case WhiteKingSideCastle:
6881           case WhiteQueenSideCastle:
6882           case BlackKingSideCastle:
6883           case BlackQueenSideCastle:
6884           case WhiteKingSideCastleWild:
6885           case WhiteQueenSideCastleWild:
6886           case BlackKingSideCastleWild:
6887           case BlackQueenSideCastleWild:
6888           /* PUSH Fabien */
6889           case WhiteHSideCastleFR:
6890           case WhiteASideCastleFR:
6891           case BlackHSideCastleFR:
6892           case BlackASideCastleFR:
6893           /* POP Fabien */
6894             fromX = currentMoveString[0] - AAA;
6895             fromY = currentMoveString[1] - ONE;
6896             toX = currentMoveString[2] - AAA;
6897             toY = currentMoveString[3] - ONE;
6898             promoChar = currentMoveString[4];
6899             break;
6900           case WhiteDrop:
6901           case BlackDrop:
6902             fromX = moveType == WhiteDrop ?
6903               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6904             (int) CharToPiece(ToLower(currentMoveString[0]));
6905             fromY = DROP_RANK;
6906             toX = currentMoveString[2] - AAA;
6907             toY = currentMoveString[3] - ONE;
6908             promoChar = NULLCHAR;
6909             break;
6910           case AmbiguousMove:
6911             /* bug? */
6912             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6913   if (appData.debugMode) {
6914     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6915     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6916     setbuf(debugFP, NULL);
6917   }
6918             DisplayError(buf, 0);
6919             return;
6920           case ImpossibleMove:
6921             /* bug? */
6922             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6923   if (appData.debugMode) {
6924     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6925     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6926     setbuf(debugFP, NULL);
6927   }
6928             DisplayError(buf, 0);
6929             return;
6930           case (ChessMove) 0:   /* end of file */
6931             if (boardIndex < backwardMostMove) {
6932                 /* Oops, gap.  How did that happen? */
6933                 DisplayError(_("Gap in move list"), 0);
6934                 return;
6935             }
6936             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6937             if (boardIndex > forwardMostMove) {
6938                 forwardMostMove = boardIndex;
6939             }
6940             return;
6941           case ElapsedTime:
6942             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6943                 strcat(parseList[boardIndex-1], " ");
6944                 strcat(parseList[boardIndex-1], yy_text);
6945             }
6946             continue;
6947           case Comment:
6948           case PGNTag:
6949           case NAG:
6950           default:
6951             /* ignore */
6952             continue;
6953           case WhiteWins:
6954           case BlackWins:
6955           case GameIsDrawn:
6956           case GameUnfinished:
6957             if (gameMode == IcsExamining) {
6958                 if (boardIndex < backwardMostMove) {
6959                     /* Oops, gap.  How did that happen? */
6960                     return;
6961                 }
6962                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6963                 return;
6964             }
6965             gameInfo.result = moveType;
6966             p = strchr(yy_text, '{');
6967             if (p == NULL) p = strchr(yy_text, '(');
6968             if (p == NULL) {
6969                 p = yy_text;
6970                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6971             } else {
6972                 q = strchr(p, *p == '{' ? '}' : ')');
6973                 if (q != NULL) *q = NULLCHAR;
6974                 p++;
6975             }
6976             gameInfo.resultDetails = StrSave(p);
6977             continue;
6978         }
6979         if (boardIndex >= forwardMostMove &&
6980             !(gameMode == IcsObserving && ics_gamenum == -1)) {
6981             backwardMostMove = blackPlaysFirst ? 1 : 0;
6982             return;
6983         }
6984         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6985                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6986                                  parseList[boardIndex]);
6987         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6988         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6989         /* currentMoveString is set as a side-effect of yylex */
6990         strcpy(moveList[boardIndex], currentMoveString);
6991         strcat(moveList[boardIndex], "\n");
6992         boardIndex++;
6993         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
6994                                         castlingRights[boardIndex], &epStatus[boardIndex]);
6995         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
6996                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
6997           case MT_NONE:
6998           case MT_STALEMATE:
6999           default:
7000             break;
7001           case MT_CHECK:
7002             if(gameInfo.variant != VariantShogi)
7003                 strcat(parseList[boardIndex - 1], "+");
7004             break;
7005           case MT_CHECKMATE:
7006           case MT_STAINMATE:
7007             strcat(parseList[boardIndex - 1], "#");
7008             break;
7009         }
7010     }
7011 }
7012
7013
7014 /* Apply a move to the given board  */
7015 void
7016 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7017      int fromX, fromY, toX, toY;
7018      int promoChar;
7019      Board board;
7020      char *castling;
7021      char *ep;
7022 {
7023   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7024
7025     /* [HGM] compute & store e.p. status and castling rights for new position */
7026     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7027     { int i;
7028
7029       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7030       oldEP = *ep;
7031       *ep = EP_NONE;
7032
7033       if( board[toY][toX] != EmptySquare ) 
7034            *ep = EP_CAPTURE;  
7035
7036       if( board[fromY][fromX] == WhitePawn ) {
7037            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7038                *ep = EP_PAWN_MOVE;
7039            if( toY-fromY==2) {
7040                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7041                         gameInfo.variant != VariantBerolina || toX < fromX)
7042                       *ep = toX | berolina;
7043                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7044                         gameInfo.variant != VariantBerolina || toX > fromX) 
7045                       *ep = toX;
7046            }
7047       } else 
7048       if( board[fromY][fromX] == BlackPawn ) {
7049            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7050                *ep = EP_PAWN_MOVE; 
7051            if( toY-fromY== -2) {
7052                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7053                         gameInfo.variant != VariantBerolina || toX < fromX)
7054                       *ep = toX | berolina;
7055                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7056                         gameInfo.variant != VariantBerolina || toX > fromX) 
7057                       *ep = toX;
7058            }
7059        }
7060
7061        for(i=0; i<nrCastlingRights; i++) {
7062            if(castling[i] == fromX && castlingRank[i] == fromY ||
7063               castling[i] == toX   && castlingRank[i] == toY   
7064              ) castling[i] = -1; // revoke for moved or captured piece
7065        }
7066
7067     }
7068
7069   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7070   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7071        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7072          
7073   if (fromX == toX && fromY == toY) return;
7074
7075   if (fromY == DROP_RANK) {
7076         /* must be first */
7077         piece = board[toY][toX] = (ChessSquare) fromX;
7078   } else {
7079      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7080      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7081      if(gameInfo.variant == VariantKnightmate)
7082          king += (int) WhiteUnicorn - (int) WhiteKing;
7083
7084     /* Code added by Tord: */
7085     /* FRC castling assumed when king captures friendly rook. */
7086     if (board[fromY][fromX] == WhiteKing &&
7087              board[toY][toX] == WhiteRook) {
7088       board[fromY][fromX] = EmptySquare;
7089       board[toY][toX] = EmptySquare;
7090       if(toX > fromX) {
7091         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7092       } else {
7093         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7094       }
7095     } else if (board[fromY][fromX] == BlackKing &&
7096                board[toY][toX] == BlackRook) {
7097       board[fromY][fromX] = EmptySquare;
7098       board[toY][toX] = EmptySquare;
7099       if(toX > fromX) {
7100         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7101       } else {
7102         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7103       }
7104     /* End of code added by Tord */
7105
7106     } else if (board[fromY][fromX] == king
7107         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7108         && toY == fromY && toX > fromX+1) {
7109         board[fromY][fromX] = EmptySquare;
7110         board[toY][toX] = king;
7111         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7112         board[fromY][BOARD_RGHT-1] = EmptySquare;
7113     } else if (board[fromY][fromX] == king
7114         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7115                && toY == fromY && toX < fromX-1) {
7116         board[fromY][fromX] = EmptySquare;
7117         board[toY][toX] = king;
7118         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7119         board[fromY][BOARD_LEFT] = EmptySquare;
7120     } else if (board[fromY][fromX] == WhitePawn
7121                && toY == BOARD_HEIGHT-1
7122                && gameInfo.variant != VariantXiangqi
7123                ) {
7124         /* white pawn promotion */
7125         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7126         if (board[toY][toX] == EmptySquare) {
7127             board[toY][toX] = WhiteQueen;
7128         }
7129         if(gameInfo.variant==VariantBughouse ||
7130            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7131             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7132         board[fromY][fromX] = EmptySquare;
7133     } else if ((fromY == BOARD_HEIGHT-4)
7134                && (toX != fromX)
7135                && gameInfo.variant != VariantXiangqi
7136                && gameInfo.variant != VariantBerolina
7137                && (board[fromY][fromX] == WhitePawn)
7138                && (board[toY][toX] == EmptySquare)) {
7139         board[fromY][fromX] = EmptySquare;
7140         board[toY][toX] = WhitePawn;
7141         captured = board[toY - 1][toX];
7142         board[toY - 1][toX] = EmptySquare;
7143     } else if ((fromY == BOARD_HEIGHT-4)
7144                && (toX == fromX)
7145                && gameInfo.variant == VariantBerolina
7146                && (board[fromY][fromX] == WhitePawn)
7147                && (board[toY][toX] == EmptySquare)) {
7148         board[fromY][fromX] = EmptySquare;
7149         board[toY][toX] = WhitePawn;
7150         if(oldEP & EP_BEROLIN_A) {
7151                 captured = board[fromY][fromX-1];
7152                 board[fromY][fromX-1] = EmptySquare;
7153         }else{  captured = board[fromY][fromX+1];
7154                 board[fromY][fromX+1] = EmptySquare;
7155         }
7156     } else if (board[fromY][fromX] == king
7157         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7158                && toY == fromY && toX > fromX+1) {
7159         board[fromY][fromX] = EmptySquare;
7160         board[toY][toX] = king;
7161         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7162         board[fromY][BOARD_RGHT-1] = EmptySquare;
7163     } else if (board[fromY][fromX] == king
7164         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7165                && toY == fromY && toX < fromX-1) {
7166         board[fromY][fromX] = EmptySquare;
7167         board[toY][toX] = king;
7168         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7169         board[fromY][BOARD_LEFT] = EmptySquare;
7170     } else if (fromY == 7 && fromX == 3
7171                && board[fromY][fromX] == BlackKing
7172                && toY == 7 && toX == 5) {
7173         board[fromY][fromX] = EmptySquare;
7174         board[toY][toX] = BlackKing;
7175         board[fromY][7] = EmptySquare;
7176         board[toY][4] = BlackRook;
7177     } else if (fromY == 7 && fromX == 3
7178                && board[fromY][fromX] == BlackKing
7179                && toY == 7 && toX == 1) {
7180         board[fromY][fromX] = EmptySquare;
7181         board[toY][toX] = BlackKing;
7182         board[fromY][0] = EmptySquare;
7183         board[toY][2] = BlackRook;
7184     } else if (board[fromY][fromX] == BlackPawn
7185                && toY == 0
7186                && gameInfo.variant != VariantXiangqi
7187                ) {
7188         /* black pawn promotion */
7189         board[0][toX] = CharToPiece(ToLower(promoChar));
7190         if (board[0][toX] == EmptySquare) {
7191             board[0][toX] = BlackQueen;
7192         }
7193         if(gameInfo.variant==VariantBughouse ||
7194            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7195             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7196         board[fromY][fromX] = EmptySquare;
7197     } else if ((fromY == 3)
7198                && (toX != fromX)
7199                && gameInfo.variant != VariantXiangqi
7200                && gameInfo.variant != VariantBerolina
7201                && (board[fromY][fromX] == BlackPawn)
7202                && (board[toY][toX] == EmptySquare)) {
7203         board[fromY][fromX] = EmptySquare;
7204         board[toY][toX] = BlackPawn;
7205         captured = board[toY + 1][toX];
7206         board[toY + 1][toX] = EmptySquare;
7207     } else if ((fromY == 3)
7208                && (toX == fromX)
7209                && gameInfo.variant == VariantBerolina
7210                && (board[fromY][fromX] == BlackPawn)
7211                && (board[toY][toX] == EmptySquare)) {
7212         board[fromY][fromX] = EmptySquare;
7213         board[toY][toX] = BlackPawn;
7214         if(oldEP & EP_BEROLIN_A) {
7215                 captured = board[fromY][fromX-1];
7216                 board[fromY][fromX-1] = EmptySquare;
7217         }else{  captured = board[fromY][fromX+1];
7218                 board[fromY][fromX+1] = EmptySquare;
7219         }
7220     } else {
7221         board[toY][toX] = board[fromY][fromX];
7222         board[fromY][fromX] = EmptySquare;
7223     }
7224
7225     /* [HGM] now we promote for Shogi, if needed */
7226     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7227         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7228   }
7229
7230     if (gameInfo.holdingsWidth != 0) {
7231
7232       /* !!A lot more code needs to be written to support holdings  */
7233       /* [HGM] OK, so I have written it. Holdings are stored in the */
7234       /* penultimate board files, so they are automaticlly stored   */
7235       /* in the game history.                                       */
7236       if (fromY == DROP_RANK) {
7237         /* Delete from holdings, by decreasing count */
7238         /* and erasing image if necessary            */
7239         p = (int) fromX;
7240         if(p < (int) BlackPawn) { /* white drop */
7241              p -= (int)WhitePawn;
7242              if(p >= gameInfo.holdingsSize) p = 0;
7243              if(--board[p][BOARD_WIDTH-2] == 0)
7244                   board[p][BOARD_WIDTH-1] = EmptySquare;
7245         } else {                  /* black drop */
7246              p -= (int)BlackPawn;
7247              if(p >= gameInfo.holdingsSize) p = 0;
7248              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7249                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7250         }
7251       }
7252       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7253           && gameInfo.variant != VariantBughouse        ) {
7254         /* [HGM] holdings: Add to holdings, if holdings exist */
7255         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7256                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7257                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7258         }
7259         p = (int) captured;
7260         if (p >= (int) BlackPawn) {
7261           p -= (int)BlackPawn;
7262           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7263                   /* in Shogi restore piece to its original  first */
7264                   captured = (ChessSquare) (DEMOTED captured);
7265                   p = DEMOTED p;
7266           }
7267           p = PieceToNumber((ChessSquare)p);
7268           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7269           board[p][BOARD_WIDTH-2]++;
7270           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7271         } else {
7272           p -= (int)WhitePawn;
7273           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7274                   captured = (ChessSquare) (DEMOTED captured);
7275                   p = DEMOTED p;
7276           }
7277           p = PieceToNumber((ChessSquare)p);
7278           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7279           board[BOARD_HEIGHT-1-p][1]++;
7280           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7281         }
7282       }
7283
7284     } else if (gameInfo.variant == VariantAtomic) {
7285       if (captured != EmptySquare) {
7286         int y, x;
7287         for (y = toY-1; y <= toY+1; y++) {
7288           for (x = toX-1; x <= toX+1; x++) {
7289             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7290                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7291               board[y][x] = EmptySquare;
7292             }
7293           }
7294         }
7295         board[toY][toX] = EmptySquare;
7296       }
7297     }
7298     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7299         /* [HGM] Shogi promotions */
7300         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7301     }
7302
7303     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7304                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7305         // [HGM] superchess: take promotion piece out of holdings
7306         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7307         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7308             if(!--board[k][BOARD_WIDTH-2])
7309                 board[k][BOARD_WIDTH-1] = EmptySquare;
7310         } else {
7311             if(!--board[BOARD_HEIGHT-1-k][1])
7312                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7313         }
7314     }
7315
7316 }
7317
7318 /* Updates forwardMostMove */
7319 void
7320 MakeMove(fromX, fromY, toX, toY, promoChar)
7321      int fromX, fromY, toX, toY;
7322      int promoChar;
7323 {
7324 //    forwardMostMove++; // [HGM] bare: moved downstream
7325
7326     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7327         int timeLeft; static int lastLoadFlag=0; int king, piece;
7328         piece = boards[forwardMostMove][fromY][fromX];
7329         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7330         if(gameInfo.variant == VariantKnightmate)
7331             king += (int) WhiteUnicorn - (int) WhiteKing;
7332         if(forwardMostMove == 0) {
7333             if(blackPlaysFirst) 
7334                 fprintf(serverMoves, "%s;", second.tidy);
7335             fprintf(serverMoves, "%s;", first.tidy);
7336             if(!blackPlaysFirst) 
7337                 fprintf(serverMoves, "%s;", second.tidy);
7338         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7339         lastLoadFlag = loadFlag;
7340         // print base move
7341         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7342         // print castling suffix
7343         if( toY == fromY && piece == king ) {
7344             if(toX-fromX > 1)
7345                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7346             if(fromX-toX >1)
7347                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7348         }
7349         // e.p. suffix
7350         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7351              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7352              boards[forwardMostMove][toY][toX] == EmptySquare
7353              && fromX != toX )
7354                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7355         // promotion suffix
7356         if(promoChar != NULLCHAR)
7357                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7358         if(!loadFlag) {
7359             fprintf(serverMoves, "/%d/%d",
7360                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7361             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7362             else                      timeLeft = blackTimeRemaining/1000;
7363             fprintf(serverMoves, "/%d", timeLeft);
7364         }
7365         fflush(serverMoves);
7366     }
7367
7368     if (forwardMostMove+1 >= MAX_MOVES) {
7369       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7370                         0, 1);
7371       return;
7372     }
7373     if (commentList[forwardMostMove+1] != NULL) {
7374         free(commentList[forwardMostMove+1]);
7375         commentList[forwardMostMove+1] = NULL;
7376     }
7377     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7378     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7379     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7380                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7381     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7382     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7383     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7384     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7385     gameInfo.result = GameUnfinished;
7386     if (gameInfo.resultDetails != NULL) {
7387         free(gameInfo.resultDetails);
7388         gameInfo.resultDetails = NULL;
7389     }
7390     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7391                               moveList[forwardMostMove - 1]);
7392     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7393                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7394                              fromY, fromX, toY, toX, promoChar,
7395                              parseList[forwardMostMove - 1]);
7396     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7397                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7398                             castlingRights[forwardMostMove]) ) {
7399       case MT_NONE:
7400       case MT_STALEMATE:
7401       default:
7402         break;
7403       case MT_CHECK:
7404         if(gameInfo.variant != VariantShogi)
7405             strcat(parseList[forwardMostMove - 1], "+");
7406         break;
7407       case MT_CHECKMATE:
7408       case MT_STAINMATE:
7409         strcat(parseList[forwardMostMove - 1], "#");
7410         break;
7411     }
7412     if (appData.debugMode) {
7413         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7414     }
7415
7416 }
7417
7418 /* Updates currentMove if not pausing */
7419 void
7420 ShowMove(fromX, fromY, toX, toY)
7421 {
7422     int instant = (gameMode == PlayFromGameFile) ?
7423         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7424     if(appData.noGUI) return;
7425     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7426         if (!instant) {
7427             if (forwardMostMove == currentMove + 1) {
7428                 AnimateMove(boards[forwardMostMove - 1],
7429                             fromX, fromY, toX, toY);
7430             }
7431             if (appData.highlightLastMove) {
7432                 SetHighlights(fromX, fromY, toX, toY);
7433             }
7434         }
7435         currentMove = forwardMostMove;
7436     }
7437
7438     if (instant) return;
7439
7440     DisplayMove(currentMove - 1);
7441     DrawPosition(FALSE, boards[currentMove]);
7442     DisplayBothClocks();
7443     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7444 }
7445
7446 void SendEgtPath(ChessProgramState *cps)
7447 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7448         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7449
7450         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7451
7452         while(*p) {
7453             char c, *q = name+1, *r, *s;
7454
7455             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7456             while(*p && *p != ',') *q++ = *p++;
7457             *q++ = ':'; *q = 0;
7458             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7459                 strcmp(name, ",nalimov:") == 0 ) {
7460                 // take nalimov path from the menu-changeable option first, if it is defined
7461                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7462                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7463             } else
7464             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7465                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7466                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7467                 s = r = StrStr(s, ":") + 1; // beginning of path info
7468                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7469                 c = *r; *r = 0;             // temporarily null-terminate path info
7470                     *--q = 0;               // strip of trailig ':' from name
7471                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7472                 *r = c;
7473                 SendToProgram(buf,cps);     // send egtbpath command for this format
7474             }
7475             if(*p == ',') p++; // read away comma to position for next format name
7476         }
7477 }
7478
7479 void
7480 InitChessProgram(cps, setup)
7481      ChessProgramState *cps;
7482      int setup; /* [HGM] needed to setup FRC opening position */
7483 {
7484     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7485     if (appData.noChessProgram) return;
7486     hintRequested = FALSE;
7487     bookRequested = FALSE;
7488
7489     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7490     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7491     if(cps->memSize) { /* [HGM] memory */
7492         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7493         SendToProgram(buf, cps);
7494     }
7495     SendEgtPath(cps); /* [HGM] EGT */
7496     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7497         sprintf(buf, "cores %d\n", appData.smpCores);
7498         SendToProgram(buf, cps);
7499     }
7500
7501     SendToProgram(cps->initString, cps);
7502     if (gameInfo.variant != VariantNormal &&
7503         gameInfo.variant != VariantLoadable
7504         /* [HGM] also send variant if board size non-standard */
7505         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7506                                             ) {
7507       char *v = VariantName(gameInfo.variant);
7508       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7509         /* [HGM] in protocol 1 we have to assume all variants valid */
7510         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7511         DisplayFatalError(buf, 0, 1);
7512         return;
7513       }
7514
7515       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7516       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7517       if( gameInfo.variant == VariantXiangqi )
7518            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7519       if( gameInfo.variant == VariantShogi )
7520            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7521       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7522            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7523       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7524                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7525            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7526       if( gameInfo.variant == VariantCourier )
7527            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7528       if( gameInfo.variant == VariantSuper )
7529            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7530       if( gameInfo.variant == VariantGreat )
7531            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7532
7533       if(overruled) {
7534            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7535                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7536            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7537            if(StrStr(cps->variants, b) == NULL) { 
7538                // specific sized variant not known, check if general sizing allowed
7539                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7540                    if(StrStr(cps->variants, "boardsize") == NULL) {
7541                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7542                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7543                        DisplayFatalError(buf, 0, 1);
7544                        return;
7545                    }
7546                    /* [HGM] here we really should compare with the maximum supported board size */
7547                }
7548            }
7549       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7550       sprintf(buf, "variant %s\n", b);
7551       SendToProgram(buf, cps);
7552     }
7553     currentlyInitializedVariant = gameInfo.variant;
7554
7555     /* [HGM] send opening position in FRC to first engine */
7556     if(setup) {
7557           SendToProgram("force\n", cps);
7558           SendBoard(cps, 0);
7559           /* engine is now in force mode! Set flag to wake it up after first move. */
7560           setboardSpoiledMachineBlack = 1;
7561     }
7562
7563     if (cps->sendICS) {
7564       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7565       SendToProgram(buf, cps);
7566     }
7567     cps->maybeThinking = FALSE;
7568     cps->offeredDraw = 0;
7569     if (!appData.icsActive) {
7570         SendTimeControl(cps, movesPerSession, timeControl,
7571                         timeIncrement, appData.searchDepth,
7572                         searchTime);
7573     }
7574     if (appData.showThinking 
7575         // [HGM] thinking: four options require thinking output to be sent
7576         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7577                                 ) {
7578         SendToProgram("post\n", cps);
7579     }
7580     SendToProgram("hard\n", cps);
7581     if (!appData.ponderNextMove) {
7582         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7583            it without being sure what state we are in first.  "hard"
7584            is not a toggle, so that one is OK.
7585          */
7586         SendToProgram("easy\n", cps);
7587     }
7588     if (cps->usePing) {
7589       sprintf(buf, "ping %d\n", ++cps->lastPing);
7590       SendToProgram(buf, cps);
7591     }
7592     cps->initDone = TRUE;
7593 }   
7594
7595
7596 void
7597 StartChessProgram(cps)
7598      ChessProgramState *cps;
7599 {
7600     char buf[MSG_SIZ];
7601     int err;
7602
7603     if (appData.noChessProgram) return;
7604     cps->initDone = FALSE;
7605
7606     if (strcmp(cps->host, "localhost") == 0) {
7607         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7608     } else if (*appData.remoteShell == NULLCHAR) {
7609         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7610     } else {
7611         if (*appData.remoteUser == NULLCHAR) {
7612           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7613                     cps->program);
7614         } else {
7615           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7616                     cps->host, appData.remoteUser, cps->program);
7617         }
7618         err = StartChildProcess(buf, "", &cps->pr);
7619     }
7620     
7621     if (err != 0) {
7622         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7623         DisplayFatalError(buf, err, 1);
7624         cps->pr = NoProc;
7625         cps->isr = NULL;
7626         return;
7627     }
7628     
7629     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7630     if (cps->protocolVersion > 1) {
7631       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7632       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7633       cps->comboCnt = 0;  //                and values of combo boxes
7634       SendToProgram(buf, cps);
7635     } else {
7636       SendToProgram("xboard\n", cps);
7637     }
7638 }
7639
7640
7641 void
7642 TwoMachinesEventIfReady P((void))
7643 {
7644   if (first.lastPing != first.lastPong) {
7645     DisplayMessage("", _("Waiting for first chess program"));
7646     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7647     return;
7648   }
7649   if (second.lastPing != second.lastPong) {
7650     DisplayMessage("", _("Waiting for second chess program"));
7651     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7652     return;
7653   }
7654   ThawUI();
7655   TwoMachinesEvent();
7656 }
7657
7658 void
7659 NextMatchGame P((void))
7660 {
7661     int index; /* [HGM] autoinc: step lod index during match */
7662     Reset(FALSE, TRUE);
7663     if (*appData.loadGameFile != NULLCHAR) {
7664         index = appData.loadGameIndex;
7665         if(index < 0) { // [HGM] autoinc
7666             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7667             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7668         } 
7669         LoadGameFromFile(appData.loadGameFile,
7670                          index,
7671                          appData.loadGameFile, FALSE);
7672     } else if (*appData.loadPositionFile != NULLCHAR) {
7673         index = appData.loadPositionIndex;
7674         if(index < 0) { // [HGM] autoinc
7675             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7676             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7677         } 
7678         LoadPositionFromFile(appData.loadPositionFile,
7679                              index,
7680                              appData.loadPositionFile);
7681     }
7682     TwoMachinesEventIfReady();
7683 }
7684
7685 void UserAdjudicationEvent( int result )
7686 {
7687     ChessMove gameResult = GameIsDrawn;
7688
7689     if( result > 0 ) {
7690         gameResult = WhiteWins;
7691     }
7692     else if( result < 0 ) {
7693         gameResult = BlackWins;
7694     }
7695
7696     if( gameMode == TwoMachinesPlay ) {
7697         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7698     }
7699 }
7700
7701
7702 // [HGM] save: calculate checksum of game to make games easily identifiable
7703 int StringCheckSum(char *s)
7704 {
7705         int i = 0;
7706         if(s==NULL) return 0;
7707         while(*s) i = i*259 + *s++;
7708         return i;
7709 }
7710
7711 int GameCheckSum()
7712 {
7713         int i, sum=0;
7714         for(i=backwardMostMove; i<forwardMostMove; i++) {
7715                 sum += pvInfoList[i].depth;
7716                 sum += StringCheckSum(parseList[i]);
7717                 sum += StringCheckSum(commentList[i]);
7718                 sum *= 261;
7719         }
7720         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7721         return sum + StringCheckSum(commentList[i]);
7722 } // end of save patch
7723
7724 void
7725 GameEnds(result, resultDetails, whosays)
7726      ChessMove result;
7727      char *resultDetails;
7728      int whosays;
7729 {
7730     GameMode nextGameMode;
7731     int isIcsGame;
7732     char buf[MSG_SIZ];
7733
7734     if(endingGame) return; /* [HGM] crash: forbid recursion */
7735     endingGame = 1;
7736
7737     if (appData.debugMode) {
7738       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7739               result, resultDetails ? resultDetails : "(null)", whosays);
7740     }
7741
7742     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7743         /* If we are playing on ICS, the server decides when the
7744            game is over, but the engine can offer to draw, claim 
7745            a draw, or resign. 
7746          */
7747 #if ZIPPY
7748         if (appData.zippyPlay && first.initDone) {
7749             if (result == GameIsDrawn) {
7750                 /* In case draw still needs to be claimed */
7751                 SendToICS(ics_prefix);
7752                 SendToICS("draw\n");
7753             } else if (StrCaseStr(resultDetails, "resign")) {
7754                 SendToICS(ics_prefix);
7755                 SendToICS("resign\n");
7756             }
7757         }
7758 #endif
7759         endingGame = 0; /* [HGM] crash */
7760         return;
7761     }
7762
7763     /* If we're loading the game from a file, stop */
7764     if (whosays == GE_FILE) {
7765       (void) StopLoadGameTimer();
7766       gameFileFP = NULL;
7767     }
7768
7769     /* Cancel draw offers */
7770     first.offeredDraw = second.offeredDraw = 0;
7771
7772     /* If this is an ICS game, only ICS can really say it's done;
7773        if not, anyone can. */
7774     isIcsGame = (gameMode == IcsPlayingWhite || 
7775                  gameMode == IcsPlayingBlack || 
7776                  gameMode == IcsObserving    || 
7777                  gameMode == IcsExamining);
7778
7779     if (!isIcsGame || whosays == GE_ICS) {
7780         /* OK -- not an ICS game, or ICS said it was done */
7781         StopClocks();
7782         if (!isIcsGame && !appData.noChessProgram) 
7783           SetUserThinkingEnables();
7784     
7785         /* [HGM] if a machine claims the game end we verify this claim */
7786         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7787             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7788                 char claimer;
7789                 ChessMove trueResult = (ChessMove) -1;
7790
7791                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7792                                             first.twoMachinesColor[0] :
7793                                             second.twoMachinesColor[0] ;
7794
7795                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7796                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7797                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7798                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7799                 } else
7800                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7801                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7802                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7803                 } else
7804                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7805                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7806                 }
7807
7808                 // now verify win claims, but not in drop games, as we don't understand those yet
7809                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7810                                                  || gameInfo.variant == VariantGreat) &&
7811                     (result == WhiteWins && claimer == 'w' ||
7812                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7813                       if (appData.debugMode) {
7814                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7815                                 result, epStatus[forwardMostMove], forwardMostMove);
7816                       }
7817                       if(result != trueResult) {
7818                               sprintf(buf, "False win claim: '%s'", resultDetails);
7819                               result = claimer == 'w' ? BlackWins : WhiteWins;
7820                               resultDetails = buf;
7821                       }
7822                 } else
7823                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7824                     && (forwardMostMove <= backwardMostMove ||
7825                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7826                         (claimer=='b')==(forwardMostMove&1))
7827                                                                                   ) {
7828                       /* [HGM] verify: draws that were not flagged are false claims */
7829                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7830                       result = claimer == 'w' ? BlackWins : WhiteWins;
7831                       resultDetails = buf;
7832                 }
7833                 /* (Claiming a loss is accepted no questions asked!) */
7834             }
7835             /* [HGM] bare: don't allow bare King to win */
7836             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7837                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7838                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7839                && result != GameIsDrawn)
7840             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7841                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7842                         int p = (int)boards[forwardMostMove][i][j] - color;
7843                         if(p >= 0 && p <= (int)WhiteKing) k++;
7844                 }
7845                 if (appData.debugMode) {
7846                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7847                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7848                 }
7849                 if(k <= 1) {
7850                         result = GameIsDrawn;
7851                         sprintf(buf, "%s but bare king", resultDetails);
7852                         resultDetails = buf;
7853                 }
7854             }
7855         }
7856
7857
7858         if(serverMoves != NULL && !loadFlag) { char c = '=';
7859             if(result==WhiteWins) c = '+';
7860             if(result==BlackWins) c = '-';
7861             if(resultDetails != NULL)
7862                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7863         }
7864         if (resultDetails != NULL) {
7865             gameInfo.result = result;
7866             gameInfo.resultDetails = StrSave(resultDetails);
7867
7868             /* display last move only if game was not loaded from file */
7869             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7870                 DisplayMove(currentMove - 1);
7871     
7872             if (forwardMostMove != 0) {
7873                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7874                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7875                                                                 ) {
7876                     if (*appData.saveGameFile != NULLCHAR) {
7877                         SaveGameToFile(appData.saveGameFile, TRUE);
7878                     } else if (appData.autoSaveGames) {
7879                         AutoSaveGame();
7880                     }
7881                     if (*appData.savePositionFile != NULLCHAR) {
7882                         SavePositionToFile(appData.savePositionFile);
7883                     }
7884                 }
7885             }
7886
7887             /* Tell program how game ended in case it is learning */
7888             /* [HGM] Moved this to after saving the PGN, just in case */
7889             /* engine died and we got here through time loss. In that */
7890             /* case we will get a fatal error writing the pipe, which */
7891             /* would otherwise lose us the PGN.                       */
7892             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7893             /* output during GameEnds should never be fatal anymore   */
7894             if (gameMode == MachinePlaysWhite ||
7895                 gameMode == MachinePlaysBlack ||
7896                 gameMode == TwoMachinesPlay ||
7897                 gameMode == IcsPlayingWhite ||
7898                 gameMode == IcsPlayingBlack ||
7899                 gameMode == BeginningOfGame) {
7900                 char buf[MSG_SIZ];
7901                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7902                         resultDetails);
7903                 if (first.pr != NoProc) {
7904                     SendToProgram(buf, &first);
7905                 }
7906                 if (second.pr != NoProc &&
7907                     gameMode == TwoMachinesPlay) {
7908                     SendToProgram(buf, &second);
7909                 }
7910             }
7911         }
7912
7913         if (appData.icsActive) {
7914             if (appData.quietPlay &&
7915                 (gameMode == IcsPlayingWhite ||
7916                  gameMode == IcsPlayingBlack)) {
7917                 SendToICS(ics_prefix);
7918                 SendToICS("set shout 1\n");
7919             }
7920             nextGameMode = IcsIdle;
7921             ics_user_moved = FALSE;
7922             /* clean up premove.  It's ugly when the game has ended and the
7923              * premove highlights are still on the board.
7924              */
7925             if (gotPremove) {
7926               gotPremove = FALSE;
7927               ClearPremoveHighlights();
7928               DrawPosition(FALSE, boards[currentMove]);
7929             }
7930             if (whosays == GE_ICS) {
7931                 switch (result) {
7932                 case WhiteWins:
7933                     if (gameMode == IcsPlayingWhite)
7934                         PlayIcsWinSound();
7935                     else if(gameMode == IcsPlayingBlack)
7936                         PlayIcsLossSound();
7937                     break;
7938                 case BlackWins:
7939                     if (gameMode == IcsPlayingBlack)
7940                         PlayIcsWinSound();
7941                     else if(gameMode == IcsPlayingWhite)
7942                         PlayIcsLossSound();
7943                     break;
7944                 case GameIsDrawn:
7945                     PlayIcsDrawSound();
7946                     break;
7947                 default:
7948                     PlayIcsUnfinishedSound();
7949                 }
7950             }
7951         } else if (gameMode == EditGame ||
7952                    gameMode == PlayFromGameFile || 
7953                    gameMode == AnalyzeMode || 
7954                    gameMode == AnalyzeFile) {
7955             nextGameMode = gameMode;
7956         } else {
7957             nextGameMode = EndOfGame;
7958         }
7959         pausing = FALSE;
7960         ModeHighlight();
7961     } else {
7962         nextGameMode = gameMode;
7963     }
7964
7965     if (appData.noChessProgram) {
7966         gameMode = nextGameMode;
7967         ModeHighlight();
7968         endingGame = 0; /* [HGM] crash */
7969         return;
7970     }
7971
7972     if (first.reuse) {
7973         /* Put first chess program into idle state */
7974         if (first.pr != NoProc &&
7975             (gameMode == MachinePlaysWhite ||
7976              gameMode == MachinePlaysBlack ||
7977              gameMode == TwoMachinesPlay ||
7978              gameMode == IcsPlayingWhite ||
7979              gameMode == IcsPlayingBlack ||
7980              gameMode == BeginningOfGame)) {
7981             SendToProgram("force\n", &first);
7982             if (first.usePing) {
7983               char buf[MSG_SIZ];
7984               sprintf(buf, "ping %d\n", ++first.lastPing);
7985               SendToProgram(buf, &first);
7986             }
7987         }
7988     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7989         /* Kill off first chess program */
7990         if (first.isr != NULL)
7991           RemoveInputSource(first.isr);
7992         first.isr = NULL;
7993     
7994         if (first.pr != NoProc) {
7995             ExitAnalyzeMode();
7996             DoSleep( appData.delayBeforeQuit );
7997             SendToProgram("quit\n", &first);
7998             DoSleep( appData.delayAfterQuit );
7999             DestroyChildProcess(first.pr, first.useSigterm);
8000         }
8001         first.pr = NoProc;
8002     }
8003     if (second.reuse) {
8004         /* Put second chess program into idle state */
8005         if (second.pr != NoProc &&
8006             gameMode == TwoMachinesPlay) {
8007             SendToProgram("force\n", &second);
8008             if (second.usePing) {
8009               char buf[MSG_SIZ];
8010               sprintf(buf, "ping %d\n", ++second.lastPing);
8011               SendToProgram(buf, &second);
8012             }
8013         }
8014     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8015         /* Kill off second chess program */
8016         if (second.isr != NULL)
8017           RemoveInputSource(second.isr);
8018         second.isr = NULL;
8019     
8020         if (second.pr != NoProc) {
8021             DoSleep( appData.delayBeforeQuit );
8022             SendToProgram("quit\n", &second);
8023             DoSleep( appData.delayAfterQuit );
8024             DestroyChildProcess(second.pr, second.useSigterm);
8025         }
8026         second.pr = NoProc;
8027     }
8028
8029     if (matchMode && gameMode == TwoMachinesPlay) {
8030         switch (result) {
8031         case WhiteWins:
8032           if (first.twoMachinesColor[0] == 'w') {
8033             first.matchWins++;
8034           } else {
8035             second.matchWins++;
8036           }
8037           break;
8038         case BlackWins:
8039           if (first.twoMachinesColor[0] == 'b') {
8040             first.matchWins++;
8041           } else {
8042             second.matchWins++;
8043           }
8044           break;
8045         default:
8046           break;
8047         }
8048         if (matchGame < appData.matchGames) {
8049             char *tmp;
8050             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8051                 tmp = first.twoMachinesColor;
8052                 first.twoMachinesColor = second.twoMachinesColor;
8053                 second.twoMachinesColor = tmp;
8054             }
8055             gameMode = nextGameMode;
8056             matchGame++;
8057             if(appData.matchPause>10000 || appData.matchPause<10)
8058                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8059             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8060             endingGame = 0; /* [HGM] crash */
8061             return;
8062         } else {
8063             char buf[MSG_SIZ];
8064             gameMode = nextGameMode;
8065             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8066                     first.tidy, second.tidy,
8067                     first.matchWins, second.matchWins,
8068                     appData.matchGames - (first.matchWins + second.matchWins));
8069             DisplayFatalError(buf, 0, 0);
8070         }
8071     }
8072     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8073         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8074       ExitAnalyzeMode();
8075     gameMode = nextGameMode;
8076     ModeHighlight();
8077     endingGame = 0;  /* [HGM] crash */
8078 }
8079
8080 /* Assumes program was just initialized (initString sent).
8081    Leaves program in force mode. */
8082 void
8083 FeedMovesToProgram(cps, upto) 
8084      ChessProgramState *cps;
8085      int upto;
8086 {
8087     int i;
8088     
8089     if (appData.debugMode)
8090       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8091               startedFromSetupPosition ? "position and " : "",
8092               backwardMostMove, upto, cps->which);
8093     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8094         // [HGM] variantswitch: make engine aware of new variant
8095         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8096                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8097         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8098         SendToProgram(buf, cps);
8099         currentlyInitializedVariant = gameInfo.variant;
8100     }
8101     SendToProgram("force\n", cps);
8102     if (startedFromSetupPosition) {
8103         SendBoard(cps, backwardMostMove);
8104     if (appData.debugMode) {
8105         fprintf(debugFP, "feedMoves\n");
8106     }
8107     }
8108     for (i = backwardMostMove; i < upto; i++) {
8109         SendMoveToProgram(i, cps);
8110     }
8111 }
8112
8113
8114 void
8115 ResurrectChessProgram()
8116 {
8117      /* The chess program may have exited.
8118         If so, restart it and feed it all the moves made so far. */
8119
8120     if (appData.noChessProgram || first.pr != NoProc) return;
8121     
8122     StartChessProgram(&first);
8123     InitChessProgram(&first, FALSE);
8124     FeedMovesToProgram(&first, currentMove);
8125
8126     if (!first.sendTime) {
8127         /* can't tell gnuchess what its clock should read,
8128            so we bow to its notion. */
8129         ResetClocks();
8130         timeRemaining[0][currentMove] = whiteTimeRemaining;
8131         timeRemaining[1][currentMove] = blackTimeRemaining;
8132     }
8133
8134     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8135                 appData.icsEngineAnalyze) && first.analysisSupport) {
8136       SendToProgram("analyze\n", &first);
8137       first.analyzing = TRUE;
8138     }
8139 }
8140
8141 /*
8142  * Button procedures
8143  */
8144 void
8145 Reset(redraw, init)
8146      int redraw, init;
8147 {
8148     int i;
8149
8150     if (appData.debugMode) {
8151         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8152                 redraw, init, gameMode);
8153     }
8154     pausing = pauseExamInvalid = FALSE;
8155     startedFromSetupPosition = blackPlaysFirst = FALSE;
8156     firstMove = TRUE;
8157     whiteFlag = blackFlag = FALSE;
8158     userOfferedDraw = FALSE;
8159     hintRequested = bookRequested = FALSE;
8160     first.maybeThinking = FALSE;
8161     second.maybeThinking = FALSE;
8162     first.bookSuspend = FALSE; // [HGM] book
8163     second.bookSuspend = FALSE;
8164     thinkOutput[0] = NULLCHAR;
8165     lastHint[0] = NULLCHAR;
8166     ClearGameInfo(&gameInfo);
8167     gameInfo.variant = StringToVariant(appData.variant);
8168     ics_user_moved = ics_clock_paused = FALSE;
8169     ics_getting_history = H_FALSE;
8170     ics_gamenum = -1;
8171     white_holding[0] = black_holding[0] = NULLCHAR;
8172     ClearProgramStats();
8173     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8174     
8175     ResetFrontEnd();
8176     ClearHighlights();
8177     flipView = appData.flipView;
8178     ClearPremoveHighlights();
8179     gotPremove = FALSE;
8180     alarmSounded = FALSE;
8181
8182     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8183     if(appData.serverMovesName != NULL) {
8184         /* [HGM] prepare to make moves file for broadcasting */
8185         clock_t t = clock();
8186         if(serverMoves != NULL) fclose(serverMoves);
8187         serverMoves = fopen(appData.serverMovesName, "r");
8188         if(serverMoves != NULL) {
8189             fclose(serverMoves);
8190             /* delay 15 sec before overwriting, so all clients can see end */
8191             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8192         }
8193         serverMoves = fopen(appData.serverMovesName, "w");
8194     }
8195
8196     ExitAnalyzeMode();
8197     gameMode = BeginningOfGame;
8198     ModeHighlight();
8199     if(appData.icsActive) gameInfo.variant = VariantNormal;
8200     currentMove = forwardMostMove = backwardMostMove = 0;
8201     InitPosition(redraw);
8202     for (i = 0; i < MAX_MOVES; i++) {
8203         if (commentList[i] != NULL) {
8204             free(commentList[i]);
8205             commentList[i] = NULL;
8206         }
8207     }
8208     ResetClocks();
8209     timeRemaining[0][0] = whiteTimeRemaining;
8210     timeRemaining[1][0] = blackTimeRemaining;
8211     if (first.pr == NULL) {
8212         StartChessProgram(&first);
8213     }
8214     if (init) {
8215             InitChessProgram(&first, startedFromSetupPosition);
8216     }
8217     DisplayTitle("");
8218     DisplayMessage("", "");
8219     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8220     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8221 }
8222
8223 void
8224 AutoPlayGameLoop()
8225 {
8226     for (;;) {
8227         if (!AutoPlayOneMove())
8228           return;
8229         if (matchMode || appData.timeDelay == 0)
8230           continue;
8231         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8232           return;
8233         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8234         break;
8235     }
8236 }
8237
8238
8239 int
8240 AutoPlayOneMove()
8241 {
8242     int fromX, fromY, toX, toY;
8243
8244     if (appData.debugMode) {
8245       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8246     }
8247
8248     if (gameMode != PlayFromGameFile)
8249       return FALSE;
8250
8251     if (currentMove >= forwardMostMove) {
8252       gameMode = EditGame;
8253       ModeHighlight();
8254
8255       /* [AS] Clear current move marker at the end of a game */
8256       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8257
8258       return FALSE;
8259     }
8260     
8261     toX = moveList[currentMove][2] - AAA;
8262     toY = moveList[currentMove][3] - ONE;
8263
8264     if (moveList[currentMove][1] == '@') {
8265         if (appData.highlightLastMove) {
8266             SetHighlights(-1, -1, toX, toY);
8267         }
8268     } else {
8269         fromX = moveList[currentMove][0] - AAA;
8270         fromY = moveList[currentMove][1] - ONE;
8271
8272         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8273
8274         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8275
8276         if (appData.highlightLastMove) {
8277             SetHighlights(fromX, fromY, toX, toY);
8278         }
8279     }
8280     DisplayMove(currentMove);
8281     SendMoveToProgram(currentMove++, &first);
8282     DisplayBothClocks();
8283     DrawPosition(FALSE, boards[currentMove]);
8284     // [HGM] PV info: always display, routine tests if empty
8285     DisplayComment(currentMove - 1, commentList[currentMove]);
8286     return TRUE;
8287 }
8288
8289
8290 int
8291 LoadGameOneMove(readAhead)
8292      ChessMove readAhead;
8293 {
8294     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8295     char promoChar = NULLCHAR;
8296     ChessMove moveType;
8297     char move[MSG_SIZ];
8298     char *p, *q;
8299     
8300     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8301         gameMode != AnalyzeMode && gameMode != Training) {
8302         gameFileFP = NULL;
8303         return FALSE;
8304     }
8305     
8306     yyboardindex = forwardMostMove;
8307     if (readAhead != (ChessMove)0) {
8308       moveType = readAhead;
8309     } else {
8310       if (gameFileFP == NULL)
8311           return FALSE;
8312       moveType = (ChessMove) yylex();
8313     }
8314     
8315     done = FALSE;
8316     switch (moveType) {
8317       case Comment:
8318         if (appData.debugMode) 
8319           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8320         p = yy_text;
8321         if (*p == '{' || *p == '[' || *p == '(') {
8322             p[strlen(p) - 1] = NULLCHAR;
8323             p++;
8324         }
8325
8326         /* append the comment but don't display it */
8327         while (*p == '\n') p++;
8328         AppendComment(currentMove, p);
8329         return TRUE;
8330
8331       case WhiteCapturesEnPassant:
8332       case BlackCapturesEnPassant:
8333       case WhitePromotionChancellor:
8334       case BlackPromotionChancellor:
8335       case WhitePromotionArchbishop:
8336       case BlackPromotionArchbishop:
8337       case WhitePromotionCentaur:
8338       case BlackPromotionCentaur:
8339       case WhitePromotionQueen:
8340       case BlackPromotionQueen:
8341       case WhitePromotionRook:
8342       case BlackPromotionRook:
8343       case WhitePromotionBishop:
8344       case BlackPromotionBishop:
8345       case WhitePromotionKnight:
8346       case BlackPromotionKnight:
8347       case WhitePromotionKing:
8348       case BlackPromotionKing:
8349       case NormalMove:
8350       case WhiteKingSideCastle:
8351       case WhiteQueenSideCastle:
8352       case BlackKingSideCastle:
8353       case BlackQueenSideCastle:
8354       case WhiteKingSideCastleWild:
8355       case WhiteQueenSideCastleWild:
8356       case BlackKingSideCastleWild:
8357       case BlackQueenSideCastleWild:
8358       /* PUSH Fabien */
8359       case WhiteHSideCastleFR:
8360       case WhiteASideCastleFR:
8361       case BlackHSideCastleFR:
8362       case BlackASideCastleFR:
8363       /* POP Fabien */
8364         if (appData.debugMode)
8365           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8366         fromX = currentMoveString[0] - AAA;
8367         fromY = currentMoveString[1] - ONE;
8368         toX = currentMoveString[2] - AAA;
8369         toY = currentMoveString[3] - ONE;
8370         promoChar = currentMoveString[4];
8371         break;
8372
8373       case WhiteDrop:
8374       case BlackDrop:
8375         if (appData.debugMode)
8376           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8377         fromX = moveType == WhiteDrop ?
8378           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8379         (int) CharToPiece(ToLower(currentMoveString[0]));
8380         fromY = DROP_RANK;
8381         toX = currentMoveString[2] - AAA;
8382         toY = currentMoveString[3] - ONE;
8383         break;
8384
8385       case WhiteWins:
8386       case BlackWins:
8387       case GameIsDrawn:
8388       case GameUnfinished:
8389         if (appData.debugMode)
8390           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8391         p = strchr(yy_text, '{');
8392         if (p == NULL) p = strchr(yy_text, '(');
8393         if (p == NULL) {
8394             p = yy_text;
8395             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8396         } else {
8397             q = strchr(p, *p == '{' ? '}' : ')');
8398             if (q != NULL) *q = NULLCHAR;
8399             p++;
8400         }
8401         GameEnds(moveType, p, GE_FILE);
8402         done = TRUE;
8403         if (cmailMsgLoaded) {
8404             ClearHighlights();
8405             flipView = WhiteOnMove(currentMove);
8406             if (moveType == GameUnfinished) flipView = !flipView;
8407             if (appData.debugMode)
8408               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8409         }
8410         break;
8411
8412       case (ChessMove) 0:       /* end of file */
8413         if (appData.debugMode)
8414           fprintf(debugFP, "Parser hit end of file\n");
8415         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8416                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8417           case MT_NONE:
8418           case MT_CHECK:
8419             break;
8420           case MT_CHECKMATE:
8421           case MT_STAINMATE:
8422             if (WhiteOnMove(currentMove)) {
8423                 GameEnds(BlackWins, "Black mates", GE_FILE);
8424             } else {
8425                 GameEnds(WhiteWins, "White mates", GE_FILE);
8426             }
8427             break;
8428           case MT_STALEMATE:
8429             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8430             break;
8431         }
8432         done = TRUE;
8433         break;
8434
8435       case MoveNumberOne:
8436         if (lastLoadGameStart == GNUChessGame) {
8437             /* GNUChessGames have numbers, but they aren't move numbers */
8438             if (appData.debugMode)
8439               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8440                       yy_text, (int) moveType);
8441             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8442         }
8443         /* else fall thru */
8444
8445       case XBoardGame:
8446       case GNUChessGame:
8447       case PGNTag:
8448         /* Reached start of next game in file */
8449         if (appData.debugMode)
8450           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8451         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8452                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8453           case MT_NONE:
8454           case MT_CHECK:
8455             break;
8456           case MT_CHECKMATE:
8457           case MT_STAINMATE:
8458             if (WhiteOnMove(currentMove)) {
8459                 GameEnds(BlackWins, "Black mates", GE_FILE);
8460             } else {
8461                 GameEnds(WhiteWins, "White mates", GE_FILE);
8462             }
8463             break;
8464           case MT_STALEMATE:
8465             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8466             break;
8467         }
8468         done = TRUE;
8469         break;
8470
8471       case PositionDiagram:     /* should not happen; ignore */
8472       case ElapsedTime:         /* ignore */
8473       case NAG:                 /* ignore */
8474         if (appData.debugMode)
8475           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8476                   yy_text, (int) moveType);
8477         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8478
8479       case IllegalMove:
8480         if (appData.testLegality) {
8481             if (appData.debugMode)
8482               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8483             sprintf(move, _("Illegal move: %d.%s%s"),
8484                     (forwardMostMove / 2) + 1,
8485                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8486             DisplayError(move, 0);
8487             done = TRUE;
8488         } else {
8489             if (appData.debugMode)
8490               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8491                       yy_text, currentMoveString);
8492             fromX = currentMoveString[0] - AAA;
8493             fromY = currentMoveString[1] - ONE;
8494             toX = currentMoveString[2] - AAA;
8495             toY = currentMoveString[3] - ONE;
8496             promoChar = currentMoveString[4];
8497         }
8498         break;
8499
8500       case AmbiguousMove:
8501         if (appData.debugMode)
8502           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8503         sprintf(move, _("Ambiguous move: %d.%s%s"),
8504                 (forwardMostMove / 2) + 1,
8505                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8506         DisplayError(move, 0);
8507         done = TRUE;
8508         break;
8509
8510       default:
8511       case ImpossibleMove:
8512         if (appData.debugMode)
8513           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8514         sprintf(move, _("Illegal move: %d.%s%s"),
8515                 (forwardMostMove / 2) + 1,
8516                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8517         DisplayError(move, 0);
8518         done = TRUE;
8519         break;
8520     }
8521
8522     if (done) {
8523         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8524             DrawPosition(FALSE, boards[currentMove]);
8525             DisplayBothClocks();
8526             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8527               DisplayComment(currentMove - 1, commentList[currentMove]);
8528         }
8529         (void) StopLoadGameTimer();
8530         gameFileFP = NULL;
8531         cmailOldMove = forwardMostMove;
8532         return FALSE;
8533     } else {
8534         /* currentMoveString is set as a side-effect of yylex */
8535         strcat(currentMoveString, "\n");
8536         strcpy(moveList[forwardMostMove], currentMoveString);
8537         
8538         thinkOutput[0] = NULLCHAR;
8539         MakeMove(fromX, fromY, toX, toY, promoChar);
8540         currentMove = forwardMostMove;
8541         return TRUE;
8542     }
8543 }
8544
8545 /* Load the nth game from the given file */
8546 int
8547 LoadGameFromFile(filename, n, title, useList)
8548      char *filename;
8549      int n;
8550      char *title;
8551      /*Boolean*/ int useList;
8552 {
8553     FILE *f;
8554     char buf[MSG_SIZ];
8555
8556     if (strcmp(filename, "-") == 0) {
8557         f = stdin;
8558         title = "stdin";
8559     } else {
8560         f = fopen(filename, "rb");
8561         if (f == NULL) {
8562           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8563             DisplayError(buf, errno);
8564             return FALSE;
8565         }
8566     }
8567     if (fseek(f, 0, 0) == -1) {
8568         /* f is not seekable; probably a pipe */
8569         useList = FALSE;
8570     }
8571     if (useList && n == 0) {
8572         int error = GameListBuild(f);
8573         if (error) {
8574             DisplayError(_("Cannot build game list"), error);
8575         } else if (!ListEmpty(&gameList) &&
8576                    ((ListGame *) gameList.tailPred)->number > 1) {
8577             GameListPopUp(f, title);
8578             return TRUE;
8579         }
8580         GameListDestroy();
8581         n = 1;
8582     }
8583     if (n == 0) n = 1;
8584     return LoadGame(f, n, title, FALSE);
8585 }
8586
8587
8588 void
8589 MakeRegisteredMove()
8590 {
8591     int fromX, fromY, toX, toY;
8592     char promoChar;
8593     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8594         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8595           case CMAIL_MOVE:
8596           case CMAIL_DRAW:
8597             if (appData.debugMode)
8598               fprintf(debugFP, "Restoring %s for game %d\n",
8599                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8600     
8601             thinkOutput[0] = NULLCHAR;
8602             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8603             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8604             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8605             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8606             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8607             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8608             MakeMove(fromX, fromY, toX, toY, promoChar);
8609             ShowMove(fromX, fromY, toX, toY);
8610               
8611             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8612                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8613               case MT_NONE:
8614               case MT_CHECK:
8615                 break;
8616                 
8617               case MT_CHECKMATE:
8618               case MT_STAINMATE:
8619                 if (WhiteOnMove(currentMove)) {
8620                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8621                 } else {
8622                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8623                 }
8624                 break;
8625                 
8626               case MT_STALEMATE:
8627                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8628                 break;
8629             }
8630
8631             break;
8632             
8633           case CMAIL_RESIGN:
8634             if (WhiteOnMove(currentMove)) {
8635                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8636             } else {
8637                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8638             }
8639             break;
8640             
8641           case CMAIL_ACCEPT:
8642             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8643             break;
8644               
8645           default:
8646             break;
8647         }
8648     }
8649
8650     return;
8651 }
8652
8653 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8654 int
8655 CmailLoadGame(f, gameNumber, title, useList)
8656      FILE *f;
8657      int gameNumber;
8658      char *title;
8659      int useList;
8660 {
8661     int retVal;
8662
8663     if (gameNumber > nCmailGames) {
8664         DisplayError(_("No more games in this message"), 0);
8665         return FALSE;
8666     }
8667     if (f == lastLoadGameFP) {
8668         int offset = gameNumber - lastLoadGameNumber;
8669         if (offset == 0) {
8670             cmailMsg[0] = NULLCHAR;
8671             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8672                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8673                 nCmailMovesRegistered--;
8674             }
8675             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8676             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8677                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8678             }
8679         } else {
8680             if (! RegisterMove()) return FALSE;
8681         }
8682     }
8683
8684     retVal = LoadGame(f, gameNumber, title, useList);
8685
8686     /* Make move registered during previous look at this game, if any */
8687     MakeRegisteredMove();
8688
8689     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8690         commentList[currentMove]
8691           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8692         DisplayComment(currentMove - 1, commentList[currentMove]);
8693     }
8694
8695     return retVal;
8696 }
8697
8698 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8699 int
8700 ReloadGame(offset)
8701      int offset;
8702 {
8703     int gameNumber = lastLoadGameNumber + offset;
8704     if (lastLoadGameFP == NULL) {
8705         DisplayError(_("No game has been loaded yet"), 0);
8706         return FALSE;
8707     }
8708     if (gameNumber <= 0) {
8709         DisplayError(_("Can't back up any further"), 0);
8710         return FALSE;
8711     }
8712     if (cmailMsgLoaded) {
8713         return CmailLoadGame(lastLoadGameFP, gameNumber,
8714                              lastLoadGameTitle, lastLoadGameUseList);
8715     } else {
8716         return LoadGame(lastLoadGameFP, gameNumber,
8717                         lastLoadGameTitle, lastLoadGameUseList);
8718     }
8719 }
8720
8721
8722
8723 /* Load the nth game from open file f */
8724 int
8725 LoadGame(f, gameNumber, title, useList)
8726      FILE *f;
8727      int gameNumber;
8728      char *title;
8729      int useList;
8730 {
8731     ChessMove cm;
8732     char buf[MSG_SIZ];
8733     int gn = gameNumber;
8734     ListGame *lg = NULL;
8735     int numPGNTags = 0;
8736     int err;
8737     GameMode oldGameMode;
8738     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8739
8740     if (appData.debugMode) 
8741         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8742
8743     if (gameMode == Training )
8744         SetTrainingModeOff();
8745
8746     oldGameMode = gameMode;
8747     if (gameMode != BeginningOfGame) {
8748       Reset(FALSE, TRUE);
8749     }
8750
8751     gameFileFP = f;
8752     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8753         fclose(lastLoadGameFP);
8754     }
8755
8756     if (useList) {
8757         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8758         
8759         if (lg) {
8760             fseek(f, lg->offset, 0);
8761             GameListHighlight(gameNumber);
8762             gn = 1;
8763         }
8764         else {
8765             DisplayError(_("Game number out of range"), 0);
8766             return FALSE;
8767         }
8768     } else {
8769         GameListDestroy();
8770         if (fseek(f, 0, 0) == -1) {
8771             if (f == lastLoadGameFP ?
8772                 gameNumber == lastLoadGameNumber + 1 :
8773                 gameNumber == 1) {
8774                 gn = 1;
8775             } else {
8776                 DisplayError(_("Can't seek on game file"), 0);
8777                 return FALSE;
8778             }
8779         }
8780     }
8781     lastLoadGameFP = f;
8782     lastLoadGameNumber = gameNumber;
8783     strcpy(lastLoadGameTitle, title);
8784     lastLoadGameUseList = useList;
8785
8786     yynewfile(f);
8787
8788     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8789       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8790                 lg->gameInfo.black);
8791             DisplayTitle(buf);
8792     } else if (*title != NULLCHAR) {
8793         if (gameNumber > 1) {
8794             sprintf(buf, "%s %d", title, gameNumber);
8795             DisplayTitle(buf);
8796         } else {
8797             DisplayTitle(title);
8798         }
8799     }
8800
8801     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8802         gameMode = PlayFromGameFile;
8803         ModeHighlight();
8804     }
8805
8806     currentMove = forwardMostMove = backwardMostMove = 0;
8807     CopyBoard(boards[0], initialPosition);
8808     StopClocks();
8809
8810     /*
8811      * Skip the first gn-1 games in the file.
8812      * Also skip over anything that precedes an identifiable 
8813      * start of game marker, to avoid being confused by 
8814      * garbage at the start of the file.  Currently 
8815      * recognized start of game markers are the move number "1",
8816      * the pattern "gnuchess .* game", the pattern
8817      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8818      * A game that starts with one of the latter two patterns
8819      * will also have a move number 1, possibly
8820      * following a position diagram.
8821      * 5-4-02: Let's try being more lenient and allowing a game to
8822      * start with an unnumbered move.  Does that break anything?
8823      */
8824     cm = lastLoadGameStart = (ChessMove) 0;
8825     while (gn > 0) {
8826         yyboardindex = forwardMostMove;
8827         cm = (ChessMove) yylex();
8828         switch (cm) {
8829           case (ChessMove) 0:
8830             if (cmailMsgLoaded) {
8831                 nCmailGames = CMAIL_MAX_GAMES - gn;
8832             } else {
8833                 Reset(TRUE, TRUE);
8834                 DisplayError(_("Game not found in file"), 0);
8835             }
8836             return FALSE;
8837
8838           case GNUChessGame:
8839           case XBoardGame:
8840             gn--;
8841             lastLoadGameStart = cm;
8842             break;
8843             
8844           case MoveNumberOne:
8845             switch (lastLoadGameStart) {
8846               case GNUChessGame:
8847               case XBoardGame:
8848               case PGNTag:
8849                 break;
8850               case MoveNumberOne:
8851               case (ChessMove) 0:
8852                 gn--;           /* count this game */
8853                 lastLoadGameStart = cm;
8854                 break;
8855               default:
8856                 /* impossible */
8857                 break;
8858             }
8859             break;
8860
8861           case PGNTag:
8862             switch (lastLoadGameStart) {
8863               case GNUChessGame:
8864               case PGNTag:
8865               case MoveNumberOne:
8866               case (ChessMove) 0:
8867                 gn--;           /* count this game */
8868                 lastLoadGameStart = cm;
8869                 break;
8870               case XBoardGame:
8871                 lastLoadGameStart = cm; /* game counted already */
8872                 break;
8873               default:
8874                 /* impossible */
8875                 break;
8876             }
8877             if (gn > 0) {
8878                 do {
8879                     yyboardindex = forwardMostMove;
8880                     cm = (ChessMove) yylex();
8881                 } while (cm == PGNTag || cm == Comment);
8882             }
8883             break;
8884
8885           case WhiteWins:
8886           case BlackWins:
8887           case GameIsDrawn:
8888             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8889                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8890                     != CMAIL_OLD_RESULT) {
8891                     nCmailResults ++ ;
8892                     cmailResult[  CMAIL_MAX_GAMES
8893                                 - gn - 1] = CMAIL_OLD_RESULT;
8894                 }
8895             }
8896             break;
8897
8898           case NormalMove:
8899             /* Only a NormalMove can be at the start of a game
8900              * without a position diagram. */
8901             if (lastLoadGameStart == (ChessMove) 0) {
8902               gn--;
8903               lastLoadGameStart = MoveNumberOne;
8904             }
8905             break;
8906
8907           default:
8908             break;
8909         }
8910     }
8911     
8912     if (appData.debugMode)
8913       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8914
8915     if (cm == XBoardGame) {
8916         /* Skip any header junk before position diagram and/or move 1 */
8917         for (;;) {
8918             yyboardindex = forwardMostMove;
8919             cm = (ChessMove) yylex();
8920
8921             if (cm == (ChessMove) 0 ||
8922                 cm == GNUChessGame || cm == XBoardGame) {
8923                 /* Empty game; pretend end-of-file and handle later */
8924                 cm = (ChessMove) 0;
8925                 break;
8926             }
8927
8928             if (cm == MoveNumberOne || cm == PositionDiagram ||
8929                 cm == PGNTag || cm == Comment)
8930               break;
8931         }
8932     } else if (cm == GNUChessGame) {
8933         if (gameInfo.event != NULL) {
8934             free(gameInfo.event);
8935         }
8936         gameInfo.event = StrSave(yy_text);
8937     }   
8938
8939     startedFromSetupPosition = FALSE;
8940     while (cm == PGNTag) {
8941         if (appData.debugMode) 
8942           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8943         err = ParsePGNTag(yy_text, &gameInfo);
8944         if (!err) numPGNTags++;
8945
8946         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8947         if(gameInfo.variant != oldVariant) {
8948             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8949             InitPosition(TRUE);
8950             oldVariant = gameInfo.variant;
8951             if (appData.debugMode) 
8952               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8953         }
8954
8955
8956         if (gameInfo.fen != NULL) {
8957           Board initial_position;
8958           startedFromSetupPosition = TRUE;
8959           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8960             Reset(TRUE, TRUE);
8961             DisplayError(_("Bad FEN position in file"), 0);
8962             return FALSE;
8963           }
8964           CopyBoard(boards[0], initial_position);
8965           if (blackPlaysFirst) {
8966             currentMove = forwardMostMove = backwardMostMove = 1;
8967             CopyBoard(boards[1], initial_position);
8968             strcpy(moveList[0], "");
8969             strcpy(parseList[0], "");
8970             timeRemaining[0][1] = whiteTimeRemaining;
8971             timeRemaining[1][1] = blackTimeRemaining;
8972             if (commentList[0] != NULL) {
8973               commentList[1] = commentList[0];
8974               commentList[0] = NULL;
8975             }
8976           } else {
8977             currentMove = forwardMostMove = backwardMostMove = 0;
8978           }
8979           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8980           {   int i;
8981               initialRulePlies = FENrulePlies;
8982               epStatus[forwardMostMove] = FENepStatus;
8983               for( i=0; i< nrCastlingRights; i++ )
8984                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8985           }
8986           yyboardindex = forwardMostMove;
8987           free(gameInfo.fen);
8988           gameInfo.fen = NULL;
8989         }
8990
8991         yyboardindex = forwardMostMove;
8992         cm = (ChessMove) yylex();
8993
8994         /* Handle comments interspersed among the tags */
8995         while (cm == Comment) {
8996             char *p;
8997             if (appData.debugMode) 
8998               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8999             p = yy_text;
9000             if (*p == '{' || *p == '[' || *p == '(') {
9001                 p[strlen(p) - 1] = NULLCHAR;
9002                 p++;
9003             }
9004             while (*p == '\n') p++;
9005             AppendComment(currentMove, p);
9006             yyboardindex = forwardMostMove;
9007             cm = (ChessMove) yylex();
9008         }
9009     }
9010
9011     /* don't rely on existence of Event tag since if game was
9012      * pasted from clipboard the Event tag may not exist
9013      */
9014     if (numPGNTags > 0){
9015         char *tags;
9016         if (gameInfo.variant == VariantNormal) {
9017           gameInfo.variant = StringToVariant(gameInfo.event);
9018         }
9019         if (!matchMode) {
9020           if( appData.autoDisplayTags ) {
9021             tags = PGNTags(&gameInfo);
9022             TagsPopUp(tags, CmailMsg());
9023             free(tags);
9024           }
9025         }
9026     } else {
9027         /* Make something up, but don't display it now */
9028         SetGameInfo();
9029         TagsPopDown();
9030     }
9031
9032     if (cm == PositionDiagram) {
9033         int i, j;
9034         char *p;
9035         Board initial_position;
9036
9037         if (appData.debugMode)
9038           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9039
9040         if (!startedFromSetupPosition) {
9041             p = yy_text;
9042             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9043               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9044                 switch (*p) {
9045                   case '[':
9046                   case '-':
9047                   case ' ':
9048                   case '\t':
9049                   case '\n':
9050                   case '\r':
9051                     break;
9052                   default:
9053                     initial_position[i][j++] = CharToPiece(*p);
9054                     break;
9055                 }
9056             while (*p == ' ' || *p == '\t' ||
9057                    *p == '\n' || *p == '\r') p++;
9058         
9059             if (strncmp(p, "black", strlen("black"))==0)
9060               blackPlaysFirst = TRUE;
9061             else
9062               blackPlaysFirst = FALSE;
9063             startedFromSetupPosition = TRUE;
9064         
9065             CopyBoard(boards[0], initial_position);
9066             if (blackPlaysFirst) {
9067                 currentMove = forwardMostMove = backwardMostMove = 1;
9068                 CopyBoard(boards[1], initial_position);
9069                 strcpy(moveList[0], "");
9070                 strcpy(parseList[0], "");
9071                 timeRemaining[0][1] = whiteTimeRemaining;
9072                 timeRemaining[1][1] = blackTimeRemaining;
9073                 if (commentList[0] != NULL) {
9074                     commentList[1] = commentList[0];
9075                     commentList[0] = NULL;
9076                 }
9077             } else {
9078                 currentMove = forwardMostMove = backwardMostMove = 0;
9079             }
9080         }
9081         yyboardindex = forwardMostMove;
9082         cm = (ChessMove) yylex();
9083     }
9084
9085     if (first.pr == NoProc) {
9086         StartChessProgram(&first);
9087     }
9088     InitChessProgram(&first, FALSE);
9089     SendToProgram("force\n", &first);
9090     if (startedFromSetupPosition) {
9091         SendBoard(&first, forwardMostMove);
9092     if (appData.debugMode) {
9093         fprintf(debugFP, "Load Game\n");
9094     }
9095         DisplayBothClocks();
9096     }      
9097
9098     /* [HGM] server: flag to write setup moves in broadcast file as one */
9099     loadFlag = appData.suppressLoadMoves;
9100
9101     while (cm == Comment) {
9102         char *p;
9103         if (appData.debugMode) 
9104           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9105         p = yy_text;
9106         if (*p == '{' || *p == '[' || *p == '(') {
9107             p[strlen(p) - 1] = NULLCHAR;
9108             p++;
9109         }
9110         while (*p == '\n') p++;
9111         AppendComment(currentMove, p);
9112         yyboardindex = forwardMostMove;
9113         cm = (ChessMove) yylex();
9114     }
9115
9116     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9117         cm == WhiteWins || cm == BlackWins ||
9118         cm == GameIsDrawn || cm == GameUnfinished) {
9119         DisplayMessage("", _("No moves in game"));
9120         if (cmailMsgLoaded) {
9121             if (appData.debugMode)
9122               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9123             ClearHighlights();
9124             flipView = FALSE;
9125         }
9126         DrawPosition(FALSE, boards[currentMove]);
9127         DisplayBothClocks();
9128         gameMode = EditGame;
9129         ModeHighlight();
9130         gameFileFP = NULL;
9131         cmailOldMove = 0;
9132         return TRUE;
9133     }
9134
9135     // [HGM] PV info: routine tests if comment empty
9136     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9137         DisplayComment(currentMove - 1, commentList[currentMove]);
9138     }
9139     if (!matchMode && appData.timeDelay != 0) 
9140       DrawPosition(FALSE, boards[currentMove]);
9141
9142     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9143       programStats.ok_to_send = 1;
9144     }
9145
9146     /* if the first token after the PGN tags is a move
9147      * and not move number 1, retrieve it from the parser 
9148      */
9149     if (cm != MoveNumberOne)
9150         LoadGameOneMove(cm);
9151
9152     /* load the remaining moves from the file */
9153     while (LoadGameOneMove((ChessMove)0)) {
9154       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9155       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9156     }
9157
9158     /* rewind to the start of the game */
9159     currentMove = backwardMostMove;
9160
9161     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9162
9163     if (oldGameMode == AnalyzeFile ||
9164         oldGameMode == AnalyzeMode) {
9165       AnalyzeFileEvent();
9166     }
9167
9168     if (matchMode || appData.timeDelay == 0) {
9169       ToEndEvent();
9170       gameMode = EditGame;
9171       ModeHighlight();
9172     } else if (appData.timeDelay > 0) {
9173       AutoPlayGameLoop();
9174     }
9175
9176     if (appData.debugMode) 
9177         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9178
9179     loadFlag = 0; /* [HGM] true game starts */
9180     return TRUE;
9181 }
9182
9183 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9184 int
9185 ReloadPosition(offset)
9186      int offset;
9187 {
9188     int positionNumber = lastLoadPositionNumber + offset;
9189     if (lastLoadPositionFP == NULL) {
9190         DisplayError(_("No position has been loaded yet"), 0);
9191         return FALSE;
9192     }
9193     if (positionNumber <= 0) {
9194         DisplayError(_("Can't back up any further"), 0);
9195         return FALSE;
9196     }
9197     return LoadPosition(lastLoadPositionFP, positionNumber,
9198                         lastLoadPositionTitle);
9199 }
9200
9201 /* Load the nth position from the given file */
9202 int
9203 LoadPositionFromFile(filename, n, title)
9204      char *filename;
9205      int n;
9206      char *title;
9207 {
9208     FILE *f;
9209     char buf[MSG_SIZ];
9210
9211     if (strcmp(filename, "-") == 0) {
9212         return LoadPosition(stdin, n, "stdin");
9213     } else {
9214         f = fopen(filename, "rb");
9215         if (f == NULL) {
9216             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9217             DisplayError(buf, errno);
9218             return FALSE;
9219         } else {
9220             return LoadPosition(f, n, title);
9221         }
9222     }
9223 }
9224
9225 /* Load the nth position from the given open file, and close it */
9226 int
9227 LoadPosition(f, positionNumber, title)
9228      FILE *f;
9229      int positionNumber;
9230      char *title;
9231 {
9232     char *p, line[MSG_SIZ];
9233     Board initial_position;
9234     int i, j, fenMode, pn;
9235     
9236     if (gameMode == Training )
9237         SetTrainingModeOff();
9238
9239     if (gameMode != BeginningOfGame) {
9240         Reset(FALSE, TRUE);
9241     }
9242     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9243         fclose(lastLoadPositionFP);
9244     }
9245     if (positionNumber == 0) positionNumber = 1;
9246     lastLoadPositionFP = f;
9247     lastLoadPositionNumber = positionNumber;
9248     strcpy(lastLoadPositionTitle, title);
9249     if (first.pr == NoProc) {
9250       StartChessProgram(&first);
9251       InitChessProgram(&first, FALSE);
9252     }    
9253     pn = positionNumber;
9254     if (positionNumber < 0) {
9255         /* Negative position number means to seek to that byte offset */
9256         if (fseek(f, -positionNumber, 0) == -1) {
9257             DisplayError(_("Can't seek on position file"), 0);
9258             return FALSE;
9259         };
9260         pn = 1;
9261     } else {
9262         if (fseek(f, 0, 0) == -1) {
9263             if (f == lastLoadPositionFP ?
9264                 positionNumber == lastLoadPositionNumber + 1 :
9265                 positionNumber == 1) {
9266                 pn = 1;
9267             } else {
9268                 DisplayError(_("Can't seek on position file"), 0);
9269                 return FALSE;
9270             }
9271         }
9272     }
9273     /* See if this file is FEN or old-style xboard */
9274     if (fgets(line, MSG_SIZ, f) == NULL) {
9275         DisplayError(_("Position not found in file"), 0);
9276         return FALSE;
9277     }
9278     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9279     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9280
9281     if (pn >= 2) {
9282         if (fenMode || line[0] == '#') pn--;
9283         while (pn > 0) {
9284             /* skip positions before number pn */
9285             if (fgets(line, MSG_SIZ, f) == NULL) {
9286                 Reset(TRUE, TRUE);
9287                 DisplayError(_("Position not found in file"), 0);
9288                 return FALSE;
9289             }
9290             if (fenMode || line[0] == '#') pn--;
9291         }
9292     }
9293
9294     if (fenMode) {
9295         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9296             DisplayError(_("Bad FEN position in file"), 0);
9297             return FALSE;
9298         }
9299     } else {
9300         (void) fgets(line, MSG_SIZ, f);
9301         (void) fgets(line, MSG_SIZ, f);
9302     
9303         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9304             (void) fgets(line, MSG_SIZ, f);
9305             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9306                 if (*p == ' ')
9307                   continue;
9308                 initial_position[i][j++] = CharToPiece(*p);
9309             }
9310         }
9311     
9312         blackPlaysFirst = FALSE;
9313         if (!feof(f)) {
9314             (void) fgets(line, MSG_SIZ, f);
9315             if (strncmp(line, "black", strlen("black"))==0)
9316               blackPlaysFirst = TRUE;
9317         }
9318     }
9319     startedFromSetupPosition = TRUE;
9320     
9321     SendToProgram("force\n", &first);
9322     CopyBoard(boards[0], initial_position);
9323     if (blackPlaysFirst) {
9324         currentMove = forwardMostMove = backwardMostMove = 1;
9325         strcpy(moveList[0], "");
9326         strcpy(parseList[0], "");
9327         CopyBoard(boards[1], initial_position);
9328         DisplayMessage("", _("Black to play"));
9329     } else {
9330         currentMove = forwardMostMove = backwardMostMove = 0;
9331         DisplayMessage("", _("White to play"));
9332     }
9333           /* [HGM] copy FEN attributes as well */
9334           {   int i;
9335               initialRulePlies = FENrulePlies;
9336               epStatus[forwardMostMove] = FENepStatus;
9337               for( i=0; i< nrCastlingRights; i++ )
9338                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9339           }
9340     SendBoard(&first, forwardMostMove);
9341     if (appData.debugMode) {
9342 int i, j;
9343   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9344   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9345         fprintf(debugFP, "Load Position\n");
9346     }
9347
9348     if (positionNumber > 1) {
9349         sprintf(line, "%s %d", title, positionNumber);
9350         DisplayTitle(line);
9351     } else {
9352         DisplayTitle(title);
9353     }
9354     gameMode = EditGame;
9355     ModeHighlight();
9356     ResetClocks();
9357     timeRemaining[0][1] = whiteTimeRemaining;
9358     timeRemaining[1][1] = blackTimeRemaining;
9359     DrawPosition(FALSE, boards[currentMove]);
9360    
9361     return TRUE;
9362 }
9363
9364
9365 void
9366 CopyPlayerNameIntoFileName(dest, src)
9367      char **dest, *src;
9368 {
9369     while (*src != NULLCHAR && *src != ',') {
9370         if (*src == ' ') {
9371             *(*dest)++ = '_';
9372             src++;
9373         } else {
9374             *(*dest)++ = *src++;
9375         }
9376     }
9377 }
9378
9379 char *DefaultFileName(ext)
9380      char *ext;
9381 {
9382     static char def[MSG_SIZ];
9383     char *p;
9384
9385     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9386         p = def;
9387         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9388         *p++ = '-';
9389         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9390         *p++ = '.';
9391         strcpy(p, ext);
9392     } else {
9393         def[0] = NULLCHAR;
9394     }
9395     return def;
9396 }
9397
9398 /* Save the current game to the given file */
9399 int
9400 SaveGameToFile(filename, append)
9401      char *filename;
9402      int append;
9403 {
9404     FILE *f;
9405     char buf[MSG_SIZ];
9406
9407     if (strcmp(filename, "-") == 0) {
9408         return SaveGame(stdout, 0, NULL);
9409     } else {
9410         f = fopen(filename, append ? "a" : "w");
9411         if (f == NULL) {
9412             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9413             DisplayError(buf, errno);
9414             return FALSE;
9415         } else {
9416             return SaveGame(f, 0, NULL);
9417         }
9418     }
9419 }
9420
9421 char *
9422 SavePart(str)
9423      char *str;
9424 {
9425     static char buf[MSG_SIZ];
9426     char *p;
9427     
9428     p = strchr(str, ' ');
9429     if (p == NULL) return str;
9430     strncpy(buf, str, p - str);
9431     buf[p - str] = NULLCHAR;
9432     return buf;
9433 }
9434
9435 #define PGN_MAX_LINE 75
9436
9437 #define PGN_SIDE_WHITE  0
9438 #define PGN_SIDE_BLACK  1
9439
9440 /* [AS] */
9441 static int FindFirstMoveOutOfBook( int side )
9442 {
9443     int result = -1;
9444
9445     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9446         int index = backwardMostMove;
9447         int has_book_hit = 0;
9448
9449         if( (index % 2) != side ) {
9450             index++;
9451         }
9452
9453         while( index < forwardMostMove ) {
9454             /* Check to see if engine is in book */
9455             int depth = pvInfoList[index].depth;
9456             int score = pvInfoList[index].score;
9457             int in_book = 0;
9458
9459             if( depth <= 2 ) {
9460                 in_book = 1;
9461             }
9462             else if( score == 0 && depth == 63 ) {
9463                 in_book = 1; /* Zappa */
9464             }
9465             else if( score == 2 && depth == 99 ) {
9466                 in_book = 1; /* Abrok */
9467             }
9468
9469             has_book_hit += in_book;
9470
9471             if( ! in_book ) {
9472                 result = index;
9473
9474                 break;
9475             }
9476
9477             index += 2;
9478         }
9479     }
9480
9481     return result;
9482 }
9483
9484 /* [AS] */
9485 void GetOutOfBookInfo( char * buf )
9486 {
9487     int oob[2];
9488     int i;
9489     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9490
9491     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9492     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9493
9494     *buf = '\0';
9495
9496     if( oob[0] >= 0 || oob[1] >= 0 ) {
9497         for( i=0; i<2; i++ ) {
9498             int idx = oob[i];
9499
9500             if( idx >= 0 ) {
9501                 if( i > 0 && oob[0] >= 0 ) {
9502                     strcat( buf, "   " );
9503                 }
9504
9505                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9506                 sprintf( buf+strlen(buf), "%s%.2f", 
9507                     pvInfoList[idx].score >= 0 ? "+" : "",
9508                     pvInfoList[idx].score / 100.0 );
9509             }
9510         }
9511     }
9512 }
9513
9514 /* Save game in PGN style and close the file */
9515 int
9516 SaveGamePGN(f)
9517      FILE *f;
9518 {
9519     int i, offset, linelen, newblock;
9520     time_t tm;
9521 //    char *movetext;
9522     char numtext[32];
9523     int movelen, numlen, blank;
9524     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9525
9526     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9527     
9528     tm = time((time_t *) NULL);
9529     
9530     PrintPGNTags(f, &gameInfo);
9531     
9532     if (backwardMostMove > 0 || startedFromSetupPosition) {
9533         char *fen = PositionToFEN(backwardMostMove, NULL);
9534         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9535         fprintf(f, "\n{--------------\n");
9536         PrintPosition(f, backwardMostMove);
9537         fprintf(f, "--------------}\n");
9538         free(fen);
9539     }
9540     else {
9541         /* [AS] Out of book annotation */
9542         if( appData.saveOutOfBookInfo ) {
9543             char buf[64];
9544
9545             GetOutOfBookInfo( buf );
9546
9547             if( buf[0] != '\0' ) {
9548                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9549             }
9550         }
9551
9552         fprintf(f, "\n");
9553     }
9554
9555     i = backwardMostMove;
9556     linelen = 0;
9557     newblock = TRUE;
9558
9559     while (i < forwardMostMove) {
9560         /* Print comments preceding this move */
9561         if (commentList[i] != NULL) {
9562             if (linelen > 0) fprintf(f, "\n");
9563             fprintf(f, "{\n%s}\n", commentList[i]);
9564             linelen = 0;
9565             newblock = TRUE;
9566         }
9567
9568         /* Format move number */
9569         if ((i % 2) == 0) {
9570             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9571         } else {
9572             if (newblock) {
9573                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9574             } else {
9575                 numtext[0] = NULLCHAR;
9576             }
9577         }
9578         numlen = strlen(numtext);
9579         newblock = FALSE;
9580
9581         /* Print move number */
9582         blank = linelen > 0 && numlen > 0;
9583         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9584             fprintf(f, "\n");
9585             linelen = 0;
9586             blank = 0;
9587         }
9588         if (blank) {
9589             fprintf(f, " ");
9590             linelen++;
9591         }
9592         fprintf(f, numtext);
9593         linelen += numlen;
9594
9595         /* Get move */
9596         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9597         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9598
9599         /* Print move */
9600         blank = linelen > 0 && movelen > 0;
9601         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9602             fprintf(f, "\n");
9603             linelen = 0;
9604             blank = 0;
9605         }
9606         if (blank) {
9607             fprintf(f, " ");
9608             linelen++;
9609         }
9610         fprintf(f, move_buffer);
9611         linelen += movelen;
9612
9613         /* [AS] Add PV info if present */
9614         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9615             /* [HGM] add time */
9616             char buf[MSG_SIZ]; int seconds = 0;
9617
9618             if(i >= backwardMostMove) {
9619                 if(WhiteOnMove(i))
9620                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9621                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9622                 else
9623                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9624                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9625             }
9626             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9627
9628             if( seconds <= 0) buf[0] = 0; else
9629             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9630                 seconds = (seconds + 4)/10; // round to full seconds
9631                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9632                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9633             }
9634
9635             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9636                 pvInfoList[i].score >= 0 ? "+" : "",
9637                 pvInfoList[i].score / 100.0,
9638                 pvInfoList[i].depth,
9639                 buf );
9640
9641             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9642
9643             /* Print score/depth */
9644             blank = linelen > 0 && movelen > 0;
9645             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9646                 fprintf(f, "\n");
9647                 linelen = 0;
9648                 blank = 0;
9649             }
9650             if (blank) {
9651                 fprintf(f, " ");
9652                 linelen++;
9653             }
9654             fprintf(f, move_buffer);
9655             linelen += movelen;
9656         }
9657
9658         i++;
9659     }
9660     
9661     /* Start a new line */
9662     if (linelen > 0) fprintf(f, "\n");
9663
9664     /* Print comments after last move */
9665     if (commentList[i] != NULL) {
9666         fprintf(f, "{\n%s}\n", commentList[i]);
9667     }
9668
9669     /* Print result */
9670     if (gameInfo.resultDetails != NULL &&
9671         gameInfo.resultDetails[0] != NULLCHAR) {
9672         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9673                 PGNResult(gameInfo.result));
9674     } else {
9675         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9676     }
9677
9678     fclose(f);
9679     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9680     return TRUE;
9681 }
9682
9683 /* Save game in old style and close the file */
9684 int
9685 SaveGameOldStyle(f)
9686      FILE *f;
9687 {
9688     int i, offset;
9689     time_t tm;
9690     
9691     tm = time((time_t *) NULL);
9692     
9693     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9694     PrintOpponents(f);
9695     
9696     if (backwardMostMove > 0 || startedFromSetupPosition) {
9697         fprintf(f, "\n[--------------\n");
9698         PrintPosition(f, backwardMostMove);
9699         fprintf(f, "--------------]\n");
9700     } else {
9701         fprintf(f, "\n");
9702     }
9703
9704     i = backwardMostMove;
9705     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9706
9707     while (i < forwardMostMove) {
9708         if (commentList[i] != NULL) {
9709             fprintf(f, "[%s]\n", commentList[i]);
9710         }
9711
9712         if ((i % 2) == 1) {
9713             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9714             i++;
9715         } else {
9716             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9717             i++;
9718             if (commentList[i] != NULL) {
9719                 fprintf(f, "\n");
9720                 continue;
9721             }
9722             if (i >= forwardMostMove) {
9723                 fprintf(f, "\n");
9724                 break;
9725             }
9726             fprintf(f, "%s\n", parseList[i]);
9727             i++;
9728         }
9729     }
9730     
9731     if (commentList[i] != NULL) {
9732         fprintf(f, "[%s]\n", commentList[i]);
9733     }
9734
9735     /* This isn't really the old style, but it's close enough */
9736     if (gameInfo.resultDetails != NULL &&
9737         gameInfo.resultDetails[0] != NULLCHAR) {
9738         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9739                 gameInfo.resultDetails);
9740     } else {
9741         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9742     }
9743
9744     fclose(f);
9745     return TRUE;
9746 }
9747
9748 /* Save the current game to open file f and close the file */
9749 int
9750 SaveGame(f, dummy, dummy2)
9751      FILE *f;
9752      int dummy;
9753      char *dummy2;
9754 {
9755     if (gameMode == EditPosition) EditPositionDone();
9756     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9757     if (appData.oldSaveStyle)
9758       return SaveGameOldStyle(f);
9759     else
9760       return SaveGamePGN(f);
9761 }
9762
9763 /* Save the current position to the given file */
9764 int
9765 SavePositionToFile(filename)
9766      char *filename;
9767 {
9768     FILE *f;
9769     char buf[MSG_SIZ];
9770
9771     if (strcmp(filename, "-") == 0) {
9772         return SavePosition(stdout, 0, NULL);
9773     } else {
9774         f = fopen(filename, "a");
9775         if (f == NULL) {
9776             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9777             DisplayError(buf, errno);
9778             return FALSE;
9779         } else {
9780             SavePosition(f, 0, NULL);
9781             return TRUE;
9782         }
9783     }
9784 }
9785
9786 /* Save the current position to the given open file and close the file */
9787 int
9788 SavePosition(f, dummy, dummy2)
9789      FILE *f;
9790      int dummy;
9791      char *dummy2;
9792 {
9793     time_t tm;
9794     char *fen;
9795     
9796     if (appData.oldSaveStyle) {
9797         tm = time((time_t *) NULL);
9798     
9799         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9800         PrintOpponents(f);
9801         fprintf(f, "[--------------\n");
9802         PrintPosition(f, currentMove);
9803         fprintf(f, "--------------]\n");
9804     } else {
9805         fen = PositionToFEN(currentMove, NULL);
9806         fprintf(f, "%s\n", fen);
9807         free(fen);
9808     }
9809     fclose(f);
9810     return TRUE;
9811 }
9812
9813 void
9814 ReloadCmailMsgEvent(unregister)
9815      int unregister;
9816 {
9817 #if !WIN32
9818     static char *inFilename = NULL;
9819     static char *outFilename;
9820     int i;
9821     struct stat inbuf, outbuf;
9822     int status;
9823     
9824     /* Any registered moves are unregistered if unregister is set, */
9825     /* i.e. invoked by the signal handler */
9826     if (unregister) {
9827         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9828             cmailMoveRegistered[i] = FALSE;
9829             if (cmailCommentList[i] != NULL) {
9830                 free(cmailCommentList[i]);
9831                 cmailCommentList[i] = NULL;
9832             }
9833         }
9834         nCmailMovesRegistered = 0;
9835     }
9836
9837     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9838         cmailResult[i] = CMAIL_NOT_RESULT;
9839     }
9840     nCmailResults = 0;
9841
9842     if (inFilename == NULL) {
9843         /* Because the filenames are static they only get malloced once  */
9844         /* and they never get freed                                      */
9845         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9846         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9847
9848         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9849         sprintf(outFilename, "%s.out", appData.cmailGameName);
9850     }
9851     
9852     status = stat(outFilename, &outbuf);
9853     if (status < 0) {
9854         cmailMailedMove = FALSE;
9855     } else {
9856         status = stat(inFilename, &inbuf);
9857         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9858     }
9859     
9860     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9861        counts the games, notes how each one terminated, etc.
9862        
9863        It would be nice to remove this kludge and instead gather all
9864        the information while building the game list.  (And to keep it
9865        in the game list nodes instead of having a bunch of fixed-size
9866        parallel arrays.)  Note this will require getting each game's
9867        termination from the PGN tags, as the game list builder does
9868        not process the game moves.  --mann
9869        */
9870     cmailMsgLoaded = TRUE;
9871     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9872     
9873     /* Load first game in the file or popup game menu */
9874     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9875
9876 #endif /* !WIN32 */
9877     return;
9878 }
9879
9880 int
9881 RegisterMove()
9882 {
9883     FILE *f;
9884     char string[MSG_SIZ];
9885
9886     if (   cmailMailedMove
9887         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9888         return TRUE;            /* Allow free viewing  */
9889     }
9890
9891     /* Unregister move to ensure that we don't leave RegisterMove        */
9892     /* with the move registered when the conditions for registering no   */
9893     /* longer hold                                                       */
9894     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9895         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9896         nCmailMovesRegistered --;
9897
9898         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9899           {
9900               free(cmailCommentList[lastLoadGameNumber - 1]);
9901               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9902           }
9903     }
9904
9905     if (cmailOldMove == -1) {
9906         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9907         return FALSE;
9908     }
9909
9910     if (currentMove > cmailOldMove + 1) {
9911         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9912         return FALSE;
9913     }
9914
9915     if (currentMove < cmailOldMove) {
9916         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9917         return FALSE;
9918     }
9919
9920     if (forwardMostMove > currentMove) {
9921         /* Silently truncate extra moves */
9922         TruncateGame();
9923     }
9924
9925     if (   (currentMove == cmailOldMove + 1)
9926         || (   (currentMove == cmailOldMove)
9927             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9928                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9929         if (gameInfo.result != GameUnfinished) {
9930             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9931         }
9932
9933         if (commentList[currentMove] != NULL) {
9934             cmailCommentList[lastLoadGameNumber - 1]
9935               = StrSave(commentList[currentMove]);
9936         }
9937         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9938
9939         if (appData.debugMode)
9940           fprintf(debugFP, "Saving %s for game %d\n",
9941                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9942
9943         sprintf(string,
9944                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9945         
9946         f = fopen(string, "w");
9947         if (appData.oldSaveStyle) {
9948             SaveGameOldStyle(f); /* also closes the file */
9949             
9950             sprintf(string, "%s.pos.out", appData.cmailGameName);
9951             f = fopen(string, "w");
9952             SavePosition(f, 0, NULL); /* also closes the file */
9953         } else {
9954             fprintf(f, "{--------------\n");
9955             PrintPosition(f, currentMove);
9956             fprintf(f, "--------------}\n\n");
9957             
9958             SaveGame(f, 0, NULL); /* also closes the file*/
9959         }
9960         
9961         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9962         nCmailMovesRegistered ++;
9963     } else if (nCmailGames == 1) {
9964         DisplayError(_("You have not made a move yet"), 0);
9965         return FALSE;
9966     }
9967
9968     return TRUE;
9969 }
9970
9971 void
9972 MailMoveEvent()
9973 {
9974 #if !WIN32
9975     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9976     FILE *commandOutput;
9977     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9978     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
9979     int nBuffers;
9980     int i;
9981     int archived;
9982     char *arcDir;
9983
9984     if (! cmailMsgLoaded) {
9985         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9986         return;
9987     }
9988
9989     if (nCmailGames == nCmailResults) {
9990         DisplayError(_("No unfinished games"), 0);
9991         return;
9992     }
9993
9994 #if CMAIL_PROHIBIT_REMAIL
9995     if (cmailMailedMove) {
9996         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);
9997         DisplayError(msg, 0);
9998         return;
9999     }
10000 #endif
10001
10002     if (! (cmailMailedMove || RegisterMove())) return;
10003     
10004     if (   cmailMailedMove
10005         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10006         sprintf(string, partCommandString,
10007                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10008         commandOutput = popen(string, "r");
10009
10010         if (commandOutput == NULL) {
10011             DisplayError(_("Failed to invoke cmail"), 0);
10012         } else {
10013             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10014                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10015             }
10016             if (nBuffers > 1) {
10017                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10018                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10019                 nBytes = MSG_SIZ - 1;
10020             } else {
10021                 (void) memcpy(msg, buffer, nBytes);
10022             }
10023             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10024
10025             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10026                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10027
10028                 archived = TRUE;
10029                 for (i = 0; i < nCmailGames; i ++) {
10030                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10031                         archived = FALSE;
10032                     }
10033                 }
10034                 if (   archived
10035                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10036                         != NULL)) {
10037                     sprintf(buffer, "%s/%s.%s.archive",
10038                             arcDir,
10039                             appData.cmailGameName,
10040                             gameInfo.date);
10041                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10042                     cmailMsgLoaded = FALSE;
10043                 }
10044             }
10045
10046             DisplayInformation(msg);
10047             pclose(commandOutput);
10048         }
10049     } else {
10050         if ((*cmailMsg) != '\0') {
10051             DisplayInformation(cmailMsg);
10052         }
10053     }
10054
10055     return;
10056 #endif /* !WIN32 */
10057 }
10058
10059 char *
10060 CmailMsg()
10061 {
10062 #if WIN32
10063     return NULL;
10064 #else
10065     int  prependComma = 0;
10066     char number[5];
10067     char string[MSG_SIZ];       /* Space for game-list */
10068     int  i;
10069     
10070     if (!cmailMsgLoaded) return "";
10071
10072     if (cmailMailedMove) {
10073         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10074     } else {
10075         /* Create a list of games left */
10076         sprintf(string, "[");
10077         for (i = 0; i < nCmailGames; i ++) {
10078             if (! (   cmailMoveRegistered[i]
10079                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10080                 if (prependComma) {
10081                     sprintf(number, ",%d", i + 1);
10082                 } else {
10083                     sprintf(number, "%d", i + 1);
10084                     prependComma = 1;
10085                 }
10086                 
10087                 strcat(string, number);
10088             }
10089         }
10090         strcat(string, "]");
10091
10092         if (nCmailMovesRegistered + nCmailResults == 0) {
10093             switch (nCmailGames) {
10094               case 1:
10095                 sprintf(cmailMsg,
10096                         _("Still need to make move for game\n"));
10097                 break;
10098                 
10099               case 2:
10100                 sprintf(cmailMsg,
10101                         _("Still need to make moves for both games\n"));
10102                 break;
10103                 
10104               default:
10105                 sprintf(cmailMsg,
10106                         _("Still need to make moves for all %d games\n"),
10107                         nCmailGames);
10108                 break;
10109             }
10110         } else {
10111             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10112               case 1:
10113                 sprintf(cmailMsg,
10114                         _("Still need to make a move for game %s\n"),
10115                         string);
10116                 break;
10117                 
10118               case 0:
10119                 if (nCmailResults == nCmailGames) {
10120                     sprintf(cmailMsg, _("No unfinished games\n"));
10121                 } else {
10122                     sprintf(cmailMsg, _("Ready to send mail\n"));
10123                 }
10124                 break;
10125                 
10126               default:
10127                 sprintf(cmailMsg,
10128                         _("Still need to make moves for games %s\n"),
10129                         string);
10130             }
10131         }
10132     }
10133     return cmailMsg;
10134 #endif /* WIN32 */
10135 }
10136
10137 void
10138 ResetGameEvent()
10139 {
10140     if (gameMode == Training)
10141       SetTrainingModeOff();
10142
10143     Reset(TRUE, TRUE);
10144     cmailMsgLoaded = FALSE;
10145     if (appData.icsActive) {
10146       SendToICS(ics_prefix);
10147       SendToICS("refresh\n");
10148     }
10149 }
10150
10151 void
10152 ExitEvent(status)
10153      int status;
10154 {
10155     exiting++;
10156     if (exiting > 2) {
10157       /* Give up on clean exit */
10158       exit(status);
10159     }
10160     if (exiting > 1) {
10161       /* Keep trying for clean exit */
10162       return;
10163     }
10164
10165     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10166
10167     if (telnetISR != NULL) {
10168       RemoveInputSource(telnetISR);
10169     }
10170     if (icsPR != NoProc) {
10171       DestroyChildProcess(icsPR, TRUE);
10172     }
10173
10174     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10175     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10176
10177     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10178     /* make sure this other one finishes before killing it!                  */
10179     if(endingGame) { int count = 0;
10180         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10181         while(endingGame && count++ < 10) DoSleep(1);
10182         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10183     }
10184
10185     /* Kill off chess programs */
10186     if (first.pr != NoProc) {
10187         ExitAnalyzeMode();
10188         
10189         DoSleep( appData.delayBeforeQuit );
10190         SendToProgram("quit\n", &first);
10191         DoSleep( appData.delayAfterQuit );
10192         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10193     }
10194     if (second.pr != NoProc) {
10195         DoSleep( appData.delayBeforeQuit );
10196         SendToProgram("quit\n", &second);
10197         DoSleep( appData.delayAfterQuit );
10198         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10199     }
10200     if (first.isr != NULL) {
10201         RemoveInputSource(first.isr);
10202     }
10203     if (second.isr != NULL) {
10204         RemoveInputSource(second.isr);
10205     }
10206
10207     ShutDownFrontEnd();
10208     exit(status);
10209 }
10210
10211 void
10212 PauseEvent()
10213 {
10214     if (appData.debugMode)
10215         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10216     if (pausing) {
10217         pausing = FALSE;
10218         ModeHighlight();
10219         if (gameMode == MachinePlaysWhite ||
10220             gameMode == MachinePlaysBlack) {
10221             StartClocks();
10222         } else {
10223             DisplayBothClocks();
10224         }
10225         if (gameMode == PlayFromGameFile) {
10226             if (appData.timeDelay >= 0) 
10227                 AutoPlayGameLoop();
10228         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10229             Reset(FALSE, TRUE);
10230             SendToICS(ics_prefix);
10231             SendToICS("refresh\n");
10232         } else if (currentMove < forwardMostMove) {
10233             ForwardInner(forwardMostMove);
10234         }
10235         pauseExamInvalid = FALSE;
10236     } else {
10237         switch (gameMode) {
10238           default:
10239             return;
10240           case IcsExamining:
10241             pauseExamForwardMostMove = forwardMostMove;
10242             pauseExamInvalid = FALSE;
10243             /* fall through */
10244           case IcsObserving:
10245           case IcsPlayingWhite:
10246           case IcsPlayingBlack:
10247             pausing = TRUE;
10248             ModeHighlight();
10249             return;
10250           case PlayFromGameFile:
10251             (void) StopLoadGameTimer();
10252             pausing = TRUE;
10253             ModeHighlight();
10254             break;
10255           case BeginningOfGame:
10256             if (appData.icsActive) return;
10257             /* else fall through */
10258           case MachinePlaysWhite:
10259           case MachinePlaysBlack:
10260           case TwoMachinesPlay:
10261             if (forwardMostMove == 0)
10262               return;           /* don't pause if no one has moved */
10263             if ((gameMode == MachinePlaysWhite &&
10264                  !WhiteOnMove(forwardMostMove)) ||
10265                 (gameMode == MachinePlaysBlack &&
10266                  WhiteOnMove(forwardMostMove))) {
10267                 StopClocks();
10268             }
10269             pausing = TRUE;
10270             ModeHighlight();
10271             break;
10272         }
10273     }
10274 }
10275
10276 void
10277 EditCommentEvent()
10278 {
10279     char title[MSG_SIZ];
10280
10281     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10282         strcpy(title, _("Edit comment"));
10283     } else {
10284         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10285                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10286                 parseList[currentMove - 1]);
10287     }
10288
10289     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10290 }
10291
10292
10293 void
10294 EditTagsEvent()
10295 {
10296     char *tags = PGNTags(&gameInfo);
10297     EditTagsPopUp(tags);
10298     free(tags);
10299 }
10300
10301 void
10302 AnalyzeModeEvent()
10303 {
10304     if (appData.noChessProgram || gameMode == AnalyzeMode)
10305       return;
10306
10307     if (gameMode != AnalyzeFile) {
10308         if (!appData.icsEngineAnalyze) {
10309                EditGameEvent();
10310                if (gameMode != EditGame) return;
10311         }
10312         ResurrectChessProgram();
10313         SendToProgram("analyze\n", &first);
10314         first.analyzing = TRUE;
10315         /*first.maybeThinking = TRUE;*/
10316         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10317         AnalysisPopUp(_("Analysis"),
10318                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10319     }
10320     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10321     pausing = FALSE;
10322     ModeHighlight();
10323     SetGameInfo();
10324
10325     StartAnalysisClock();
10326     GetTimeMark(&lastNodeCountTime);
10327     lastNodeCount = 0;
10328 }
10329
10330 void
10331 AnalyzeFileEvent()
10332 {
10333     if (appData.noChessProgram || gameMode == AnalyzeFile)
10334       return;
10335
10336     if (gameMode != AnalyzeMode) {
10337         EditGameEvent();
10338         if (gameMode != EditGame) return;
10339         ResurrectChessProgram();
10340         SendToProgram("analyze\n", &first);
10341         first.analyzing = TRUE;
10342         /*first.maybeThinking = TRUE;*/
10343         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10344         AnalysisPopUp(_("Analysis"),
10345                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10346     }
10347     gameMode = AnalyzeFile;
10348     pausing = FALSE;
10349     ModeHighlight();
10350     SetGameInfo();
10351
10352     StartAnalysisClock();
10353     GetTimeMark(&lastNodeCountTime);
10354     lastNodeCount = 0;
10355 }
10356
10357 void
10358 MachineWhiteEvent()
10359 {
10360     char buf[MSG_SIZ];
10361     char *bookHit = NULL;
10362
10363     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10364       return;
10365
10366
10367     if (gameMode == PlayFromGameFile || 
10368         gameMode == TwoMachinesPlay  || 
10369         gameMode == Training         || 
10370         gameMode == AnalyzeMode      || 
10371         gameMode == EndOfGame)
10372         EditGameEvent();
10373
10374     if (gameMode == EditPosition) 
10375         EditPositionDone();
10376
10377     if (!WhiteOnMove(currentMove)) {
10378         DisplayError(_("It is not White's turn"), 0);
10379         return;
10380     }
10381   
10382     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10383       ExitAnalyzeMode();
10384
10385     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10386         gameMode == AnalyzeFile)
10387         TruncateGame();
10388
10389     ResurrectChessProgram();    /* in case it isn't running */
10390     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10391         gameMode = MachinePlaysWhite;
10392         ResetClocks();
10393     } else
10394     gameMode = MachinePlaysWhite;
10395     pausing = FALSE;
10396     ModeHighlight();
10397     SetGameInfo();
10398     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10399     DisplayTitle(buf);
10400     if (first.sendName) {
10401       sprintf(buf, "name %s\n", gameInfo.black);
10402       SendToProgram(buf, &first);
10403     }
10404     if (first.sendTime) {
10405       if (first.useColors) {
10406         SendToProgram("black\n", &first); /*gnu kludge*/
10407       }
10408       SendTimeRemaining(&first, TRUE);
10409     }
10410     if (first.useColors) {
10411       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10412     }
10413     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10414     SetMachineThinkingEnables();
10415     first.maybeThinking = TRUE;
10416     StartClocks();
10417     firstMove = FALSE;
10418
10419     if (appData.autoFlipView && !flipView) {
10420       flipView = !flipView;
10421       DrawPosition(FALSE, NULL);
10422       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10423     }
10424
10425     if(bookHit) { // [HGM] book: simulate book reply
10426         static char bookMove[MSG_SIZ]; // a bit generous?
10427
10428         programStats.nodes = programStats.depth = programStats.time = 
10429         programStats.score = programStats.got_only_move = 0;
10430         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10431
10432         strcpy(bookMove, "move ");
10433         strcat(bookMove, bookHit);
10434         HandleMachineMove(bookMove, &first);
10435     }
10436 }
10437
10438 void
10439 MachineBlackEvent()
10440 {
10441     char buf[MSG_SIZ];
10442    char *bookHit = NULL;
10443
10444     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10445         return;
10446
10447
10448     if (gameMode == PlayFromGameFile || 
10449         gameMode == TwoMachinesPlay  || 
10450         gameMode == Training         || 
10451         gameMode == AnalyzeMode      || 
10452         gameMode == EndOfGame)
10453         EditGameEvent();
10454
10455     if (gameMode == EditPosition) 
10456         EditPositionDone();
10457
10458     if (WhiteOnMove(currentMove)) {
10459         DisplayError(_("It is not Black's turn"), 0);
10460         return;
10461     }
10462     
10463     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10464       ExitAnalyzeMode();
10465
10466     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10467         gameMode == AnalyzeFile)
10468         TruncateGame();
10469
10470     ResurrectChessProgram();    /* in case it isn't running */
10471     gameMode = MachinePlaysBlack;
10472     pausing = FALSE;
10473     ModeHighlight();
10474     SetGameInfo();
10475     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10476     DisplayTitle(buf);
10477     if (first.sendName) {
10478       sprintf(buf, "name %s\n", gameInfo.white);
10479       SendToProgram(buf, &first);
10480     }
10481     if (first.sendTime) {
10482       if (first.useColors) {
10483         SendToProgram("white\n", &first); /*gnu kludge*/
10484       }
10485       SendTimeRemaining(&first, FALSE);
10486     }
10487     if (first.useColors) {
10488       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10489     }
10490     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10491     SetMachineThinkingEnables();
10492     first.maybeThinking = TRUE;
10493     StartClocks();
10494
10495     if (appData.autoFlipView && flipView) {
10496       flipView = !flipView;
10497       DrawPosition(FALSE, NULL);
10498       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10499     }
10500     if(bookHit) { // [HGM] book: simulate book reply
10501         static char bookMove[MSG_SIZ]; // a bit generous?
10502
10503         programStats.nodes = programStats.depth = programStats.time = 
10504         programStats.score = programStats.got_only_move = 0;
10505         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10506
10507         strcpy(bookMove, "move ");
10508         strcat(bookMove, bookHit);
10509         HandleMachineMove(bookMove, &first);
10510     }
10511 }
10512
10513
10514 void
10515 DisplayTwoMachinesTitle()
10516 {
10517     char buf[MSG_SIZ];
10518     if (appData.matchGames > 0) {
10519         if (first.twoMachinesColor[0] == 'w') {
10520             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10521                     gameInfo.white, gameInfo.black,
10522                     first.matchWins, second.matchWins,
10523                     matchGame - 1 - (first.matchWins + second.matchWins));
10524         } else {
10525             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10526                     gameInfo.white, gameInfo.black,
10527                     second.matchWins, first.matchWins,
10528                     matchGame - 1 - (first.matchWins + second.matchWins));
10529         }
10530     } else {
10531         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10532     }
10533     DisplayTitle(buf);
10534 }
10535
10536 void
10537 TwoMachinesEvent P((void))
10538 {
10539     int i;
10540     char buf[MSG_SIZ];
10541     ChessProgramState *onmove;
10542     char *bookHit = NULL;
10543     
10544     if (appData.noChessProgram) return;
10545
10546     switch (gameMode) {
10547       case TwoMachinesPlay:
10548         return;
10549       case MachinePlaysWhite:
10550       case MachinePlaysBlack:
10551         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10552             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10553             return;
10554         }
10555         /* fall through */
10556       case BeginningOfGame:
10557       case PlayFromGameFile:
10558       case EndOfGame:
10559         EditGameEvent();
10560         if (gameMode != EditGame) return;
10561         break;
10562       case EditPosition:
10563         EditPositionDone();
10564         break;
10565       case AnalyzeMode:
10566       case AnalyzeFile:
10567         ExitAnalyzeMode();
10568         break;
10569       case EditGame:
10570       default:
10571         break;
10572     }
10573
10574     forwardMostMove = currentMove;
10575     ResurrectChessProgram();    /* in case first program isn't running */
10576
10577     if (second.pr == NULL) {
10578         StartChessProgram(&second);
10579         if (second.protocolVersion == 1) {
10580           TwoMachinesEventIfReady();
10581         } else {
10582           /* kludge: allow timeout for initial "feature" command */
10583           FreezeUI();
10584           DisplayMessage("", _("Starting second chess program"));
10585           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10586         }
10587         return;
10588     }
10589     DisplayMessage("", "");
10590     InitChessProgram(&second, FALSE);
10591     SendToProgram("force\n", &second);
10592     if (startedFromSetupPosition) {
10593         SendBoard(&second, backwardMostMove);
10594     if (appData.debugMode) {
10595         fprintf(debugFP, "Two Machines\n");
10596     }
10597     }
10598     for (i = backwardMostMove; i < forwardMostMove; i++) {
10599         SendMoveToProgram(i, &second);
10600     }
10601
10602     gameMode = TwoMachinesPlay;
10603     pausing = FALSE;
10604     ModeHighlight();
10605     SetGameInfo();
10606     DisplayTwoMachinesTitle();
10607     firstMove = TRUE;
10608     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10609         onmove = &first;
10610     } else {
10611         onmove = &second;
10612     }
10613
10614     SendToProgram(first.computerString, &first);
10615     if (first.sendName) {
10616       sprintf(buf, "name %s\n", second.tidy);
10617       SendToProgram(buf, &first);
10618     }
10619     SendToProgram(second.computerString, &second);
10620     if (second.sendName) {
10621       sprintf(buf, "name %s\n", first.tidy);
10622       SendToProgram(buf, &second);
10623     }
10624
10625     ResetClocks();
10626     if (!first.sendTime || !second.sendTime) {
10627         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10628         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10629     }
10630     if (onmove->sendTime) {
10631       if (onmove->useColors) {
10632         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10633       }
10634       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10635     }
10636     if (onmove->useColors) {
10637       SendToProgram(onmove->twoMachinesColor, onmove);
10638     }
10639     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10640 //    SendToProgram("go\n", onmove);
10641     onmove->maybeThinking = TRUE;
10642     SetMachineThinkingEnables();
10643
10644     StartClocks();
10645
10646     if(bookHit) { // [HGM] book: simulate book reply
10647         static char bookMove[MSG_SIZ]; // a bit generous?
10648
10649         programStats.nodes = programStats.depth = programStats.time = 
10650         programStats.score = programStats.got_only_move = 0;
10651         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10652
10653         strcpy(bookMove, "move ");
10654         strcat(bookMove, bookHit);
10655         HandleMachineMove(bookMove, &first);
10656     }
10657 }
10658
10659 void
10660 TrainingEvent()
10661 {
10662     if (gameMode == Training) {
10663       SetTrainingModeOff();
10664       gameMode = PlayFromGameFile;
10665       DisplayMessage("", _("Training mode off"));
10666     } else {
10667       gameMode = Training;
10668       animateTraining = appData.animate;
10669
10670       /* make sure we are not already at the end of the game */
10671       if (currentMove < forwardMostMove) {
10672         SetTrainingModeOn();
10673         DisplayMessage("", _("Training mode on"));
10674       } else {
10675         gameMode = PlayFromGameFile;
10676         DisplayError(_("Already at end of game"), 0);
10677       }
10678     }
10679     ModeHighlight();
10680 }
10681
10682 void
10683 IcsClientEvent()
10684 {
10685     if (!appData.icsActive) return;
10686     switch (gameMode) {
10687       case IcsPlayingWhite:
10688       case IcsPlayingBlack:
10689       case IcsObserving:
10690       case IcsIdle:
10691       case BeginningOfGame:
10692       case IcsExamining:
10693         return;
10694
10695       case EditGame:
10696         break;
10697
10698       case EditPosition:
10699         EditPositionDone();
10700         break;
10701
10702       case AnalyzeMode:
10703       case AnalyzeFile:
10704         ExitAnalyzeMode();
10705         break;
10706         
10707       default:
10708         EditGameEvent();
10709         break;
10710     }
10711
10712     gameMode = IcsIdle;
10713     ModeHighlight();
10714     return;
10715 }
10716
10717
10718 void
10719 EditGameEvent()
10720 {
10721     int i;
10722
10723     switch (gameMode) {
10724       case Training:
10725         SetTrainingModeOff();
10726         break;
10727       case MachinePlaysWhite:
10728       case MachinePlaysBlack:
10729       case BeginningOfGame:
10730         SendToProgram("force\n", &first);
10731         SetUserThinkingEnables();
10732         break;
10733       case PlayFromGameFile:
10734         (void) StopLoadGameTimer();
10735         if (gameFileFP != NULL) {
10736             gameFileFP = NULL;
10737         }
10738         break;
10739       case EditPosition:
10740         EditPositionDone();
10741         break;
10742       case AnalyzeMode:
10743       case AnalyzeFile:
10744         ExitAnalyzeMode();
10745         SendToProgram("force\n", &first);
10746         break;
10747       case TwoMachinesPlay:
10748         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10749         ResurrectChessProgram();
10750         SetUserThinkingEnables();
10751         break;
10752       case EndOfGame:
10753         ResurrectChessProgram();
10754         break;
10755       case IcsPlayingBlack:
10756       case IcsPlayingWhite:
10757         DisplayError(_("Warning: You are still playing a game"), 0);
10758         break;
10759       case IcsObserving:
10760         DisplayError(_("Warning: You are still observing a game"), 0);
10761         break;
10762       case IcsExamining:
10763         DisplayError(_("Warning: You are still examining a game"), 0);
10764         break;
10765       case IcsIdle:
10766         break;
10767       case EditGame:
10768       default:
10769         return;
10770     }
10771     
10772     pausing = FALSE;
10773     StopClocks();
10774     first.offeredDraw = second.offeredDraw = 0;
10775
10776     if (gameMode == PlayFromGameFile) {
10777         whiteTimeRemaining = timeRemaining[0][currentMove];
10778         blackTimeRemaining = timeRemaining[1][currentMove];
10779         DisplayTitle("");
10780     }
10781
10782     if (gameMode == MachinePlaysWhite ||
10783         gameMode == MachinePlaysBlack ||
10784         gameMode == TwoMachinesPlay ||
10785         gameMode == EndOfGame) {
10786         i = forwardMostMove;
10787         while (i > currentMove) {
10788             SendToProgram("undo\n", &first);
10789             i--;
10790         }
10791         whiteTimeRemaining = timeRemaining[0][currentMove];
10792         blackTimeRemaining = timeRemaining[1][currentMove];
10793         DisplayBothClocks();
10794         if (whiteFlag || blackFlag) {
10795             whiteFlag = blackFlag = 0;
10796         }
10797         DisplayTitle("");
10798     }           
10799     
10800     gameMode = EditGame;
10801     ModeHighlight();
10802     SetGameInfo();
10803 }
10804
10805
10806 void
10807 EditPositionEvent()
10808 {
10809     if (gameMode == EditPosition) {
10810         EditGameEvent();
10811         return;
10812     }
10813     
10814     EditGameEvent();
10815     if (gameMode != EditGame) return;
10816     
10817     gameMode = EditPosition;
10818     ModeHighlight();
10819     SetGameInfo();
10820     if (currentMove > 0)
10821       CopyBoard(boards[0], boards[currentMove]);
10822     
10823     blackPlaysFirst = !WhiteOnMove(currentMove);
10824     ResetClocks();
10825     currentMove = forwardMostMove = backwardMostMove = 0;
10826     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10827     DisplayMove(-1);
10828 }
10829
10830 void
10831 ExitAnalyzeMode()
10832 {
10833     /* [DM] icsEngineAnalyze - possible call from other functions */
10834     if (appData.icsEngineAnalyze) {
10835         appData.icsEngineAnalyze = FALSE;
10836
10837         DisplayMessage("",_("Close ICS engine analyze..."));
10838     }
10839     if (first.analysisSupport && first.analyzing) {
10840       SendToProgram("exit\n", &first);
10841       first.analyzing = FALSE;
10842     }
10843     AnalysisPopDown();
10844     thinkOutput[0] = NULLCHAR;
10845 }
10846
10847 void
10848 EditPositionDone()
10849 {
10850     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10851
10852     startedFromSetupPosition = TRUE;
10853     InitChessProgram(&first, FALSE);
10854     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10855     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10856         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10857         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10858     } else castlingRights[0][2] = -1;
10859     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10860         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10861         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10862     } else castlingRights[0][5] = -1;
10863     SendToProgram("force\n", &first);
10864     if (blackPlaysFirst) {
10865         strcpy(moveList[0], "");
10866         strcpy(parseList[0], "");
10867         currentMove = forwardMostMove = backwardMostMove = 1;
10868         CopyBoard(boards[1], boards[0]);
10869         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10870         { int i;
10871           epStatus[1] = epStatus[0];
10872           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10873         }
10874     } else {
10875         currentMove = forwardMostMove = backwardMostMove = 0;
10876     }
10877     SendBoard(&first, forwardMostMove);
10878     if (appData.debugMode) {
10879         fprintf(debugFP, "EditPosDone\n");
10880     }
10881     DisplayTitle("");
10882     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10883     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10884     gameMode = EditGame;
10885     ModeHighlight();
10886     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10887     ClearHighlights(); /* [AS] */
10888 }
10889
10890 /* Pause for `ms' milliseconds */
10891 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10892 void
10893 TimeDelay(ms)
10894      long ms;
10895 {
10896     TimeMark m1, m2;
10897
10898     GetTimeMark(&m1);
10899     do {
10900         GetTimeMark(&m2);
10901     } while (SubtractTimeMarks(&m2, &m1) < ms);
10902 }
10903
10904 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10905 void
10906 SendMultiLineToICS(buf)
10907      char *buf;
10908 {
10909     char temp[MSG_SIZ+1], *p;
10910     int len;
10911
10912     len = strlen(buf);
10913     if (len > MSG_SIZ)
10914       len = MSG_SIZ;
10915   
10916     strncpy(temp, buf, len);
10917     temp[len] = 0;
10918
10919     p = temp;
10920     while (*p) {
10921         if (*p == '\n' || *p == '\r')
10922           *p = ' ';
10923         ++p;
10924     }
10925
10926     strcat(temp, "\n");
10927     SendToICS(temp);
10928     SendToPlayer(temp, strlen(temp));
10929 }
10930
10931 void
10932 SetWhiteToPlayEvent()
10933 {
10934     if (gameMode == EditPosition) {
10935         blackPlaysFirst = FALSE;
10936         DisplayBothClocks();    /* works because currentMove is 0 */
10937     } else if (gameMode == IcsExamining) {
10938         SendToICS(ics_prefix);
10939         SendToICS("tomove white\n");
10940     }
10941 }
10942
10943 void
10944 SetBlackToPlayEvent()
10945 {
10946     if (gameMode == EditPosition) {
10947         blackPlaysFirst = TRUE;
10948         currentMove = 1;        /* kludge */
10949         DisplayBothClocks();
10950         currentMove = 0;
10951     } else if (gameMode == IcsExamining) {
10952         SendToICS(ics_prefix);
10953         SendToICS("tomove black\n");
10954     }
10955 }
10956
10957 void
10958 EditPositionMenuEvent(selection, x, y)
10959      ChessSquare selection;
10960      int x, y;
10961 {
10962     char buf[MSG_SIZ];
10963     ChessSquare piece = boards[0][y][x];
10964
10965     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10966
10967     switch (selection) {
10968       case ClearBoard:
10969         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10970             SendToICS(ics_prefix);
10971             SendToICS("bsetup clear\n");
10972         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10973             SendToICS(ics_prefix);
10974             SendToICS("clearboard\n");
10975         } else {
10976             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10977                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10978                 for (y = 0; y < BOARD_HEIGHT; y++) {
10979                     if (gameMode == IcsExamining) {
10980                         if (boards[currentMove][y][x] != EmptySquare) {
10981                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
10982                                     AAA + x, ONE + y);
10983                             SendToICS(buf);
10984                         }
10985                     } else {
10986                         boards[0][y][x] = p;
10987                     }
10988                 }
10989             }
10990         }
10991         if (gameMode == EditPosition) {
10992             DrawPosition(FALSE, boards[0]);
10993         }
10994         break;
10995
10996       case WhitePlay:
10997         SetWhiteToPlayEvent();
10998         break;
10999
11000       case BlackPlay:
11001         SetBlackToPlayEvent();
11002         break;
11003
11004       case EmptySquare:
11005         if (gameMode == IcsExamining) {
11006             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11007             SendToICS(buf);
11008         } else {
11009             boards[0][y][x] = EmptySquare;
11010             DrawPosition(FALSE, boards[0]);
11011         }
11012         break;
11013
11014       case PromotePiece:
11015         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11016            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11017             selection = (ChessSquare) (PROMOTED piece);
11018         } else if(piece == EmptySquare) selection = WhiteSilver;
11019         else selection = (ChessSquare)((int)piece - 1);
11020         goto defaultlabel;
11021
11022       case DemotePiece:
11023         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11024            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11025             selection = (ChessSquare) (DEMOTED piece);
11026         } else if(piece == EmptySquare) selection = BlackSilver;
11027         else selection = (ChessSquare)((int)piece + 1);       
11028         goto defaultlabel;
11029
11030       case WhiteQueen:
11031       case BlackQueen:
11032         if(gameInfo.variant == VariantShatranj ||
11033            gameInfo.variant == VariantXiangqi  ||
11034            gameInfo.variant == VariantCourier    )
11035             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11036         goto defaultlabel;
11037
11038       case WhiteKing:
11039       case BlackKing:
11040         if(gameInfo.variant == VariantXiangqi)
11041             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11042         if(gameInfo.variant == VariantKnightmate)
11043             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11044       default:
11045         defaultlabel:
11046         if (gameMode == IcsExamining) {
11047             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11048                     PieceToChar(selection), AAA + x, ONE + y);
11049             SendToICS(buf);
11050         } else {
11051             boards[0][y][x] = selection;
11052             DrawPosition(FALSE, boards[0]);
11053         }
11054         break;
11055     }
11056 }
11057
11058
11059 void
11060 DropMenuEvent(selection, x, y)
11061      ChessSquare selection;
11062      int x, y;
11063 {
11064     ChessMove moveType;
11065
11066     switch (gameMode) {
11067       case IcsPlayingWhite:
11068       case MachinePlaysBlack:
11069         if (!WhiteOnMove(currentMove)) {
11070             DisplayMoveError(_("It is Black's turn"));
11071             return;
11072         }
11073         moveType = WhiteDrop;
11074         break;
11075       case IcsPlayingBlack:
11076       case MachinePlaysWhite:
11077         if (WhiteOnMove(currentMove)) {
11078             DisplayMoveError(_("It is White's turn"));
11079             return;
11080         }
11081         moveType = BlackDrop;
11082         break;
11083       case EditGame:
11084         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11085         break;
11086       default:
11087         return;
11088     }
11089
11090     if (moveType == BlackDrop && selection < BlackPawn) {
11091       selection = (ChessSquare) ((int) selection
11092                                  + (int) BlackPawn - (int) WhitePawn);
11093     }
11094     if (boards[currentMove][y][x] != EmptySquare) {
11095         DisplayMoveError(_("That square is occupied"));
11096         return;
11097     }
11098
11099     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11100 }
11101
11102 void
11103 AcceptEvent()
11104 {
11105     /* Accept a pending offer of any kind from opponent */
11106     
11107     if (appData.icsActive) {
11108         SendToICS(ics_prefix);
11109         SendToICS("accept\n");
11110     } else if (cmailMsgLoaded) {
11111         if (currentMove == cmailOldMove &&
11112             commentList[cmailOldMove] != NULL &&
11113             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11114                    "Black offers a draw" : "White offers a draw")) {
11115             TruncateGame();
11116             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11117             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11118         } else {
11119             DisplayError(_("There is no pending offer on this move"), 0);
11120             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11121         }
11122     } else {
11123         /* Not used for offers from chess program */
11124     }
11125 }
11126
11127 void
11128 DeclineEvent()
11129 {
11130     /* Decline a pending offer of any kind from opponent */
11131     
11132     if (appData.icsActive) {
11133         SendToICS(ics_prefix);
11134         SendToICS("decline\n");
11135     } else if (cmailMsgLoaded) {
11136         if (currentMove == cmailOldMove &&
11137             commentList[cmailOldMove] != NULL &&
11138             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11139                    "Black offers a draw" : "White offers a draw")) {
11140 #ifdef NOTDEF
11141             AppendComment(cmailOldMove, "Draw declined");
11142             DisplayComment(cmailOldMove - 1, "Draw declined");
11143 #endif /*NOTDEF*/
11144         } else {
11145             DisplayError(_("There is no pending offer on this move"), 0);
11146         }
11147     } else {
11148         /* Not used for offers from chess program */
11149     }
11150 }
11151
11152 void
11153 RematchEvent()
11154 {
11155     /* Issue ICS rematch command */
11156     if (appData.icsActive) {
11157         SendToICS(ics_prefix);
11158         SendToICS("rematch\n");
11159     }
11160 }
11161
11162 void
11163 CallFlagEvent()
11164 {
11165     /* Call your opponent's flag (claim a win on time) */
11166     if (appData.icsActive) {
11167         SendToICS(ics_prefix);
11168         SendToICS("flag\n");
11169     } else {
11170         switch (gameMode) {
11171           default:
11172             return;
11173           case MachinePlaysWhite:
11174             if (whiteFlag) {
11175                 if (blackFlag)
11176                   GameEnds(GameIsDrawn, "Both players ran out of time",
11177                            GE_PLAYER);
11178                 else
11179                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11180             } else {
11181                 DisplayError(_("Your opponent is not out of time"), 0);
11182             }
11183             break;
11184           case MachinePlaysBlack:
11185             if (blackFlag) {
11186                 if (whiteFlag)
11187                   GameEnds(GameIsDrawn, "Both players ran out of time",
11188                            GE_PLAYER);
11189                 else
11190                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11191             } else {
11192                 DisplayError(_("Your opponent is not out of time"), 0);
11193             }
11194             break;
11195         }
11196     }
11197 }
11198
11199 void
11200 DrawEvent()
11201 {
11202     /* Offer draw or accept pending draw offer from opponent */
11203     
11204     if (appData.icsActive) {
11205         /* Note: tournament rules require draw offers to be
11206            made after you make your move but before you punch
11207            your clock.  Currently ICS doesn't let you do that;
11208            instead, you immediately punch your clock after making
11209            a move, but you can offer a draw at any time. */
11210         
11211         SendToICS(ics_prefix);
11212         SendToICS("draw\n");
11213     } else if (cmailMsgLoaded) {
11214         if (currentMove == cmailOldMove &&
11215             commentList[cmailOldMove] != NULL &&
11216             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11217                    "Black offers a draw" : "White offers a draw")) {
11218             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11219             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11220         } else if (currentMove == cmailOldMove + 1) {
11221             char *offer = WhiteOnMove(cmailOldMove) ?
11222               "White offers a draw" : "Black offers a draw";
11223             AppendComment(currentMove, offer);
11224             DisplayComment(currentMove - 1, offer);
11225             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11226         } else {
11227             DisplayError(_("You must make your move before offering a draw"), 0);
11228             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11229         }
11230     } else if (first.offeredDraw) {
11231         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11232     } else {
11233         if (first.sendDrawOffers) {
11234             SendToProgram("draw\n", &first);
11235             userOfferedDraw = TRUE;
11236         }
11237     }
11238 }
11239
11240 void
11241 AdjournEvent()
11242 {
11243     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11244     
11245     if (appData.icsActive) {
11246         SendToICS(ics_prefix);
11247         SendToICS("adjourn\n");
11248     } else {
11249         /* Currently GNU Chess doesn't offer or accept Adjourns */
11250     }
11251 }
11252
11253
11254 void
11255 AbortEvent()
11256 {
11257     /* Offer Abort or accept pending Abort offer from opponent */
11258     
11259     if (appData.icsActive) {
11260         SendToICS(ics_prefix);
11261         SendToICS("abort\n");
11262     } else {
11263         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11264     }
11265 }
11266
11267 void
11268 ResignEvent()
11269 {
11270     /* Resign.  You can do this even if it's not your turn. */
11271     
11272     if (appData.icsActive) {
11273         SendToICS(ics_prefix);
11274         SendToICS("resign\n");
11275     } else {
11276         switch (gameMode) {
11277           case MachinePlaysWhite:
11278             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11279             break;
11280           case MachinePlaysBlack:
11281             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11282             break;
11283           case EditGame:
11284             if (cmailMsgLoaded) {
11285                 TruncateGame();
11286                 if (WhiteOnMove(cmailOldMove)) {
11287                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11288                 } else {
11289                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11290                 }
11291                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11292             }
11293             break;
11294           default:
11295             break;
11296         }
11297     }
11298 }
11299
11300
11301 void
11302 StopObservingEvent()
11303 {
11304     /* Stop observing current games */
11305     SendToICS(ics_prefix);
11306     SendToICS("unobserve\n");
11307 }
11308
11309 void
11310 StopExaminingEvent()
11311 {
11312     /* Stop observing current game */
11313     SendToICS(ics_prefix);
11314     SendToICS("unexamine\n");
11315 }
11316
11317 void
11318 ForwardInner(target)
11319      int target;
11320 {
11321     int limit;
11322
11323     if (appData.debugMode)
11324         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11325                 target, currentMove, forwardMostMove);
11326
11327     if (gameMode == EditPosition)
11328       return;
11329
11330     if (gameMode == PlayFromGameFile && !pausing)
11331       PauseEvent();
11332     
11333     if (gameMode == IcsExamining && pausing)
11334       limit = pauseExamForwardMostMove;
11335     else
11336       limit = forwardMostMove;
11337     
11338     if (target > limit) target = limit;
11339
11340     if (target > 0 && moveList[target - 1][0]) {
11341         int fromX, fromY, toX, toY;
11342         toX = moveList[target - 1][2] - AAA;
11343         toY = moveList[target - 1][3] - ONE;
11344         if (moveList[target - 1][1] == '@') {
11345             if (appData.highlightLastMove) {
11346                 SetHighlights(-1, -1, toX, toY);
11347             }
11348         } else {
11349             fromX = moveList[target - 1][0] - AAA;
11350             fromY = moveList[target - 1][1] - ONE;
11351             if (target == currentMove + 1) {
11352                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11353             }
11354             if (appData.highlightLastMove) {
11355                 SetHighlights(fromX, fromY, toX, toY);
11356             }
11357         }
11358     }
11359     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11360         gameMode == Training || gameMode == PlayFromGameFile || 
11361         gameMode == AnalyzeFile) {
11362         while (currentMove < target) {
11363             SendMoveToProgram(currentMove++, &first);
11364         }
11365     } else {
11366         currentMove = target;
11367     }
11368     
11369     if (gameMode == EditGame || gameMode == EndOfGame) {
11370         whiteTimeRemaining = timeRemaining[0][currentMove];
11371         blackTimeRemaining = timeRemaining[1][currentMove];
11372     }
11373     DisplayBothClocks();
11374     DisplayMove(currentMove - 1);
11375     DrawPosition(FALSE, boards[currentMove]);
11376     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11377     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11378         DisplayComment(currentMove - 1, commentList[currentMove]);
11379     }
11380 }
11381
11382
11383 void
11384 ForwardEvent()
11385 {
11386     if (gameMode == IcsExamining && !pausing) {
11387         SendToICS(ics_prefix);
11388         SendToICS("forward\n");
11389     } else {
11390         ForwardInner(currentMove + 1);
11391     }
11392 }
11393
11394 void
11395 ToEndEvent()
11396 {
11397     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11398         /* to optimze, we temporarily turn off analysis mode while we feed
11399          * the remaining moves to the engine. Otherwise we get analysis output
11400          * after each move.
11401          */ 
11402         if (first.analysisSupport) {
11403           SendToProgram("exit\nforce\n", &first);
11404           first.analyzing = FALSE;
11405         }
11406     }
11407         
11408     if (gameMode == IcsExamining && !pausing) {
11409         SendToICS(ics_prefix);
11410         SendToICS("forward 999999\n");
11411     } else {
11412         ForwardInner(forwardMostMove);
11413     }
11414
11415     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11416         /* we have fed all the moves, so reactivate analysis mode */
11417         SendToProgram("analyze\n", &first);
11418         first.analyzing = TRUE;
11419         /*first.maybeThinking = TRUE;*/
11420         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11421     }
11422 }
11423
11424 void
11425 BackwardInner(target)
11426      int target;
11427 {
11428     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11429
11430     if (appData.debugMode)
11431         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11432                 target, currentMove, forwardMostMove);
11433
11434     if (gameMode == EditPosition) return;
11435     if (currentMove <= backwardMostMove) {
11436         ClearHighlights();
11437         DrawPosition(full_redraw, boards[currentMove]);
11438         return;
11439     }
11440     if (gameMode == PlayFromGameFile && !pausing)
11441       PauseEvent();
11442     
11443     if (moveList[target][0]) {
11444         int fromX, fromY, toX, toY;
11445         toX = moveList[target][2] - AAA;
11446         toY = moveList[target][3] - ONE;
11447         if (moveList[target][1] == '@') {
11448             if (appData.highlightLastMove) {
11449                 SetHighlights(-1, -1, toX, toY);
11450             }
11451         } else {
11452             fromX = moveList[target][0] - AAA;
11453             fromY = moveList[target][1] - ONE;
11454             if (target == currentMove - 1) {
11455                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11456             }
11457             if (appData.highlightLastMove) {
11458                 SetHighlights(fromX, fromY, toX, toY);
11459             }
11460         }
11461     }
11462     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11463         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11464         while (currentMove > target) {
11465             SendToProgram("undo\n", &first);
11466             currentMove--;
11467         }
11468     } else {
11469         currentMove = target;
11470     }
11471     
11472     if (gameMode == EditGame || gameMode == EndOfGame) {
11473         whiteTimeRemaining = timeRemaining[0][currentMove];
11474         blackTimeRemaining = timeRemaining[1][currentMove];
11475     }
11476     DisplayBothClocks();
11477     DisplayMove(currentMove - 1);
11478     DrawPosition(full_redraw, boards[currentMove]);
11479     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11480     // [HGM] PV info: routine tests if comment empty
11481     DisplayComment(currentMove - 1, commentList[currentMove]);
11482 }
11483
11484 void
11485 BackwardEvent()
11486 {
11487     if (gameMode == IcsExamining && !pausing) {
11488         SendToICS(ics_prefix);
11489         SendToICS("backward\n");
11490     } else {
11491         BackwardInner(currentMove - 1);
11492     }
11493 }
11494
11495 void
11496 ToStartEvent()
11497 {
11498     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11499         /* to optimze, we temporarily turn off analysis mode while we undo
11500          * all the moves. Otherwise we get analysis output after each undo.
11501          */ 
11502         if (first.analysisSupport) {
11503           SendToProgram("exit\nforce\n", &first);
11504           first.analyzing = FALSE;
11505         }
11506     }
11507
11508     if (gameMode == IcsExamining && !pausing) {
11509         SendToICS(ics_prefix);
11510         SendToICS("backward 999999\n");
11511     } else {
11512         BackwardInner(backwardMostMove);
11513     }
11514
11515     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11516         /* we have fed all the moves, so reactivate analysis mode */
11517         SendToProgram("analyze\n", &first);
11518         first.analyzing = TRUE;
11519         /*first.maybeThinking = TRUE;*/
11520         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11521     }
11522 }
11523
11524 void
11525 ToNrEvent(int to)
11526 {
11527   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11528   if (to >= forwardMostMove) to = forwardMostMove;
11529   if (to <= backwardMostMove) to = backwardMostMove;
11530   if (to < currentMove) {
11531     BackwardInner(to);
11532   } else {
11533     ForwardInner(to);
11534   }
11535 }
11536
11537 void
11538 RevertEvent()
11539 {
11540     if (gameMode != IcsExamining) {
11541         DisplayError(_("You are not examining a game"), 0);
11542         return;
11543     }
11544     if (pausing) {
11545         DisplayError(_("You can't revert while pausing"), 0);
11546         return;
11547     }
11548     SendToICS(ics_prefix);
11549     SendToICS("revert\n");
11550 }
11551
11552 void
11553 RetractMoveEvent()
11554 {
11555     switch (gameMode) {
11556       case MachinePlaysWhite:
11557       case MachinePlaysBlack:
11558         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11559             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11560             return;
11561         }
11562         if (forwardMostMove < 2) return;
11563         currentMove = forwardMostMove = forwardMostMove - 2;
11564         whiteTimeRemaining = timeRemaining[0][currentMove];
11565         blackTimeRemaining = timeRemaining[1][currentMove];
11566         DisplayBothClocks();
11567         DisplayMove(currentMove - 1);
11568         ClearHighlights();/*!! could figure this out*/
11569         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11570         SendToProgram("remove\n", &first);
11571         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11572         break;
11573
11574       case BeginningOfGame:
11575       default:
11576         break;
11577
11578       case IcsPlayingWhite:
11579       case IcsPlayingBlack:
11580         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11581             SendToICS(ics_prefix);
11582             SendToICS("takeback 2\n");
11583         } else {
11584             SendToICS(ics_prefix);
11585             SendToICS("takeback 1\n");
11586         }
11587         break;
11588     }
11589 }
11590
11591 void
11592 MoveNowEvent()
11593 {
11594     ChessProgramState *cps;
11595
11596     switch (gameMode) {
11597       case MachinePlaysWhite:
11598         if (!WhiteOnMove(forwardMostMove)) {
11599             DisplayError(_("It is your turn"), 0);
11600             return;
11601         }
11602         cps = &first;
11603         break;
11604       case MachinePlaysBlack:
11605         if (WhiteOnMove(forwardMostMove)) {
11606             DisplayError(_("It is your turn"), 0);
11607             return;
11608         }
11609         cps = &first;
11610         break;
11611       case TwoMachinesPlay:
11612         if (WhiteOnMove(forwardMostMove) ==
11613             (first.twoMachinesColor[0] == 'w')) {
11614             cps = &first;
11615         } else {
11616             cps = &second;
11617         }
11618         break;
11619       case BeginningOfGame:
11620       default:
11621         return;
11622     }
11623     SendToProgram("?\n", cps);
11624 }
11625
11626 void
11627 TruncateGameEvent()
11628 {
11629     EditGameEvent();
11630     if (gameMode != EditGame) return;
11631     TruncateGame();
11632 }
11633
11634 void
11635 TruncateGame()
11636 {
11637     if (forwardMostMove > currentMove) {
11638         if (gameInfo.resultDetails != NULL) {
11639             free(gameInfo.resultDetails);
11640             gameInfo.resultDetails = NULL;
11641             gameInfo.result = GameUnfinished;
11642         }
11643         forwardMostMove = currentMove;
11644         HistorySet(parseList, backwardMostMove, forwardMostMove,
11645                    currentMove-1);
11646     }
11647 }
11648
11649 void
11650 HintEvent()
11651 {
11652     if (appData.noChessProgram) return;
11653     switch (gameMode) {
11654       case MachinePlaysWhite:
11655         if (WhiteOnMove(forwardMostMove)) {
11656             DisplayError(_("Wait until your turn"), 0);
11657             return;
11658         }
11659         break;
11660       case BeginningOfGame:
11661       case MachinePlaysBlack:
11662         if (!WhiteOnMove(forwardMostMove)) {
11663             DisplayError(_("Wait until your turn"), 0);
11664             return;
11665         }
11666         break;
11667       default:
11668         DisplayError(_("No hint available"), 0);
11669         return;
11670     }
11671     SendToProgram("hint\n", &first);
11672     hintRequested = TRUE;
11673 }
11674
11675 void
11676 BookEvent()
11677 {
11678     if (appData.noChessProgram) return;
11679     switch (gameMode) {
11680       case MachinePlaysWhite:
11681         if (WhiteOnMove(forwardMostMove)) {
11682             DisplayError(_("Wait until your turn"), 0);
11683             return;
11684         }
11685         break;
11686       case BeginningOfGame:
11687       case MachinePlaysBlack:
11688         if (!WhiteOnMove(forwardMostMove)) {
11689             DisplayError(_("Wait until your turn"), 0);
11690             return;
11691         }
11692         break;
11693       case EditPosition:
11694         EditPositionDone();
11695         break;
11696       case TwoMachinesPlay:
11697         return;
11698       default:
11699         break;
11700     }
11701     SendToProgram("bk\n", &first);
11702     bookOutput[0] = NULLCHAR;
11703     bookRequested = TRUE;
11704 }
11705
11706 void
11707 AboutGameEvent()
11708 {
11709     char *tags = PGNTags(&gameInfo);
11710     TagsPopUp(tags, CmailMsg());
11711     free(tags);
11712 }
11713
11714 /* end button procedures */
11715
11716 void
11717 PrintPosition(fp, move)
11718      FILE *fp;
11719      int move;
11720 {
11721     int i, j;
11722     
11723     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11724         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11725             char c = PieceToChar(boards[move][i][j]);
11726             fputc(c == 'x' ? '.' : c, fp);
11727             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11728         }
11729     }
11730     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11731       fprintf(fp, "white to play\n");
11732     else
11733       fprintf(fp, "black to play\n");
11734 }
11735
11736 void
11737 PrintOpponents(fp)
11738      FILE *fp;
11739 {
11740     if (gameInfo.white != NULL) {
11741         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11742     } else {
11743         fprintf(fp, "\n");
11744     }
11745 }
11746
11747 /* Find last component of program's own name, using some heuristics */
11748 void
11749 TidyProgramName(prog, host, buf)
11750      char *prog, *host, buf[MSG_SIZ];
11751 {
11752     char *p, *q;
11753     int local = (strcmp(host, "localhost") == 0);
11754     while (!local && (p = strchr(prog, ';')) != NULL) {
11755         p++;
11756         while (*p == ' ') p++;
11757         prog = p;
11758     }
11759     if (*prog == '"' || *prog == '\'') {
11760         q = strchr(prog + 1, *prog);
11761     } else {
11762         q = strchr(prog, ' ');
11763     }
11764     if (q == NULL) q = prog + strlen(prog);
11765     p = q;
11766     while (p >= prog && *p != '/' && *p != '\\') p--;
11767     p++;
11768     if(p == prog && *p == '"') p++;
11769     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11770     memcpy(buf, p, q - p);
11771     buf[q - p] = NULLCHAR;
11772     if (!local) {
11773         strcat(buf, "@");
11774         strcat(buf, host);
11775     }
11776 }
11777
11778 char *
11779 TimeControlTagValue()
11780 {
11781     char buf[MSG_SIZ];
11782     if (!appData.clockMode) {
11783         strcpy(buf, "-");
11784     } else if (movesPerSession > 0) {
11785         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11786     } else if (timeIncrement == 0) {
11787         sprintf(buf, "%ld", timeControl/1000);
11788     } else {
11789         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11790     }
11791     return StrSave(buf);
11792 }
11793
11794 void
11795 SetGameInfo()
11796 {
11797     /* This routine is used only for certain modes */
11798     VariantClass v = gameInfo.variant;
11799     ClearGameInfo(&gameInfo);
11800     gameInfo.variant = v;
11801
11802     switch (gameMode) {
11803       case MachinePlaysWhite:
11804         gameInfo.event = StrSave( appData.pgnEventHeader );
11805         gameInfo.site = StrSave(HostName());
11806         gameInfo.date = PGNDate();
11807         gameInfo.round = StrSave("-");
11808         gameInfo.white = StrSave(first.tidy);
11809         gameInfo.black = StrSave(UserName());
11810         gameInfo.timeControl = TimeControlTagValue();
11811         break;
11812
11813       case MachinePlaysBlack:
11814         gameInfo.event = StrSave( appData.pgnEventHeader );
11815         gameInfo.site = StrSave(HostName());
11816         gameInfo.date = PGNDate();
11817         gameInfo.round = StrSave("-");
11818         gameInfo.white = StrSave(UserName());
11819         gameInfo.black = StrSave(first.tidy);
11820         gameInfo.timeControl = TimeControlTagValue();
11821         break;
11822
11823       case TwoMachinesPlay:
11824         gameInfo.event = StrSave( appData.pgnEventHeader );
11825         gameInfo.site = StrSave(HostName());
11826         gameInfo.date = PGNDate();
11827         if (matchGame > 0) {
11828             char buf[MSG_SIZ];
11829             sprintf(buf, "%d", matchGame);
11830             gameInfo.round = StrSave(buf);
11831         } else {
11832             gameInfo.round = StrSave("-");
11833         }
11834         if (first.twoMachinesColor[0] == 'w') {
11835             gameInfo.white = StrSave(first.tidy);
11836             gameInfo.black = StrSave(second.tidy);
11837         } else {
11838             gameInfo.white = StrSave(second.tidy);
11839             gameInfo.black = StrSave(first.tidy);
11840         }
11841         gameInfo.timeControl = TimeControlTagValue();
11842         break;
11843
11844       case EditGame:
11845         gameInfo.event = StrSave("Edited game");
11846         gameInfo.site = StrSave(HostName());
11847         gameInfo.date = PGNDate();
11848         gameInfo.round = StrSave("-");
11849         gameInfo.white = StrSave("-");
11850         gameInfo.black = StrSave("-");
11851         break;
11852
11853       case EditPosition:
11854         gameInfo.event = StrSave("Edited position");
11855         gameInfo.site = StrSave(HostName());
11856         gameInfo.date = PGNDate();
11857         gameInfo.round = StrSave("-");
11858         gameInfo.white = StrSave("-");
11859         gameInfo.black = StrSave("-");
11860         break;
11861
11862       case IcsPlayingWhite:
11863       case IcsPlayingBlack:
11864       case IcsObserving:
11865       case IcsExamining:
11866         break;
11867
11868       case PlayFromGameFile:
11869         gameInfo.event = StrSave("Game from non-PGN file");
11870         gameInfo.site = StrSave(HostName());
11871         gameInfo.date = PGNDate();
11872         gameInfo.round = StrSave("-");
11873         gameInfo.white = StrSave("?");
11874         gameInfo.black = StrSave("?");
11875         break;
11876
11877       default:
11878         break;
11879     }
11880 }
11881
11882 void
11883 ReplaceComment(index, text)
11884      int index;
11885      char *text;
11886 {
11887     int len;
11888
11889     while (*text == '\n') text++;
11890     len = strlen(text);
11891     while (len > 0 && text[len - 1] == '\n') len--;
11892
11893     if (commentList[index] != NULL)
11894       free(commentList[index]);
11895
11896     if (len == 0) {
11897         commentList[index] = NULL;
11898         return;
11899     }
11900     commentList[index] = (char *) malloc(len + 2);
11901     strncpy(commentList[index], text, len);
11902     commentList[index][len] = '\n';
11903     commentList[index][len + 1] = NULLCHAR;
11904 }
11905
11906 void
11907 CrushCRs(text)
11908      char *text;
11909 {
11910   char *p = text;
11911   char *q = text;
11912   char ch;
11913
11914   do {
11915     ch = *p++;
11916     if (ch == '\r') continue;
11917     *q++ = ch;
11918   } while (ch != '\0');
11919 }
11920
11921 void
11922 AppendComment(index, text)
11923      int index;
11924      char *text;
11925 {
11926     int oldlen, len;
11927     char *old;
11928
11929     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11930
11931     CrushCRs(text);
11932     while (*text == '\n') text++;
11933     len = strlen(text);
11934     while (len > 0 && text[len - 1] == '\n') len--;
11935
11936     if (len == 0) return;
11937
11938     if (commentList[index] != NULL) {
11939         old = commentList[index];
11940         oldlen = strlen(old);
11941         commentList[index] = (char *) malloc(oldlen + len + 2);
11942         strcpy(commentList[index], old);
11943         free(old);
11944         strncpy(&commentList[index][oldlen], text, len);
11945         commentList[index][oldlen + len] = '\n';
11946         commentList[index][oldlen + len + 1] = NULLCHAR;
11947     } else {
11948         commentList[index] = (char *) malloc(len + 2);
11949         strncpy(commentList[index], text, len);
11950         commentList[index][len] = '\n';
11951         commentList[index][len + 1] = NULLCHAR;
11952     }
11953 }
11954
11955 static char * FindStr( char * text, char * sub_text )
11956 {
11957     char * result = strstr( text, sub_text );
11958
11959     if( result != NULL ) {
11960         result += strlen( sub_text );
11961     }
11962
11963     return result;
11964 }
11965
11966 /* [AS] Try to extract PV info from PGN comment */
11967 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11968 char *GetInfoFromComment( int index, char * text )
11969 {
11970     char * sep = text;
11971
11972     if( text != NULL && index > 0 ) {
11973         int score = 0;
11974         int depth = 0;
11975         int time = -1, sec = 0, deci;
11976         char * s_eval = FindStr( text, "[%eval " );
11977         char * s_emt = FindStr( text, "[%emt " );
11978
11979         if( s_eval != NULL || s_emt != NULL ) {
11980             /* New style */
11981             char delim;
11982
11983             if( s_eval != NULL ) {
11984                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11985                     return text;
11986                 }
11987
11988                 if( delim != ']' ) {
11989                     return text;
11990                 }
11991             }
11992
11993             if( s_emt != NULL ) {
11994             }
11995         }
11996         else {
11997             /* We expect something like: [+|-]nnn.nn/dd */
11998             int score_lo = 0;
11999
12000             sep = strchr( text, '/' );
12001             if( sep == NULL || sep < (text+4) ) {
12002                 return text;
12003             }
12004
12005             time = -1; sec = -1; deci = -1;
12006             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12007                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12008                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12009                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12010                 return text;
12011             }
12012
12013             if( score_lo < 0 || score_lo >= 100 ) {
12014                 return text;
12015             }
12016
12017             if(sec >= 0) time = 600*time + 10*sec; else
12018             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12019
12020             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12021
12022             /* [HGM] PV time: now locate end of PV info */
12023             while( *++sep >= '0' && *sep <= '9'); // strip depth
12024             if(time >= 0)
12025             while( *++sep >= '0' && *sep <= '9'); // strip time
12026             if(sec >= 0)
12027             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12028             if(deci >= 0)
12029             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12030             while(*sep == ' ') sep++;
12031         }
12032
12033         if( depth <= 0 ) {
12034             return text;
12035         }
12036
12037         if( time < 0 ) {
12038             time = -1;
12039         }
12040
12041         pvInfoList[index-1].depth = depth;
12042         pvInfoList[index-1].score = score;
12043         pvInfoList[index-1].time  = 10*time; // centi-sec
12044     }
12045     return sep;
12046 }
12047
12048 void
12049 SendToProgram(message, cps)
12050      char *message;
12051      ChessProgramState *cps;
12052 {
12053     int count, outCount, error;
12054     char buf[MSG_SIZ];
12055
12056     if (cps->pr == NULL) return;
12057     Attention(cps);
12058     
12059     if (appData.debugMode) {
12060         TimeMark now;
12061         GetTimeMark(&now);
12062         fprintf(debugFP, "%ld >%-6s: %s", 
12063                 SubtractTimeMarks(&now, &programStartTime),
12064                 cps->which, message);
12065     }
12066     
12067     count = strlen(message);
12068     outCount = OutputToProcess(cps->pr, message, count, &error);
12069     if (outCount < count && !exiting 
12070                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12071         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12072         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12073             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12074                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12075                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12076             } else {
12077                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12078             }
12079             gameInfo.resultDetails = buf;
12080         }
12081         DisplayFatalError(buf, error, 1);
12082     }
12083 }
12084
12085 void
12086 ReceiveFromProgram(isr, closure, message, count, error)
12087      InputSourceRef isr;
12088      VOIDSTAR closure;
12089      char *message;
12090      int count;
12091      int error;
12092 {
12093     char *end_str;
12094     char buf[MSG_SIZ];
12095     ChessProgramState *cps = (ChessProgramState *)closure;
12096
12097     if (isr != cps->isr) return; /* Killed intentionally */
12098     if (count <= 0) {
12099         if (count == 0) {
12100             sprintf(buf,
12101                     _("Error: %s chess program (%s) exited unexpectedly"),
12102                     cps->which, cps->program);
12103         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12104                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12105                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12106                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12107                 } else {
12108                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12109                 }
12110                 gameInfo.resultDetails = buf;
12111             }
12112             RemoveInputSource(cps->isr);
12113             DisplayFatalError(buf, 0, 1);
12114         } else {
12115             sprintf(buf,
12116                     _("Error reading from %s chess program (%s)"),
12117                     cps->which, cps->program);
12118             RemoveInputSource(cps->isr);
12119
12120             /* [AS] Program is misbehaving badly... kill it */
12121             if( count == -2 ) {
12122                 DestroyChildProcess( cps->pr, 9 );
12123                 cps->pr = NoProc;
12124             }
12125
12126             DisplayFatalError(buf, error, 1);
12127         }
12128         return;
12129     }
12130     
12131     if ((end_str = strchr(message, '\r')) != NULL)
12132       *end_str = NULLCHAR;
12133     if ((end_str = strchr(message, '\n')) != NULL)
12134       *end_str = NULLCHAR;
12135     
12136     if (appData.debugMode) {
12137         TimeMark now; int print = 1;
12138         char *quote = ""; char c; int i;
12139
12140         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12141                 char start = message[0];
12142                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12143                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12144                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12145                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12146                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12147                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12148                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12149                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12150                         { quote = "# "; print = (appData.engineComments == 2); }
12151                 message[0] = start; // restore original message
12152         }
12153         if(print) {
12154                 GetTimeMark(&now);
12155                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12156                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12157                         quote,
12158                         message);
12159         }
12160     }
12161
12162     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12163     if (appData.icsEngineAnalyze) {
12164         if (strstr(message, "whisper") != NULL ||
12165              strstr(message, "kibitz") != NULL || 
12166             strstr(message, "tellics") != NULL) return;
12167     }
12168
12169     HandleMachineMove(message, cps);
12170 }
12171
12172
12173 void
12174 SendTimeControl(cps, mps, tc, inc, sd, st)
12175      ChessProgramState *cps;
12176      int mps, inc, sd, st;
12177      long tc;
12178 {
12179     char buf[MSG_SIZ];
12180     int seconds;
12181
12182     if( timeControl_2 > 0 ) {
12183         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12184             tc = timeControl_2;
12185         }
12186     }
12187     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12188     inc /= cps->timeOdds;
12189     st  /= cps->timeOdds;
12190
12191     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12192
12193     if (st > 0) {
12194       /* Set exact time per move, normally using st command */
12195       if (cps->stKludge) {
12196         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12197         seconds = st % 60;
12198         if (seconds == 0) {
12199           sprintf(buf, "level 1 %d\n", st/60);
12200         } else {
12201           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12202         }
12203       } else {
12204         sprintf(buf, "st %d\n", st);
12205       }
12206     } else {
12207       /* Set conventional or incremental time control, using level command */
12208       if (seconds == 0) {
12209         /* Note old gnuchess bug -- minutes:seconds used to not work.
12210            Fixed in later versions, but still avoid :seconds
12211            when seconds is 0. */
12212         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12213       } else {
12214         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12215                 seconds, inc/1000);
12216       }
12217     }
12218     SendToProgram(buf, cps);
12219
12220     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12221     /* Orthogonally, limit search to given depth */
12222     if (sd > 0) {
12223       if (cps->sdKludge) {
12224         sprintf(buf, "depth\n%d\n", sd);
12225       } else {
12226         sprintf(buf, "sd %d\n", sd);
12227       }
12228       SendToProgram(buf, cps);
12229     }
12230
12231     if(cps->nps > 0) { /* [HGM] nps */
12232         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12233         else {
12234                 sprintf(buf, "nps %d\n", cps->nps);
12235               SendToProgram(buf, cps);
12236         }
12237     }
12238 }
12239
12240 ChessProgramState *WhitePlayer()
12241 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12242 {
12243     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12244        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12245         return &second;
12246     return &first;
12247 }
12248
12249 void
12250 SendTimeRemaining(cps, machineWhite)
12251      ChessProgramState *cps;
12252      int /*boolean*/ machineWhite;
12253 {
12254     char message[MSG_SIZ];
12255     long time, otime;
12256
12257     /* Note: this routine must be called when the clocks are stopped
12258        or when they have *just* been set or switched; otherwise
12259        it will be off by the time since the current tick started.
12260     */
12261     if (machineWhite) {
12262         time = whiteTimeRemaining / 10;
12263         otime = blackTimeRemaining / 10;
12264     } else {
12265         time = blackTimeRemaining / 10;
12266         otime = whiteTimeRemaining / 10;
12267     }
12268     /* [HGM] translate opponent's time by time-odds factor */
12269     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12270     if (appData.debugMode) {
12271         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12272     }
12273
12274     if (time <= 0) time = 1;
12275     if (otime <= 0) otime = 1;
12276     
12277     sprintf(message, "time %ld\n", time);
12278     SendToProgram(message, cps);
12279
12280     sprintf(message, "otim %ld\n", otime);
12281     SendToProgram(message, cps);
12282 }
12283
12284 int
12285 BoolFeature(p, name, loc, cps)
12286      char **p;
12287      char *name;
12288      int *loc;
12289      ChessProgramState *cps;
12290 {
12291   char buf[MSG_SIZ];
12292   int len = strlen(name);
12293   int val;
12294   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12295     (*p) += len + 1;
12296     sscanf(*p, "%d", &val);
12297     *loc = (val != 0);
12298     while (**p && **p != ' ') (*p)++;
12299     sprintf(buf, "accepted %s\n", name);
12300     SendToProgram(buf, cps);
12301     return TRUE;
12302   }
12303   return FALSE;
12304 }
12305
12306 int
12307 IntFeature(p, name, loc, cps)
12308      char **p;
12309      char *name;
12310      int *loc;
12311      ChessProgramState *cps;
12312 {
12313   char buf[MSG_SIZ];
12314   int len = strlen(name);
12315   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12316     (*p) += len + 1;
12317     sscanf(*p, "%d", loc);
12318     while (**p && **p != ' ') (*p)++;
12319     sprintf(buf, "accepted %s\n", name);
12320     SendToProgram(buf, cps);
12321     return TRUE;
12322   }
12323   return FALSE;
12324 }
12325
12326 int
12327 StringFeature(p, name, loc, cps)
12328      char **p;
12329      char *name;
12330      char loc[];
12331      ChessProgramState *cps;
12332 {
12333   char buf[MSG_SIZ];
12334   int len = strlen(name);
12335   if (strncmp((*p), name, len) == 0
12336       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12337     (*p) += len + 2;
12338     sscanf(*p, "%[^\"]", loc);
12339     while (**p && **p != '\"') (*p)++;
12340     if (**p == '\"') (*p)++;
12341     sprintf(buf, "accepted %s\n", name);
12342     SendToProgram(buf, cps);
12343     return TRUE;
12344   }
12345   return FALSE;
12346 }
12347
12348 int 
12349 ParseOption(Option *opt, ChessProgramState *cps)
12350 // [HGM] options: process the string that defines an engine option, and determine
12351 // name, type, default value, and allowed value range
12352 {
12353         char *p, *q, buf[MSG_SIZ];
12354         int n, min = (-1)<<31, max = 1<<31, def;
12355
12356         if(p = strstr(opt->name, " -spin ")) {
12357             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12358             if(max < min) max = min; // enforce consistency
12359             if(def < min) def = min;
12360             if(def > max) def = max;
12361             opt->value = def;
12362             opt->min = min;
12363             opt->max = max;
12364             opt->type = Spin;
12365         } else if((p = strstr(opt->name, " -slider "))) {
12366             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12367             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12368             if(max < min) max = min; // enforce consistency
12369             if(def < min) def = min;
12370             if(def > max) def = max;
12371             opt->value = def;
12372             opt->min = min;
12373             opt->max = max;
12374             opt->type = Spin; // Slider;
12375         } else if((p = strstr(opt->name, " -string "))) {
12376             opt->textValue = p+9;
12377             opt->type = TextBox;
12378         } else if((p = strstr(opt->name, " -file "))) {
12379             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12380             opt->textValue = p+7;
12381             opt->type = TextBox; // FileName;
12382         } else if((p = strstr(opt->name, " -path "))) {
12383             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12384             opt->textValue = p+7;
12385             opt->type = TextBox; // PathName;
12386         } else if(p = strstr(opt->name, " -check ")) {
12387             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12388             opt->value = (def != 0);
12389             opt->type = CheckBox;
12390         } else if(p = strstr(opt->name, " -combo ")) {
12391             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12392             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12393             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12394             opt->value = n = 0;
12395             while(q = StrStr(q, " /// ")) {
12396                 n++; *q = 0;    // count choices, and null-terminate each of them
12397                 q += 5;
12398                 if(*q == '*') { // remember default, which is marked with * prefix
12399                     q++;
12400                     opt->value = n;
12401                 }
12402                 cps->comboList[cps->comboCnt++] = q;
12403             }
12404             cps->comboList[cps->comboCnt++] = NULL;
12405             opt->max = n + 1;
12406             opt->type = ComboBox;
12407         } else if(p = strstr(opt->name, " -button")) {
12408             opt->type = Button;
12409         } else if(p = strstr(opt->name, " -save")) {
12410             opt->type = SaveButton;
12411         } else return FALSE;
12412         *p = 0; // terminate option name
12413         // now look if the command-line options define a setting for this engine option.
12414         if(cps->optionSettings && cps->optionSettings[0])
12415             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12416         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12417                 sprintf(buf, "option %s", p);
12418                 if(p = strstr(buf, ",")) *p = 0;
12419                 strcat(buf, "\n");
12420                 SendToProgram(buf, cps);
12421         }
12422         return TRUE;
12423 }
12424
12425 void
12426 FeatureDone(cps, val)
12427      ChessProgramState* cps;
12428      int val;
12429 {
12430   DelayedEventCallback cb = GetDelayedEvent();
12431   if ((cb == InitBackEnd3 && cps == &first) ||
12432       (cb == TwoMachinesEventIfReady && cps == &second)) {
12433     CancelDelayedEvent();
12434     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12435   }
12436   cps->initDone = val;
12437 }
12438
12439 /* Parse feature command from engine */
12440 void
12441 ParseFeatures(args, cps)
12442      char* args;
12443      ChessProgramState *cps;  
12444 {
12445   char *p = args;
12446   char *q;
12447   int val;
12448   char buf[MSG_SIZ];
12449
12450   for (;;) {
12451     while (*p == ' ') p++;
12452     if (*p == NULLCHAR) return;
12453
12454     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12455     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12456     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12457     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12458     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12459     if (BoolFeature(&p, "reuse", &val, cps)) {
12460       /* Engine can disable reuse, but can't enable it if user said no */
12461       if (!val) cps->reuse = FALSE;
12462       continue;
12463     }
12464     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12465     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12466       if (gameMode == TwoMachinesPlay) {
12467         DisplayTwoMachinesTitle();
12468       } else {
12469         DisplayTitle("");
12470       }
12471       continue;
12472     }
12473     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12474     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12475     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12476     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12477     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12478     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12479     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12480     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12481     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12482     if (IntFeature(&p, "done", &val, cps)) {
12483       FeatureDone(cps, val);
12484       continue;
12485     }
12486     /* Added by Tord: */
12487     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12488     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12489     /* End of additions by Tord */
12490
12491     /* [HGM] added features: */
12492     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12493     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12494     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12495     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12496     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12497     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12498     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12499         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12500             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12501             SendToProgram(buf, cps);
12502             continue;
12503         }
12504         if(cps->nrOptions >= MAX_OPTIONS) {
12505             cps->nrOptions--;
12506             sprintf(buf, "%s engine has too many options\n", cps->which);
12507             DisplayError(buf, 0);
12508         }
12509         continue;
12510     }
12511     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12512     /* End of additions by HGM */
12513
12514     /* unknown feature: complain and skip */
12515     q = p;
12516     while (*q && *q != '=') q++;
12517     sprintf(buf, "rejected %.*s\n", q-p, p);
12518     SendToProgram(buf, cps);
12519     p = q;
12520     if (*p == '=') {
12521       p++;
12522       if (*p == '\"') {
12523         p++;
12524         while (*p && *p != '\"') p++;
12525         if (*p == '\"') p++;
12526       } else {
12527         while (*p && *p != ' ') p++;
12528       }
12529     }
12530   }
12531
12532 }
12533
12534 void
12535 PeriodicUpdatesEvent(newState)
12536      int newState;
12537 {
12538     if (newState == appData.periodicUpdates)
12539       return;
12540
12541     appData.periodicUpdates=newState;
12542
12543     /* Display type changes, so update it now */
12544     DisplayAnalysis();
12545
12546     /* Get the ball rolling again... */
12547     if (newState) {
12548         AnalysisPeriodicEvent(1);
12549         StartAnalysisClock();
12550     }
12551 }
12552
12553 void
12554 PonderNextMoveEvent(newState)
12555      int newState;
12556 {
12557     if (newState == appData.ponderNextMove) return;
12558     if (gameMode == EditPosition) EditPositionDone();
12559     if (newState) {
12560         SendToProgram("hard\n", &first);
12561         if (gameMode == TwoMachinesPlay) {
12562             SendToProgram("hard\n", &second);
12563         }
12564     } else {
12565         SendToProgram("easy\n", &first);
12566         thinkOutput[0] = NULLCHAR;
12567         if (gameMode == TwoMachinesPlay) {
12568             SendToProgram("easy\n", &second);
12569         }
12570     }
12571     appData.ponderNextMove = newState;
12572 }
12573
12574 void
12575 NewSettingEvent(option, command, value)
12576      char *command;
12577      int option, value;
12578 {
12579     char buf[MSG_SIZ];
12580
12581     if (gameMode == EditPosition) EditPositionDone();
12582     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12583     SendToProgram(buf, &first);
12584     if (gameMode == TwoMachinesPlay) {
12585         SendToProgram(buf, &second);
12586     }
12587 }
12588
12589 void
12590 ShowThinkingEvent()
12591 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12592 {
12593     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12594     int newState = appData.showThinking
12595         // [HGM] thinking: other features now need thinking output as well
12596         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12597     
12598     if (oldState == newState) return;
12599     oldState = newState;
12600     if (gameMode == EditPosition) EditPositionDone();
12601     if (oldState) {
12602         SendToProgram("post\n", &first);
12603         if (gameMode == TwoMachinesPlay) {
12604             SendToProgram("post\n", &second);
12605         }
12606     } else {
12607         SendToProgram("nopost\n", &first);
12608         thinkOutput[0] = NULLCHAR;
12609         if (gameMode == TwoMachinesPlay) {
12610             SendToProgram("nopost\n", &second);
12611         }
12612     }
12613 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12614 }
12615
12616 void
12617 AskQuestionEvent(title, question, replyPrefix, which)
12618      char *title; char *question; char *replyPrefix; char *which;
12619 {
12620   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12621   if (pr == NoProc) return;
12622   AskQuestion(title, question, replyPrefix, pr);
12623 }
12624
12625 void
12626 DisplayMove(moveNumber)
12627      int moveNumber;
12628 {
12629     char message[MSG_SIZ];
12630     char res[MSG_SIZ];
12631     char cpThinkOutput[MSG_SIZ];
12632
12633     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12634     
12635     if (moveNumber == forwardMostMove - 1 || 
12636         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12637
12638         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12639
12640         if (strchr(cpThinkOutput, '\n')) {
12641             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12642         }
12643     } else {
12644         *cpThinkOutput = NULLCHAR;
12645     }
12646
12647     /* [AS] Hide thinking from human user */
12648     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12649         *cpThinkOutput = NULLCHAR;
12650         if( thinkOutput[0] != NULLCHAR ) {
12651             int i;
12652
12653             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12654                 cpThinkOutput[i] = '.';
12655             }
12656             cpThinkOutput[i] = NULLCHAR;
12657             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12658         }
12659     }
12660
12661     if (moveNumber == forwardMostMove - 1 &&
12662         gameInfo.resultDetails != NULL) {
12663         if (gameInfo.resultDetails[0] == NULLCHAR) {
12664             sprintf(res, " %s", PGNResult(gameInfo.result));
12665         } else {
12666             sprintf(res, " {%s} %s",
12667                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12668         }
12669     } else {
12670         res[0] = NULLCHAR;
12671     }
12672
12673     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12674         DisplayMessage(res, cpThinkOutput);
12675     } else {
12676         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12677                 WhiteOnMove(moveNumber) ? " " : ".. ",
12678                 parseList[moveNumber], res);
12679         DisplayMessage(message, cpThinkOutput);
12680     }
12681 }
12682
12683 void
12684 DisplayAnalysisText(text)
12685      char *text;
12686 {
12687     char buf[MSG_SIZ];
12688
12689     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12690                || appData.icsEngineAnalyze) {
12691         sprintf(buf, "Analysis (%s)", first.tidy);
12692         AnalysisPopUp(buf, text);
12693     }
12694 }
12695
12696 static int
12697 only_one_move(str)
12698      char *str;
12699 {
12700     while (*str && isspace(*str)) ++str;
12701     while (*str && !isspace(*str)) ++str;
12702     if (!*str) return 1;
12703     while (*str && isspace(*str)) ++str;
12704     if (!*str) return 1;
12705     return 0;
12706 }
12707
12708 void
12709 DisplayAnalysis()
12710 {
12711     char buf[MSG_SIZ];
12712     char lst[MSG_SIZ / 2];
12713     double nps;
12714     static char *xtra[] = { "", " (--)", " (++)" };
12715     int h, m, s, cs;
12716   
12717     if (programStats.time == 0) {
12718         programStats.time = 1;
12719     }
12720   
12721     if (programStats.got_only_move) {
12722         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12723     } else {
12724         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12725
12726         nps = (u64ToDouble(programStats.nodes) /
12727              ((double)programStats.time /100.0));
12728
12729         cs = programStats.time % 100;
12730         s = programStats.time / 100;
12731         h = (s / (60*60));
12732         s = s - h*60*60;
12733         m = (s/60);
12734         s = s - m*60;
12735
12736         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12737           if (programStats.move_name[0] != NULLCHAR) {
12738             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12739                     programStats.depth,
12740                     programStats.nr_moves-programStats.moves_left,
12741                     programStats.nr_moves, programStats.move_name,
12742                     ((float)programStats.score)/100.0, lst,
12743                     only_one_move(lst)?
12744                     xtra[programStats.got_fail] : "",
12745                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12746           } else {
12747             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12748                     programStats.depth,
12749                     programStats.nr_moves-programStats.moves_left,
12750                     programStats.nr_moves, ((float)programStats.score)/100.0,
12751                     lst,
12752                     only_one_move(lst)?
12753                     xtra[programStats.got_fail] : "",
12754                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12755           }
12756         } else {
12757             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12758                     programStats.depth,
12759                     ((float)programStats.score)/100.0,
12760                     lst,
12761                     only_one_move(lst)?
12762                     xtra[programStats.got_fail] : "",
12763                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12764         }
12765     }
12766     DisplayAnalysisText(buf);
12767 }
12768
12769 void
12770 DisplayComment(moveNumber, text)
12771      int moveNumber;
12772      char *text;
12773 {
12774     char title[MSG_SIZ];
12775     char buf[8000]; // comment can be long!
12776     int score, depth;
12777
12778     if( appData.autoDisplayComment ) {
12779         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12780             strcpy(title, "Comment");
12781         } else {
12782             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12783                     WhiteOnMove(moveNumber) ? " " : ".. ",
12784                     parseList[moveNumber]);
12785         }
12786         // [HGM] PV info: display PV info together with (or as) comment
12787         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12788             if(text == NULL) text = "";                                           
12789             score = pvInfoList[moveNumber].score;
12790             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12791                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12792             text = buf;
12793         }
12794     } else title[0] = 0;
12795
12796     if (text != NULL)
12797         CommentPopUp(title, text);
12798 }
12799
12800 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12801  * might be busy thinking or pondering.  It can be omitted if your
12802  * gnuchess is configured to stop thinking immediately on any user
12803  * input.  However, that gnuchess feature depends on the FIONREAD
12804  * ioctl, which does not work properly on some flavors of Unix.
12805  */
12806 void
12807 Attention(cps)
12808      ChessProgramState *cps;
12809 {
12810 #if ATTENTION
12811     if (!cps->useSigint) return;
12812     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12813     switch (gameMode) {
12814       case MachinePlaysWhite:
12815       case MachinePlaysBlack:
12816       case TwoMachinesPlay:
12817       case IcsPlayingWhite:
12818       case IcsPlayingBlack:
12819       case AnalyzeMode:
12820       case AnalyzeFile:
12821         /* Skip if we know it isn't thinking */
12822         if (!cps->maybeThinking) return;
12823         if (appData.debugMode)
12824           fprintf(debugFP, "Interrupting %s\n", cps->which);
12825         InterruptChildProcess(cps->pr);
12826         cps->maybeThinking = FALSE;
12827         break;
12828       default:
12829         break;
12830     }
12831 #endif /*ATTENTION*/
12832 }
12833
12834 int
12835 CheckFlags()
12836 {
12837     if (whiteTimeRemaining <= 0) {
12838         if (!whiteFlag) {
12839             whiteFlag = TRUE;
12840             if (appData.icsActive) {
12841                 if (appData.autoCallFlag &&
12842                     gameMode == IcsPlayingBlack && !blackFlag) {
12843                   SendToICS(ics_prefix);
12844                   SendToICS("flag\n");
12845                 }
12846             } else {
12847                 if (blackFlag) {
12848                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12849                 } else {
12850                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12851                     if (appData.autoCallFlag) {
12852                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12853                         return TRUE;
12854                     }
12855                 }
12856             }
12857         }
12858     }
12859     if (blackTimeRemaining <= 0) {
12860         if (!blackFlag) {
12861             blackFlag = TRUE;
12862             if (appData.icsActive) {
12863                 if (appData.autoCallFlag &&
12864                     gameMode == IcsPlayingWhite && !whiteFlag) {
12865                   SendToICS(ics_prefix);
12866                   SendToICS("flag\n");
12867                 }
12868             } else {
12869                 if (whiteFlag) {
12870                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12871                 } else {
12872                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12873                     if (appData.autoCallFlag) {
12874                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12875                         return TRUE;
12876                     }
12877                 }
12878             }
12879         }
12880     }
12881     return FALSE;
12882 }
12883
12884 void
12885 CheckTimeControl()
12886 {
12887     if (!appData.clockMode || appData.icsActive ||
12888         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12889
12890     /*
12891      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12892      */
12893     if ( !WhiteOnMove(forwardMostMove) )
12894         /* White made time control */
12895         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12896         /* [HGM] time odds: correct new time quota for time odds! */
12897                                             / WhitePlayer()->timeOdds;
12898       else
12899         /* Black made time control */
12900         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12901                                             / WhitePlayer()->other->timeOdds;
12902 }
12903
12904 void
12905 DisplayBothClocks()
12906 {
12907     int wom = gameMode == EditPosition ?
12908       !blackPlaysFirst : WhiteOnMove(currentMove);
12909     DisplayWhiteClock(whiteTimeRemaining, wom);
12910     DisplayBlackClock(blackTimeRemaining, !wom);
12911 }
12912
12913
12914 /* Timekeeping seems to be a portability nightmare.  I think everyone
12915    has ftime(), but I'm really not sure, so I'm including some ifdefs
12916    to use other calls if you don't.  Clocks will be less accurate if
12917    you have neither ftime nor gettimeofday.
12918 */
12919
12920 /* VS 2008 requires the #include outside of the function */
12921 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12922 #include <sys/timeb.h>
12923 #endif
12924
12925 /* Get the current time as a TimeMark */
12926 void
12927 GetTimeMark(tm)
12928      TimeMark *tm;
12929 {
12930 #if HAVE_GETTIMEOFDAY
12931
12932     struct timeval timeVal;
12933     struct timezone timeZone;
12934
12935     gettimeofday(&timeVal, &timeZone);
12936     tm->sec = (long) timeVal.tv_sec; 
12937     tm->ms = (int) (timeVal.tv_usec / 1000L);
12938
12939 #else /*!HAVE_GETTIMEOFDAY*/
12940 #if HAVE_FTIME
12941
12942 // include <sys/timeb.h> / moved to just above start of function
12943     struct timeb timeB;
12944
12945     ftime(&timeB);
12946     tm->sec = (long) timeB.time;
12947     tm->ms = (int) timeB.millitm;
12948
12949 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12950     tm->sec = (long) time(NULL);
12951     tm->ms = 0;
12952 #endif
12953 #endif
12954 }
12955
12956 /* Return the difference in milliseconds between two
12957    time marks.  We assume the difference will fit in a long!
12958 */
12959 long
12960 SubtractTimeMarks(tm2, tm1)
12961      TimeMark *tm2, *tm1;
12962 {
12963     return 1000L*(tm2->sec - tm1->sec) +
12964            (long) (tm2->ms - tm1->ms);
12965 }
12966
12967
12968 /*
12969  * Code to manage the game clocks.
12970  *
12971  * In tournament play, black starts the clock and then white makes a move.
12972  * We give the human user a slight advantage if he is playing white---the
12973  * clocks don't run until he makes his first move, so it takes zero time.
12974  * Also, we don't account for network lag, so we could get out of sync
12975  * with GNU Chess's clock -- but then, referees are always right.  
12976  */
12977
12978 static TimeMark tickStartTM;
12979 static long intendedTickLength;
12980
12981 long
12982 NextTickLength(timeRemaining)
12983      long timeRemaining;
12984 {
12985     long nominalTickLength, nextTickLength;
12986
12987     if (timeRemaining > 0L && timeRemaining <= 10000L)
12988       nominalTickLength = 100L;
12989     else
12990       nominalTickLength = 1000L;
12991     nextTickLength = timeRemaining % nominalTickLength;
12992     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12993
12994     return nextTickLength;
12995 }
12996
12997 /* Adjust clock one minute up or down */
12998 void
12999 AdjustClock(Boolean which, int dir)
13000 {
13001     if(which) blackTimeRemaining += 60000*dir;
13002     else      whiteTimeRemaining += 60000*dir;
13003     DisplayBothClocks();
13004 }
13005
13006 /* Stop clocks and reset to a fresh time control */
13007 void
13008 ResetClocks() 
13009 {
13010     (void) StopClockTimer();
13011     if (appData.icsActive) {
13012         whiteTimeRemaining = blackTimeRemaining = 0;
13013     } else { /* [HGM] correct new time quote for time odds */
13014         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13015         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13016     }
13017     if (whiteFlag || blackFlag) {
13018         DisplayTitle("");
13019         whiteFlag = blackFlag = FALSE;
13020     }
13021     DisplayBothClocks();
13022 }
13023
13024 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13025
13026 /* Decrement running clock by amount of time that has passed */
13027 void
13028 DecrementClocks()
13029 {
13030     long timeRemaining;
13031     long lastTickLength, fudge;
13032     TimeMark now;
13033
13034     if (!appData.clockMode) return;
13035     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13036         
13037     GetTimeMark(&now);
13038
13039     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13040
13041     /* Fudge if we woke up a little too soon */
13042     fudge = intendedTickLength - lastTickLength;
13043     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13044
13045     if (WhiteOnMove(forwardMostMove)) {
13046         if(whiteNPS >= 0) lastTickLength = 0;
13047         timeRemaining = whiteTimeRemaining -= lastTickLength;
13048         DisplayWhiteClock(whiteTimeRemaining - fudge,
13049                           WhiteOnMove(currentMove));
13050     } else {
13051         if(blackNPS >= 0) lastTickLength = 0;
13052         timeRemaining = blackTimeRemaining -= lastTickLength;
13053         DisplayBlackClock(blackTimeRemaining - fudge,
13054                           !WhiteOnMove(currentMove));
13055     }
13056
13057     if (CheckFlags()) return;
13058         
13059     tickStartTM = now;
13060     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13061     StartClockTimer(intendedTickLength);
13062
13063     /* if the time remaining has fallen below the alarm threshold, sound the
13064      * alarm. if the alarm has sounded and (due to a takeback or time control
13065      * with increment) the time remaining has increased to a level above the
13066      * threshold, reset the alarm so it can sound again. 
13067      */
13068     
13069     if (appData.icsActive && appData.icsAlarm) {
13070
13071         /* make sure we are dealing with the user's clock */
13072         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13073                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13074            )) return;
13075
13076         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13077             alarmSounded = FALSE;
13078         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13079             PlayAlarmSound();
13080             alarmSounded = TRUE;
13081         }
13082     }
13083 }
13084
13085
13086 /* A player has just moved, so stop the previously running
13087    clock and (if in clock mode) start the other one.
13088    We redisplay both clocks in case we're in ICS mode, because
13089    ICS gives us an update to both clocks after every move.
13090    Note that this routine is called *after* forwardMostMove
13091    is updated, so the last fractional tick must be subtracted
13092    from the color that is *not* on move now.
13093 */
13094 void
13095 SwitchClocks()
13096 {
13097     long lastTickLength;
13098     TimeMark now;
13099     int flagged = FALSE;
13100
13101     GetTimeMark(&now);
13102
13103     if (StopClockTimer() && appData.clockMode) {
13104         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13105         if (WhiteOnMove(forwardMostMove)) {
13106             if(blackNPS >= 0) lastTickLength = 0;
13107             blackTimeRemaining -= lastTickLength;
13108            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13109 //         if(pvInfoList[forwardMostMove-1].time == -1)
13110                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13111                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13112         } else {
13113            if(whiteNPS >= 0) lastTickLength = 0;
13114            whiteTimeRemaining -= lastTickLength;
13115            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13116 //         if(pvInfoList[forwardMostMove-1].time == -1)
13117                  pvInfoList[forwardMostMove-1].time = 
13118                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13119         }
13120         flagged = CheckFlags();
13121     }
13122     CheckTimeControl();
13123
13124     if (flagged || !appData.clockMode) return;
13125
13126     switch (gameMode) {
13127       case MachinePlaysBlack:
13128       case MachinePlaysWhite:
13129       case BeginningOfGame:
13130         if (pausing) return;
13131         break;
13132
13133       case EditGame:
13134       case PlayFromGameFile:
13135       case IcsExamining:
13136         return;
13137
13138       default:
13139         break;
13140     }
13141
13142     tickStartTM = now;
13143     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13144       whiteTimeRemaining : blackTimeRemaining);
13145     StartClockTimer(intendedTickLength);
13146 }
13147         
13148
13149 /* Stop both clocks */
13150 void
13151 StopClocks()
13152 {       
13153     long lastTickLength;
13154     TimeMark now;
13155
13156     if (!StopClockTimer()) return;
13157     if (!appData.clockMode) return;
13158
13159     GetTimeMark(&now);
13160
13161     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13162     if (WhiteOnMove(forwardMostMove)) {
13163         if(whiteNPS >= 0) lastTickLength = 0;
13164         whiteTimeRemaining -= lastTickLength;
13165         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13166     } else {
13167         if(blackNPS >= 0) lastTickLength = 0;
13168         blackTimeRemaining -= lastTickLength;
13169         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13170     }
13171     CheckFlags();
13172 }
13173         
13174 /* Start clock of player on move.  Time may have been reset, so
13175    if clock is already running, stop and restart it. */
13176 void
13177 StartClocks()
13178 {
13179     (void) StopClockTimer(); /* in case it was running already */
13180     DisplayBothClocks();
13181     if (CheckFlags()) return;
13182
13183     if (!appData.clockMode) return;
13184     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13185
13186     GetTimeMark(&tickStartTM);
13187     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13188       whiteTimeRemaining : blackTimeRemaining);
13189
13190    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13191     whiteNPS = blackNPS = -1; 
13192     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13193        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13194         whiteNPS = first.nps;
13195     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13196        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13197         blackNPS = first.nps;
13198     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13199         whiteNPS = second.nps;
13200     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13201         blackNPS = second.nps;
13202     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13203
13204     StartClockTimer(intendedTickLength);
13205 }
13206
13207 char *
13208 TimeString(ms)
13209      long ms;
13210 {
13211     long second, minute, hour, day;
13212     char *sign = "";
13213     static char buf[32];
13214     
13215     if (ms > 0 && ms <= 9900) {
13216       /* convert milliseconds to tenths, rounding up */
13217       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13218
13219       sprintf(buf, " %03.1f ", tenths/10.0);
13220       return buf;
13221     }
13222
13223     /* convert milliseconds to seconds, rounding up */
13224     /* use floating point to avoid strangeness of integer division
13225        with negative dividends on many machines */
13226     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13227
13228     if (second < 0) {
13229         sign = "-";
13230         second = -second;
13231     }
13232     
13233     day = second / (60 * 60 * 24);
13234     second = second % (60 * 60 * 24);
13235     hour = second / (60 * 60);
13236     second = second % (60 * 60);
13237     minute = second / 60;
13238     second = second % 60;
13239     
13240     if (day > 0)
13241       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13242               sign, day, hour, minute, second);
13243     else if (hour > 0)
13244       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13245     else
13246       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13247     
13248     return buf;
13249 }
13250
13251
13252 /*
13253  * This is necessary because some C libraries aren't ANSI C compliant yet.
13254  */
13255 char *
13256 StrStr(string, match)
13257      char *string, *match;
13258 {
13259     int i, length;
13260     
13261     length = strlen(match);
13262     
13263     for (i = strlen(string) - length; i >= 0; i--, string++)
13264       if (!strncmp(match, string, length))
13265         return string;
13266     
13267     return NULL;
13268 }
13269
13270 char *
13271 StrCaseStr(string, match)
13272      char *string, *match;
13273 {
13274     int i, j, length;
13275     
13276     length = strlen(match);
13277     
13278     for (i = strlen(string) - length; i >= 0; i--, string++) {
13279         for (j = 0; j < length; j++) {
13280             if (ToLower(match[j]) != ToLower(string[j]))
13281               break;
13282         }
13283         if (j == length) return string;
13284     }
13285
13286     return NULL;
13287 }
13288
13289 #ifndef _amigados
13290 int
13291 StrCaseCmp(s1, s2)
13292      char *s1, *s2;
13293 {
13294     char c1, c2;
13295     
13296     for (;;) {
13297         c1 = ToLower(*s1++);
13298         c2 = ToLower(*s2++);
13299         if (c1 > c2) return 1;
13300         if (c1 < c2) return -1;
13301         if (c1 == NULLCHAR) return 0;
13302     }
13303 }
13304
13305
13306 int
13307 ToLower(c)
13308      int c;
13309 {
13310     return isupper(c) ? tolower(c) : c;
13311 }
13312
13313
13314 int
13315 ToUpper(c)
13316      int c;
13317 {
13318     return islower(c) ? toupper(c) : c;
13319 }
13320 #endif /* !_amigados    */
13321
13322 char *
13323 StrSave(s)
13324      char *s;
13325 {
13326     char *ret;
13327
13328     if ((ret = (char *) malloc(strlen(s) + 1))) {
13329         strcpy(ret, s);
13330     }
13331     return ret;
13332 }
13333
13334 char *
13335 StrSavePtr(s, savePtr)
13336      char *s, **savePtr;
13337 {
13338     if (*savePtr) {
13339         free(*savePtr);
13340     }
13341     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13342         strcpy(*savePtr, s);
13343     }
13344     return(*savePtr);
13345 }
13346
13347 char *
13348 PGNDate()
13349 {
13350     time_t clock;
13351     struct tm *tm;
13352     char buf[MSG_SIZ];
13353
13354     clock = time((time_t *)NULL);
13355     tm = localtime(&clock);
13356     sprintf(buf, "%04d.%02d.%02d",
13357             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13358     return StrSave(buf);
13359 }
13360
13361
13362 char *
13363 PositionToFEN(move, overrideCastling)
13364      int move;
13365      char *overrideCastling;
13366 {
13367     int i, j, fromX, fromY, toX, toY;
13368     int whiteToPlay;
13369     char buf[128];
13370     char *p, *q;
13371     int emptycount;
13372     ChessSquare piece;
13373
13374     whiteToPlay = (gameMode == EditPosition) ?
13375       !blackPlaysFirst : (move % 2 == 0);
13376     p = buf;
13377
13378     /* Piece placement data */
13379     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13380         emptycount = 0;
13381         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13382             if (boards[move][i][j] == EmptySquare) {
13383                 emptycount++;
13384             } else { ChessSquare piece = boards[move][i][j];
13385                 if (emptycount > 0) {
13386                     if(emptycount<10) /* [HGM] can be >= 10 */
13387                         *p++ = '0' + emptycount;
13388                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13389                     emptycount = 0;
13390                 }
13391                 if(PieceToChar(piece) == '+') {
13392                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13393                     *p++ = '+';
13394                     piece = (ChessSquare)(DEMOTED piece);
13395                 } 
13396                 *p++ = PieceToChar(piece);
13397                 if(p[-1] == '~') {
13398                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13399                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13400                     *p++ = '~';
13401                 }
13402             }
13403         }
13404         if (emptycount > 0) {
13405             if(emptycount<10) /* [HGM] can be >= 10 */
13406                 *p++ = '0' + emptycount;
13407             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13408             emptycount = 0;
13409         }
13410         *p++ = '/';
13411     }
13412     *(p - 1) = ' ';
13413
13414     /* [HGM] print Crazyhouse or Shogi holdings */
13415     if( gameInfo.holdingsWidth ) {
13416         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13417         q = p;
13418         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13419             piece = boards[move][i][BOARD_WIDTH-1];
13420             if( piece != EmptySquare )
13421               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13422                   *p++ = PieceToChar(piece);
13423         }
13424         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13425             piece = boards[move][BOARD_HEIGHT-i-1][0];
13426             if( piece != EmptySquare )
13427               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13428                   *p++ = PieceToChar(piece);
13429         }
13430
13431         if( q == p ) *p++ = '-';
13432         *p++ = ']';
13433         *p++ = ' ';
13434     }
13435
13436     /* Active color */
13437     *p++ = whiteToPlay ? 'w' : 'b';
13438     *p++ = ' ';
13439
13440   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13441     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13442   } else {
13443   if(nrCastlingRights) {
13444      q = p;
13445      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13446        /* [HGM] write directly from rights */
13447            if(castlingRights[move][2] >= 0 &&
13448               castlingRights[move][0] >= 0   )
13449                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13450            if(castlingRights[move][2] >= 0 &&
13451               castlingRights[move][1] >= 0   )
13452                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13453            if(castlingRights[move][5] >= 0 &&
13454               castlingRights[move][3] >= 0   )
13455                 *p++ = castlingRights[move][3] + AAA;
13456            if(castlingRights[move][5] >= 0 &&
13457               castlingRights[move][4] >= 0   )
13458                 *p++ = castlingRights[move][4] + AAA;
13459      } else {
13460
13461         /* [HGM] write true castling rights */
13462         if( nrCastlingRights == 6 ) {
13463             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13464                castlingRights[move][2] >= 0  ) *p++ = 'K';
13465             if(castlingRights[move][1] == BOARD_LEFT &&
13466                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13467             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13468                castlingRights[move][5] >= 0  ) *p++ = 'k';
13469             if(castlingRights[move][4] == BOARD_LEFT &&
13470                castlingRights[move][5] >= 0  ) *p++ = 'q';
13471         }
13472      }
13473      if (q == p) *p++ = '-'; /* No castling rights */
13474      *p++ = ' ';
13475   }
13476
13477   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13478      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13479     /* En passant target square */
13480     if (move > backwardMostMove) {
13481         fromX = moveList[move - 1][0] - AAA;
13482         fromY = moveList[move - 1][1] - ONE;
13483         toX = moveList[move - 1][2] - AAA;
13484         toY = moveList[move - 1][3] - ONE;
13485         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13486             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13487             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13488             fromX == toX) {
13489             /* 2-square pawn move just happened */
13490             *p++ = toX + AAA;
13491             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13492         } else {
13493             *p++ = '-';
13494         }
13495     } else {
13496         *p++ = '-';
13497     }
13498     *p++ = ' ';
13499   }
13500   }
13501
13502     /* [HGM] find reversible plies */
13503     {   int i = 0, j=move;
13504
13505         if (appData.debugMode) { int k;
13506             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13507             for(k=backwardMostMove; k<=forwardMostMove; k++)
13508                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13509
13510         }
13511
13512         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13513         if( j == backwardMostMove ) i += initialRulePlies;
13514         sprintf(p, "%d ", i);
13515         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13516     }
13517     /* Fullmove number */
13518     sprintf(p, "%d", (move / 2) + 1);
13519     
13520     return StrSave(buf);
13521 }
13522
13523 Boolean
13524 ParseFEN(board, blackPlaysFirst, fen)
13525     Board board;
13526      int *blackPlaysFirst;
13527      char *fen;
13528 {
13529     int i, j;
13530     char *p;
13531     int emptycount;
13532     ChessSquare piece;
13533
13534     p = fen;
13535
13536     /* [HGM] by default clear Crazyhouse holdings, if present */
13537     if(gameInfo.holdingsWidth) {
13538        for(i=0; i<BOARD_HEIGHT; i++) {
13539            board[i][0]             = EmptySquare; /* black holdings */
13540            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13541            board[i][1]             = (ChessSquare) 0; /* black counts */
13542            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13543        }
13544     }
13545
13546     /* Piece placement data */
13547     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13548         j = 0;
13549         for (;;) {
13550             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13551                 if (*p == '/') p++;
13552                 emptycount = gameInfo.boardWidth - j;
13553                 while (emptycount--)
13554                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13555                 break;
13556 #if(BOARD_SIZE >= 10)
13557             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13558                 p++; emptycount=10;
13559                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13560                 while (emptycount--)
13561                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13562 #endif
13563             } else if (isdigit(*p)) {
13564                 emptycount = *p++ - '0';
13565                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13566                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13567                 while (emptycount--)
13568                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13569             } else if (*p == '+' || isalpha(*p)) {
13570                 if (j >= gameInfo.boardWidth) return FALSE;
13571                 if(*p=='+') {
13572                     piece = CharToPiece(*++p);
13573                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13574                     piece = (ChessSquare) (PROMOTED piece ); p++;
13575                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13576                 } else piece = CharToPiece(*p++);
13577
13578                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13579                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13580                     piece = (ChessSquare) (PROMOTED piece);
13581                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13582                     p++;
13583                 }
13584                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13585             } else {
13586                 return FALSE;
13587             }
13588         }
13589     }
13590     while (*p == '/' || *p == ' ') p++;
13591
13592     /* [HGM] look for Crazyhouse holdings here */
13593     while(*p==' ') p++;
13594     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13595         if(*p == '[') p++;
13596         if(*p == '-' ) *p++; /* empty holdings */ else {
13597             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13598             /* if we would allow FEN reading to set board size, we would   */
13599             /* have to add holdings and shift the board read so far here   */
13600             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13601                 *p++;
13602                 if((int) piece >= (int) BlackPawn ) {
13603                     i = (int)piece - (int)BlackPawn;
13604                     i = PieceToNumber((ChessSquare)i);
13605                     if( i >= gameInfo.holdingsSize ) return FALSE;
13606                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13607                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13608                 } else {
13609                     i = (int)piece - (int)WhitePawn;
13610                     i = PieceToNumber((ChessSquare)i);
13611                     if( i >= gameInfo.holdingsSize ) return FALSE;
13612                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13613                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13614                 }
13615             }
13616         }
13617         if(*p == ']') *p++;
13618     }
13619
13620     while(*p == ' ') p++;
13621
13622     /* Active color */
13623     switch (*p++) {
13624       case 'w':
13625         *blackPlaysFirst = FALSE;
13626         break;
13627       case 'b': 
13628         *blackPlaysFirst = TRUE;
13629         break;
13630       default:
13631         return FALSE;
13632     }
13633
13634     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13635     /* return the extra info in global variiables             */
13636
13637     /* set defaults in case FEN is incomplete */
13638     FENepStatus = EP_UNKNOWN;
13639     for(i=0; i<nrCastlingRights; i++ ) {
13640         FENcastlingRights[i] =
13641             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13642     }   /* assume possible unless obviously impossible */
13643     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13644     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13645     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13646     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13647     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13648     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13649     FENrulePlies = 0;
13650
13651     while(*p==' ') p++;
13652     if(nrCastlingRights) {
13653       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13654           /* castling indicator present, so default becomes no castlings */
13655           for(i=0; i<nrCastlingRights; i++ ) {
13656                  FENcastlingRights[i] = -1;
13657           }
13658       }
13659       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13660              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13661              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13662              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13663         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13664
13665         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13666             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13667             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13668         }
13669         switch(c) {
13670           case'K':
13671               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13672               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13673               FENcastlingRights[2] = whiteKingFile;
13674               break;
13675           case'Q':
13676               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13677               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13678               FENcastlingRights[2] = whiteKingFile;
13679               break;
13680           case'k':
13681               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13682               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13683               FENcastlingRights[5] = blackKingFile;
13684               break;
13685           case'q':
13686               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13687               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13688               FENcastlingRights[5] = blackKingFile;
13689           case '-':
13690               break;
13691           default: /* FRC castlings */
13692               if(c >= 'a') { /* black rights */
13693                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13694                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13695                   if(i == BOARD_RGHT) break;
13696                   FENcastlingRights[5] = i;
13697                   c -= AAA;
13698                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13699                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13700                   if(c > i)
13701                       FENcastlingRights[3] = c;
13702                   else
13703                       FENcastlingRights[4] = c;
13704               } else { /* white rights */
13705                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13706                     if(board[0][i] == WhiteKing) break;
13707                   if(i == BOARD_RGHT) break;
13708                   FENcastlingRights[2] = i;
13709                   c -= AAA - 'a' + 'A';
13710                   if(board[0][c] >= WhiteKing) break;
13711                   if(c > i)
13712                       FENcastlingRights[0] = c;
13713                   else
13714                       FENcastlingRights[1] = c;
13715               }
13716         }
13717       }
13718     if (appData.debugMode) {
13719         fprintf(debugFP, "FEN castling rights:");
13720         for(i=0; i<nrCastlingRights; i++)
13721         fprintf(debugFP, " %d", FENcastlingRights[i]);
13722         fprintf(debugFP, "\n");
13723     }
13724
13725       while(*p==' ') p++;
13726     }
13727
13728     /* read e.p. field in games that know e.p. capture */
13729     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13730        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13731       if(*p=='-') {
13732         p++; FENepStatus = EP_NONE;
13733       } else {
13734          char c = *p++ - AAA;
13735
13736          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13737          if(*p >= '0' && *p <='9') *p++;
13738          FENepStatus = c;
13739       }
13740     }
13741
13742
13743     if(sscanf(p, "%d", &i) == 1) {
13744         FENrulePlies = i; /* 50-move ply counter */
13745         /* (The move number is still ignored)    */
13746     }
13747
13748     return TRUE;
13749 }
13750       
13751 void
13752 EditPositionPasteFEN(char *fen)
13753 {
13754   if (fen != NULL) {
13755     Board initial_position;
13756
13757     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13758       DisplayError(_("Bad FEN position in clipboard"), 0);
13759       return ;
13760     } else {
13761       int savedBlackPlaysFirst = blackPlaysFirst;
13762       EditPositionEvent();
13763       blackPlaysFirst = savedBlackPlaysFirst;
13764       CopyBoard(boards[0], initial_position);
13765           /* [HGM] copy FEN attributes as well */
13766           {   int i;
13767               initialRulePlies = FENrulePlies;
13768               epStatus[0] = FENepStatus;
13769               for( i=0; i<nrCastlingRights; i++ )
13770                   castlingRights[0][i] = FENcastlingRights[i];
13771           }
13772       EditPositionDone();
13773       DisplayBothClocks();
13774       DrawPosition(FALSE, boards[currentMove]);
13775     }
13776   }
13777 }