added ics_printf() and ics_update_width() and utility functions
[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                 }
2229                 SendToICS(str);
2230                 intfSet = TRUE;
2231             }
2232
2233             if (started == STARTED_COMMENT) {
2234                 /* Accumulate characters in comment */
2235                 parse[parse_pos++] = buf[i];
2236                 if (buf[i] == '\n') {
2237                     parse[parse_pos] = NULLCHAR;
2238                     if(chattingPartner>=0) {
2239                         char mess[MSG_SIZ];
2240                         sprintf(mess, "%s%s", talker, parse);
2241                         OutputChatMessage(chattingPartner, mess);
2242                         chattingPartner = -1;
2243                     } else
2244                     if(!suppressKibitz) // [HGM] kibitz
2245                         AppendComment(forwardMostMove, StripHighlight(parse));
2246                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2247                         int nrDigit = 0, nrAlph = 0, i;
2248                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2249                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2250                         parse[parse_pos] = NULLCHAR;
2251                         // try to be smart: if it does not look like search info, it should go to
2252                         // ICS interaction window after all, not to engine-output window.
2253                         for(i=0; i<parse_pos; i++) { // count letters and digits
2254                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2255                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2256                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2257                         }
2258                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2259                             int depth=0; float score;
2260                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2261                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2262                                 pvInfoList[forwardMostMove-1].depth = depth;
2263                                 pvInfoList[forwardMostMove-1].score = 100*score;
2264                             }
2265                             OutputKibitz(suppressKibitz, parse);
2266                         } else {
2267                             char tmp[MSG_SIZ];
2268                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2269                             SendToPlayer(tmp, strlen(tmp));
2270                         }
2271                     }
2272                     started = STARTED_NONE;
2273                 } else {
2274                     /* Don't match patterns against characters in chatter */
2275                     i++;
2276                     continue;
2277                 }
2278             }
2279             if (started == STARTED_CHATTER) {
2280                 if (buf[i] != '\n') {
2281                     /* Don't match patterns against characters in chatter */
2282                     i++;
2283                     continue;
2284                 }
2285                 started = STARTED_NONE;
2286             }
2287
2288             /* Kludge to deal with rcmd protocol */
2289             if (firstTime && looking_at(buf, &i, "\001*")) {
2290                 DisplayFatalError(&buf[1], 0, 1);
2291                 continue;
2292             } else {
2293                 firstTime = FALSE;
2294             }
2295
2296             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2297                 ics_type = ICS_ICC;
2298                 ics_prefix = "/";
2299                 if (appData.debugMode)
2300                   fprintf(debugFP, "ics_type %d\n", ics_type);
2301                 continue;
2302             }
2303             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2304                 ics_type = ICS_FICS;
2305                 ics_prefix = "$";
2306                 if (appData.debugMode)
2307                   fprintf(debugFP, "ics_type %d\n", ics_type);
2308                 continue;
2309             }
2310             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2311                 ics_type = ICS_CHESSNET;
2312                 ics_prefix = "/";
2313                 if (appData.debugMode)
2314                   fprintf(debugFP, "ics_type %d\n", ics_type);
2315                 continue;
2316             }
2317
2318             if (!loggedOn &&
2319                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2320                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2321                  looking_at(buf, &i, "will be \"*\""))) {
2322               strcpy(ics_handle, star_match[0]);
2323               continue;
2324             }
2325
2326             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2327               char buf[MSG_SIZ];
2328               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2329               DisplayIcsInteractionTitle(buf);
2330               have_set_title = TRUE;
2331             }
2332
2333             /* skip finger notes */
2334             if (started == STARTED_NONE &&
2335                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2336                  (buf[i] == '1' && buf[i+1] == '0')) &&
2337                 buf[i+2] == ':' && buf[i+3] == ' ') {
2338               started = STARTED_CHATTER;
2339               i += 3;
2340               continue;
2341             }
2342
2343             /* skip formula vars */
2344             if (started == STARTED_NONE &&
2345                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2346               started = STARTED_CHATTER;
2347               i += 3;
2348               continue;
2349             }
2350
2351             oldi = i;
2352             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2353             if (appData.autoKibitz && started == STARTED_NONE && 
2354                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2355                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2356                 if(looking_at(buf, &i, "* kibitzes: ") &&
2357                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2358                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2359                         suppressKibitz = TRUE;
2360                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2361                                 && (gameMode == IcsPlayingWhite)) ||
2362                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2363                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2364                             started = STARTED_CHATTER; // own kibitz we simply discard
2365                         else {
2366                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2367                             parse_pos = 0; parse[0] = NULLCHAR;
2368                             savingComment = TRUE;
2369                             suppressKibitz = gameMode != IcsObserving ? 2 :
2370                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2371                         } 
2372                         continue;
2373                 } else
2374                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2375                     started = STARTED_CHATTER;
2376                     suppressKibitz = TRUE;
2377                 }
2378             } // [HGM] kibitz: end of patch
2379
2380 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2381
2382             // [HGM] chat: intercept tells by users for which we have an open chat window
2383             channel = -1;
2384             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2385                                            looking_at(buf, &i, "* whispers:") ||
2386                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2387                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2388                 int p;
2389                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2390                 chattingPartner = -1;
2391
2392                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2393                 for(p=0; p<MAX_CHAT; p++) {
2394                     if(channel == atoi(chatPartner[p])) {
2395                     talker[0] = '['; strcat(talker, "]");
2396                     chattingPartner = p; break;
2397                     }
2398                 } else
2399                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2400                 for(p=0; p<MAX_CHAT; p++) {
2401                     if(!strcmp("WHISPER", chatPartner[p])) {
2402                         talker[0] = '['; strcat(talker, "]");
2403                         chattingPartner = p; break;
2404                     }
2405                 }
2406                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2407                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2408                     talker[0] = 0;
2409                     chattingPartner = p; break;
2410                 }
2411                 if(chattingPartner<0) i = oldi; else {
2412                     started = STARTED_COMMENT;
2413                     parse_pos = 0; parse[0] = NULLCHAR;
2414                     savingComment = TRUE;
2415                     suppressKibitz = TRUE;
2416                 }
2417             } // [HGM] chat: end of patch
2418
2419             if (appData.zippyTalk || appData.zippyPlay) {
2420                 /* [DM] Backup address for color zippy lines */
2421                 backup = i;
2422 #if ZIPPY
2423        #ifdef WIN32
2424                if (loggedOn == TRUE)
2425                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2426                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2427        #else
2428                 if (ZippyControl(buf, &i) ||
2429                     ZippyConverse(buf, &i) ||
2430                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2431                       loggedOn = TRUE;
2432                       if (!appData.colorize) continue;
2433                 }
2434        #endif
2435 #endif
2436             } // [DM] 'else { ' deleted
2437                 if (
2438                     /* Regular tells and says */
2439                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2440                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2441                     looking_at(buf, &i, "* says: ") ||
2442                     /* Don't color "message" or "messages" output */
2443                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2444                     looking_at(buf, &i, "*. * at *:*: ") ||
2445                     looking_at(buf, &i, "--* (*:*): ") ||
2446                     /* Message notifications (same color as tells) */
2447                     looking_at(buf, &i, "* has left a message ") ||
2448                     looking_at(buf, &i, "* just sent you a message:\n") ||
2449                     /* Whispers and kibitzes */
2450                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2451                     looking_at(buf, &i, "* kibitzes: ") ||
2452                     /* Channel tells */
2453                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2454
2455                   if (tkind == 1 && strchr(star_match[0], ':')) {
2456                       /* Avoid "tells you:" spoofs in channels */
2457                      tkind = 3;
2458                   }
2459                   if (star_match[0][0] == NULLCHAR ||
2460                       strchr(star_match[0], ' ') ||
2461                       (tkind == 3 && strchr(star_match[1], ' '))) {
2462                     /* Reject bogus matches */
2463                     i = oldi;
2464                   } else {
2465                     if (appData.colorize) {
2466                       if (oldi > next_out) {
2467                         SendToPlayer(&buf[next_out], oldi - next_out);
2468                         next_out = oldi;
2469                       }
2470                       switch (tkind) {
2471                       case 1:
2472                         Colorize(ColorTell, FALSE);
2473                         curColor = ColorTell;
2474                         break;
2475                       case 2:
2476                         Colorize(ColorKibitz, FALSE);
2477                         curColor = ColorKibitz;
2478                         break;
2479                       case 3:
2480                         p = strrchr(star_match[1], '(');
2481                         if (p == NULL) {
2482                           p = star_match[1];
2483                         } else {
2484                           p++;
2485                         }
2486                         if (atoi(p) == 1) {
2487                           Colorize(ColorChannel1, FALSE);
2488                           curColor = ColorChannel1;
2489                         } else {
2490                           Colorize(ColorChannel, FALSE);
2491                           curColor = ColorChannel;
2492                         }
2493                         break;
2494                       case 5:
2495                         curColor = ColorNormal;
2496                         break;
2497                       }
2498                     }
2499                     if (started == STARTED_NONE && appData.autoComment &&
2500                         (gameMode == IcsObserving ||
2501                          gameMode == IcsPlayingWhite ||
2502                          gameMode == IcsPlayingBlack)) {
2503                       parse_pos = i - oldi;
2504                       memcpy(parse, &buf[oldi], parse_pos);
2505                       parse[parse_pos] = NULLCHAR;
2506                       started = STARTED_COMMENT;
2507                       savingComment = TRUE;
2508                     } else {
2509                       started = STARTED_CHATTER;
2510                       savingComment = FALSE;
2511                     }
2512                     loggedOn = TRUE;
2513                     continue;
2514                   }
2515                 }
2516
2517                 if (looking_at(buf, &i, "* s-shouts: ") ||
2518                     looking_at(buf, &i, "* c-shouts: ")) {
2519                     if (appData.colorize) {
2520                         if (oldi > next_out) {
2521                             SendToPlayer(&buf[next_out], oldi - next_out);
2522                             next_out = oldi;
2523                         }
2524                         Colorize(ColorSShout, FALSE);
2525                         curColor = ColorSShout;
2526                     }
2527                     loggedOn = TRUE;
2528                     started = STARTED_CHATTER;
2529                     continue;
2530                 }
2531
2532                 if (looking_at(buf, &i, "--->")) {
2533                     loggedOn = TRUE;
2534                     continue;
2535                 }
2536
2537                 if (looking_at(buf, &i, "* shouts: ") ||
2538                     looking_at(buf, &i, "--> ")) {
2539                     if (appData.colorize) {
2540                         if (oldi > next_out) {
2541                             SendToPlayer(&buf[next_out], oldi - next_out);
2542                             next_out = oldi;
2543                         }
2544                         Colorize(ColorShout, FALSE);
2545                         curColor = ColorShout;
2546                     }
2547                     loggedOn = TRUE;
2548                     started = STARTED_CHATTER;
2549                     continue;
2550                 }
2551
2552                 if (looking_at( buf, &i, "Challenge:")) {
2553                     if (appData.colorize) {
2554                         if (oldi > next_out) {
2555                             SendToPlayer(&buf[next_out], oldi - next_out);
2556                             next_out = oldi;
2557                         }
2558                         Colorize(ColorChallenge, FALSE);
2559                         curColor = ColorChallenge;
2560                     }
2561                     loggedOn = TRUE;
2562                     continue;
2563                 }
2564
2565                 if (looking_at(buf, &i, "* offers you") ||
2566                     looking_at(buf, &i, "* offers to be") ||
2567                     looking_at(buf, &i, "* would like to") ||
2568                     looking_at(buf, &i, "* requests to") ||
2569                     looking_at(buf, &i, "Your opponent offers") ||
2570                     looking_at(buf, &i, "Your opponent requests")) {
2571
2572                     if (appData.colorize) {
2573                         if (oldi > next_out) {
2574                             SendToPlayer(&buf[next_out], oldi - next_out);
2575                             next_out = oldi;
2576                         }
2577                         Colorize(ColorRequest, FALSE);
2578                         curColor = ColorRequest;
2579                     }
2580                     continue;
2581                 }
2582
2583                 if (looking_at(buf, &i, "* (*) seeking")) {
2584                     if (appData.colorize) {
2585                         if (oldi > next_out) {
2586                             SendToPlayer(&buf[next_out], oldi - next_out);
2587                             next_out = oldi;
2588                         }
2589                         Colorize(ColorSeek, FALSE);
2590                         curColor = ColorSeek;
2591                     }
2592                     continue;
2593             }
2594
2595             if (looking_at(buf, &i, "\\   ")) {
2596                 if (prevColor != ColorNormal) {
2597                     if (oldi > next_out) {
2598                         SendToPlayer(&buf[next_out], oldi - next_out);
2599                         next_out = oldi;
2600                     }
2601                     Colorize(prevColor, TRUE);
2602                     curColor = prevColor;
2603                 }
2604                 if (savingComment) {
2605                     parse_pos = i - oldi;
2606                     memcpy(parse, &buf[oldi], parse_pos);
2607                     parse[parse_pos] = NULLCHAR;
2608                     started = STARTED_COMMENT;
2609                 } else {
2610                     started = STARTED_CHATTER;
2611                 }
2612                 continue;
2613             }
2614
2615             if (looking_at(buf, &i, "Black Strength :") ||
2616                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2617                 looking_at(buf, &i, "<10>") ||
2618                 looking_at(buf, &i, "#@#")) {
2619                 /* Wrong board style */
2620                 loggedOn = TRUE;
2621                 SendToICS(ics_prefix);
2622                 SendToICS("set style 12\n");
2623                 SendToICS(ics_prefix);
2624                 SendToICS("refresh\n");
2625                 continue;
2626             }
2627             
2628             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2629                 ICSInitScript();
2630                 have_sent_ICS_logon = 1;
2631                 continue;
2632             }
2633               
2634             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2635                 (looking_at(buf, &i, "\n<12> ") ||
2636                  looking_at(buf, &i, "<12> "))) {
2637                 loggedOn = TRUE;
2638                 if (oldi > next_out) {
2639                     SendToPlayer(&buf[next_out], oldi - next_out);
2640                 }
2641                 next_out = i;
2642                 started = STARTED_BOARD;
2643                 parse_pos = 0;
2644                 continue;
2645             }
2646
2647             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2648                 looking_at(buf, &i, "<b1> ")) {
2649                 if (oldi > next_out) {
2650                     SendToPlayer(&buf[next_out], oldi - next_out);
2651                 }
2652                 next_out = i;
2653                 started = STARTED_HOLDINGS;
2654                 parse_pos = 0;
2655                 continue;
2656             }
2657
2658             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2659                 loggedOn = TRUE;
2660                 /* Header for a move list -- first line */
2661
2662                 switch (ics_getting_history) {
2663                   case H_FALSE:
2664                     switch (gameMode) {
2665                       case IcsIdle:
2666                       case BeginningOfGame:
2667                         /* User typed "moves" or "oldmoves" while we
2668                            were idle.  Pretend we asked for these
2669                            moves and soak them up so user can step
2670                            through them and/or save them.
2671                            */
2672                         Reset(FALSE, TRUE);
2673                         gameMode = IcsObserving;
2674                         ModeHighlight();
2675                         ics_gamenum = -1;
2676                         ics_getting_history = H_GOT_UNREQ_HEADER;
2677                         break;
2678                       case EditGame: /*?*/
2679                       case EditPosition: /*?*/
2680                         /* Should above feature work in these modes too? */
2681                         /* For now it doesn't */
2682                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2683                         break;
2684                       default:
2685                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2686                         break;
2687                     }
2688                     break;
2689                   case H_REQUESTED:
2690                     /* Is this the right one? */
2691                     if (gameInfo.white && gameInfo.black &&
2692                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2693                         strcmp(gameInfo.black, star_match[2]) == 0) {
2694                         /* All is well */
2695                         ics_getting_history = H_GOT_REQ_HEADER;
2696                     }
2697                     break;
2698                   case H_GOT_REQ_HEADER:
2699                   case H_GOT_UNREQ_HEADER:
2700                   case H_GOT_UNWANTED_HEADER:
2701                   case H_GETTING_MOVES:
2702                     /* Should not happen */
2703                     DisplayError(_("Error gathering move list: two headers"), 0);
2704                     ics_getting_history = H_FALSE;
2705                     break;
2706                 }
2707
2708                 /* Save player ratings into gameInfo if needed */
2709                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2710                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2711                     (gameInfo.whiteRating == -1 ||
2712                      gameInfo.blackRating == -1)) {
2713
2714                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2715                     gameInfo.blackRating = string_to_rating(star_match[3]);
2716                     if (appData.debugMode)
2717                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2718                               gameInfo.whiteRating, gameInfo.blackRating);
2719                 }
2720                 continue;
2721             }
2722
2723             if (looking_at(buf, &i,
2724               "* * match, initial time: * minute*, increment: * second")) {
2725                 /* Header for a move list -- second line */
2726                 /* Initial board will follow if this is a wild game */
2727                 if (gameInfo.event != NULL) free(gameInfo.event);
2728                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2729                 gameInfo.event = StrSave(str);
2730                 /* [HGM] we switched variant. Translate boards if needed. */
2731                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2732                 continue;
2733             }
2734
2735             if (looking_at(buf, &i, "Move  ")) {
2736                 /* Beginning of a move list */
2737                 switch (ics_getting_history) {
2738                   case H_FALSE:
2739                     /* Normally should not happen */
2740                     /* Maybe user hit reset while we were parsing */
2741                     break;
2742                   case H_REQUESTED:
2743                     /* Happens if we are ignoring a move list that is not
2744                      * the one we just requested.  Common if the user
2745                      * tries to observe two games without turning off
2746                      * getMoveList */
2747                     break;
2748                   case H_GETTING_MOVES:
2749                     /* Should not happen */
2750                     DisplayError(_("Error gathering move list: nested"), 0);
2751                     ics_getting_history = H_FALSE;
2752                     break;
2753                   case H_GOT_REQ_HEADER:
2754                     ics_getting_history = H_GETTING_MOVES;
2755                     started = STARTED_MOVES;
2756                     parse_pos = 0;
2757                     if (oldi > next_out) {
2758                         SendToPlayer(&buf[next_out], oldi - next_out);
2759                     }
2760                     break;
2761                   case H_GOT_UNREQ_HEADER:
2762                     ics_getting_history = H_GETTING_MOVES;
2763                     started = STARTED_MOVES_NOHIDE;
2764                     parse_pos = 0;
2765                     break;
2766                   case H_GOT_UNWANTED_HEADER:
2767                     ics_getting_history = H_FALSE;
2768                     break;
2769                 }
2770                 continue;
2771             }                           
2772             
2773             if (looking_at(buf, &i, "% ") ||
2774                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2775                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2776                 savingComment = FALSE;
2777                 switch (started) {
2778                   case STARTED_MOVES:
2779                   case STARTED_MOVES_NOHIDE:
2780                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2781                     parse[parse_pos + i - oldi] = NULLCHAR;
2782                     ParseGameHistory(parse);
2783 #if ZIPPY
2784                     if (appData.zippyPlay && first.initDone) {
2785                         FeedMovesToProgram(&first, forwardMostMove);
2786                         if (gameMode == IcsPlayingWhite) {
2787                             if (WhiteOnMove(forwardMostMove)) {
2788                                 if (first.sendTime) {
2789                                   if (first.useColors) {
2790                                     SendToProgram("black\n", &first); 
2791                                   }
2792                                   SendTimeRemaining(&first, TRUE);
2793                                 }
2794                                 if (first.useColors) {
2795                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2796                                 }
2797                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2798                                 first.maybeThinking = TRUE;
2799                             } else {
2800                                 if (first.usePlayother) {
2801                                   if (first.sendTime) {
2802                                     SendTimeRemaining(&first, TRUE);
2803                                   }
2804                                   SendToProgram("playother\n", &first);
2805                                   firstMove = FALSE;
2806                                 } else {
2807                                   firstMove = TRUE;
2808                                 }
2809                             }
2810                         } else if (gameMode == IcsPlayingBlack) {
2811                             if (!WhiteOnMove(forwardMostMove)) {
2812                                 if (first.sendTime) {
2813                                   if (first.useColors) {
2814                                     SendToProgram("white\n", &first);
2815                                   }
2816                                   SendTimeRemaining(&first, FALSE);
2817                                 }
2818                                 if (first.useColors) {
2819                                   SendToProgram("black\n", &first);
2820                                 }
2821                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2822                                 first.maybeThinking = TRUE;
2823                             } else {
2824                                 if (first.usePlayother) {
2825                                   if (first.sendTime) {
2826                                     SendTimeRemaining(&first, FALSE);
2827                                   }
2828                                   SendToProgram("playother\n", &first);
2829                                   firstMove = FALSE;
2830                                 } else {
2831                                   firstMove = TRUE;
2832                                 }
2833                             }
2834                         }                       
2835                     }
2836 #endif
2837                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2838                         /* Moves came from oldmoves or moves command
2839                            while we weren't doing anything else.
2840                            */
2841                         currentMove = forwardMostMove;
2842                         ClearHighlights();/*!!could figure this out*/
2843                         flipView = appData.flipView;
2844                         DrawPosition(FALSE, boards[currentMove]);
2845                         DisplayBothClocks();
2846                         sprintf(str, "%s vs. %s",
2847                                 gameInfo.white, gameInfo.black);
2848                         DisplayTitle(str);
2849                         gameMode = IcsIdle;
2850                     } else {
2851                         /* Moves were history of an active game */
2852                         if (gameInfo.resultDetails != NULL) {
2853                             free(gameInfo.resultDetails);
2854                             gameInfo.resultDetails = NULL;
2855                         }
2856                     }
2857                     HistorySet(parseList, backwardMostMove,
2858                                forwardMostMove, currentMove-1);
2859                     DisplayMove(currentMove - 1);
2860                     if (started == STARTED_MOVES) next_out = i;
2861                     started = STARTED_NONE;
2862                     ics_getting_history = H_FALSE;
2863                     break;
2864
2865                   case STARTED_OBSERVE:
2866                     started = STARTED_NONE;
2867                     SendToICS(ics_prefix);
2868                     SendToICS("refresh\n");
2869                     break;
2870
2871                   default:
2872                     break;
2873                 }
2874                 if(bookHit) { // [HGM] book: simulate book reply
2875                     static char bookMove[MSG_SIZ]; // a bit generous?
2876
2877                     programStats.nodes = programStats.depth = programStats.time = 
2878                     programStats.score = programStats.got_only_move = 0;
2879                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2880
2881                     strcpy(bookMove, "move ");
2882                     strcat(bookMove, bookHit);
2883                     HandleMachineMove(bookMove, &first);
2884                 }
2885                 continue;
2886             }
2887             
2888             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2889                  started == STARTED_HOLDINGS ||
2890                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2891                 /* Accumulate characters in move list or board */
2892                 parse[parse_pos++] = buf[i];
2893             }
2894             
2895             /* Start of game messages.  Mostly we detect start of game
2896                when the first board image arrives.  On some versions
2897                of the ICS, though, we need to do a "refresh" after starting
2898                to observe in order to get the current board right away. */
2899             if (looking_at(buf, &i, "Adding game * to observation list")) {
2900                 started = STARTED_OBSERVE;
2901                 continue;
2902             }
2903
2904             /* Handle auto-observe */
2905             if (appData.autoObserve &&
2906                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2907                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2908                 char *player;
2909                 /* Choose the player that was highlighted, if any. */
2910                 if (star_match[0][0] == '\033' ||
2911                     star_match[1][0] != '\033') {
2912                     player = star_match[0];
2913                 } else {
2914                     player = star_match[2];
2915                 }
2916                 sprintf(str, "%sobserve %s\n",
2917                         ics_prefix, StripHighlightAndTitle(player));
2918                 SendToICS(str);
2919
2920                 /* Save ratings from notify string */
2921                 strcpy(player1Name, star_match[0]);
2922                 player1Rating = string_to_rating(star_match[1]);
2923                 strcpy(player2Name, star_match[2]);
2924                 player2Rating = string_to_rating(star_match[3]);
2925
2926                 if (appData.debugMode)
2927                   fprintf(debugFP, 
2928                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2929                           player1Name, player1Rating,
2930                           player2Name, player2Rating);
2931
2932                 continue;
2933             }
2934
2935             /* Deal with automatic examine mode after a game,
2936                and with IcsObserving -> IcsExamining transition */
2937             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2938                 looking_at(buf, &i, "has made you an examiner of game *")) {
2939
2940                 int gamenum = atoi(star_match[0]);
2941                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2942                     gamenum == ics_gamenum) {
2943                     /* We were already playing or observing this game;
2944                        no need to refetch history */
2945                     gameMode = IcsExamining;
2946                     if (pausing) {
2947                         pauseExamForwardMostMove = forwardMostMove;
2948                     } else if (currentMove < forwardMostMove) {
2949                         ForwardInner(forwardMostMove);
2950                     }
2951                 } else {
2952                     /* I don't think this case really can happen */
2953                     SendToICS(ics_prefix);
2954                     SendToICS("refresh\n");
2955                 }
2956                 continue;
2957             }    
2958             
2959             /* Error messages */
2960 //          if (ics_user_moved) {
2961             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2962                 if (looking_at(buf, &i, "Illegal move") ||
2963                     looking_at(buf, &i, "Not a legal move") ||
2964                     looking_at(buf, &i, "Your king is in check") ||
2965                     looking_at(buf, &i, "It isn't your turn") ||
2966                     looking_at(buf, &i, "It is not your move")) {
2967                     /* Illegal move */
2968                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2969                         currentMove = --forwardMostMove;
2970                         DisplayMove(currentMove - 1); /* before DMError */
2971                         DrawPosition(FALSE, boards[currentMove]);
2972                         SwitchClocks();
2973                         DisplayBothClocks();
2974                     }
2975                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2976                     ics_user_moved = 0;
2977                     continue;
2978                 }
2979             }
2980
2981             if (looking_at(buf, &i, "still have time") ||
2982                 looking_at(buf, &i, "not out of time") ||
2983                 looking_at(buf, &i, "either player is out of time") ||
2984                 looking_at(buf, &i, "has timeseal; checking")) {
2985                 /* We must have called his flag a little too soon */
2986                 whiteFlag = blackFlag = FALSE;
2987                 continue;
2988             }
2989
2990             if (looking_at(buf, &i, "added * seconds to") ||
2991                 looking_at(buf, &i, "seconds were added to")) {
2992                 /* Update the clocks */
2993                 SendToICS(ics_prefix);
2994                 SendToICS("refresh\n");
2995                 continue;
2996             }
2997
2998             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2999                 ics_clock_paused = TRUE;
3000                 StopClocks();
3001                 continue;
3002             }
3003
3004             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3005                 ics_clock_paused = FALSE;
3006                 StartClocks();
3007                 continue;
3008             }
3009
3010             /* Grab player ratings from the Creating: message.
3011                Note we have to check for the special case when
3012                the ICS inserts things like [white] or [black]. */
3013             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3014                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3015                 /* star_matches:
3016                    0    player 1 name (not necessarily white)
3017                    1    player 1 rating
3018                    2    empty, white, or black (IGNORED)
3019                    3    player 2 name (not necessarily black)
3020                    4    player 2 rating
3021                    
3022                    The names/ratings are sorted out when the game
3023                    actually starts (below).
3024                 */
3025                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3026                 player1Rating = string_to_rating(star_match[1]);
3027                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3028                 player2Rating = string_to_rating(star_match[4]);
3029
3030                 if (appData.debugMode)
3031                   fprintf(debugFP, 
3032                           "Ratings from 'Creating:' %s %d, %s %d\n",
3033                           player1Name, player1Rating,
3034                           player2Name, player2Rating);
3035
3036                 continue;
3037             }
3038             
3039             /* Improved generic start/end-of-game messages */
3040             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3041                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3042                 /* If tkind == 0: */
3043                 /* star_match[0] is the game number */
3044                 /*           [1] is the white player's name */
3045                 /*           [2] is the black player's name */
3046                 /* For end-of-game: */
3047                 /*           [3] is the reason for the game end */
3048                 /*           [4] is a PGN end game-token, preceded by " " */
3049                 /* For start-of-game: */
3050                 /*           [3] begins with "Creating" or "Continuing" */
3051                 /*           [4] is " *" or empty (don't care). */
3052                 int gamenum = atoi(star_match[0]);
3053                 char *whitename, *blackname, *why, *endtoken;
3054                 ChessMove endtype = (ChessMove) 0;
3055
3056                 if (tkind == 0) {
3057                   whitename = star_match[1];
3058                   blackname = star_match[2];
3059                   why = star_match[3];
3060                   endtoken = star_match[4];
3061                 } else {
3062                   whitename = star_match[1];
3063                   blackname = star_match[3];
3064                   why = star_match[5];
3065                   endtoken = star_match[6];
3066                 }
3067
3068                 /* Game start messages */
3069                 if (strncmp(why, "Creating ", 9) == 0 ||
3070                     strncmp(why, "Continuing ", 11) == 0) {
3071                     gs_gamenum = gamenum;
3072                     strcpy(gs_kind, strchr(why, ' ') + 1);
3073 #if ZIPPY
3074                     if (appData.zippyPlay) {
3075                         ZippyGameStart(whitename, blackname);
3076                     }
3077 #endif /*ZIPPY*/
3078                     continue;
3079                 }
3080
3081                 /* Game end messages */
3082                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3083                     ics_gamenum != gamenum) {
3084                     continue;
3085                 }
3086                 while (endtoken[0] == ' ') endtoken++;
3087                 switch (endtoken[0]) {
3088                   case '*':
3089                   default:
3090                     endtype = GameUnfinished;
3091                     break;
3092                   case '0':
3093                     endtype = BlackWins;
3094                     break;
3095                   case '1':
3096                     if (endtoken[1] == '/')
3097                       endtype = GameIsDrawn;
3098                     else
3099                       endtype = WhiteWins;
3100                     break;
3101                 }
3102                 GameEnds(endtype, why, GE_ICS);
3103 #if ZIPPY
3104                 if (appData.zippyPlay && first.initDone) {
3105                     ZippyGameEnd(endtype, why);
3106                     if (first.pr == NULL) {
3107                       /* Start the next process early so that we'll
3108                          be ready for the next challenge */
3109                       StartChessProgram(&first);
3110                     }
3111                     /* Send "new" early, in case this command takes
3112                        a long time to finish, so that we'll be ready
3113                        for the next challenge. */
3114                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3115                     Reset(TRUE, TRUE);
3116                 }
3117 #endif /*ZIPPY*/
3118                 continue;
3119             }
3120
3121             if (looking_at(buf, &i, "Removing game * from observation") ||
3122                 looking_at(buf, &i, "no longer observing game *") ||
3123                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3124                 if (gameMode == IcsObserving &&
3125                     atoi(star_match[0]) == ics_gamenum)
3126                   {
3127                       /* icsEngineAnalyze */
3128                       if (appData.icsEngineAnalyze) {
3129                             ExitAnalyzeMode();
3130                             ModeHighlight();
3131                       }
3132                       StopClocks();
3133                       gameMode = IcsIdle;
3134                       ics_gamenum = -1;
3135                       ics_user_moved = FALSE;
3136                   }
3137                 continue;
3138             }
3139
3140             if (looking_at(buf, &i, "no longer examining game *")) {
3141                 if (gameMode == IcsExamining &&
3142                     atoi(star_match[0]) == ics_gamenum)
3143                   {
3144                       gameMode = IcsIdle;
3145                       ics_gamenum = -1;
3146                       ics_user_moved = FALSE;
3147                   }
3148                 continue;
3149             }
3150
3151             /* Advance leftover_start past any newlines we find,
3152                so only partial lines can get reparsed */
3153             if (looking_at(buf, &i, "\n")) {
3154                 prevColor = curColor;
3155                 if (curColor != ColorNormal) {
3156                     if (oldi > next_out) {
3157                         SendToPlayer(&buf[next_out], oldi - next_out);
3158                         next_out = oldi;
3159                     }
3160                     Colorize(ColorNormal, FALSE);
3161                     curColor = ColorNormal;
3162                 }
3163                 if (started == STARTED_BOARD) {
3164                     started = STARTED_NONE;
3165                     parse[parse_pos] = NULLCHAR;
3166                     ParseBoard12(parse);
3167                     ics_user_moved = 0;
3168
3169                     /* Send premove here */
3170                     if (appData.premove) {
3171                       char str[MSG_SIZ];
3172                       if (currentMove == 0 &&
3173                           gameMode == IcsPlayingWhite &&
3174                           appData.premoveWhite) {
3175                         sprintf(str, "%s%s\n", ics_prefix,
3176                                 appData.premoveWhiteText);
3177                         if (appData.debugMode)
3178                           fprintf(debugFP, "Sending premove:\n");
3179                         SendToICS(str);
3180                       } else if (currentMove == 1 &&
3181                                  gameMode == IcsPlayingBlack &&
3182                                  appData.premoveBlack) {
3183                         sprintf(str, "%s%s\n", ics_prefix,
3184                                 appData.premoveBlackText);
3185                         if (appData.debugMode)
3186                           fprintf(debugFP, "Sending premove:\n");
3187                         SendToICS(str);
3188                       } else if (gotPremove) {
3189                         gotPremove = 0;
3190                         ClearPremoveHighlights();
3191                         if (appData.debugMode)
3192                           fprintf(debugFP, "Sending premove:\n");
3193                           UserMoveEvent(premoveFromX, premoveFromY, 
3194                                         premoveToX, premoveToY, 
3195                                         premovePromoChar);
3196                       }
3197                     }
3198
3199                     /* Usually suppress following prompt */
3200                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3201                         if (looking_at(buf, &i, "*% ")) {
3202                             savingComment = FALSE;
3203                         }
3204                     }
3205                     next_out = i;
3206                 } else if (started == STARTED_HOLDINGS) {
3207                     int gamenum;
3208                     char new_piece[MSG_SIZ];
3209                     started = STARTED_NONE;
3210                     parse[parse_pos] = NULLCHAR;
3211                     if (appData.debugMode)
3212                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3213                                                         parse, currentMove);
3214                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3215                         gamenum == ics_gamenum) {
3216                         if (gameInfo.variant == VariantNormal) {
3217                           /* [HGM] We seem to switch variant during a game!
3218                            * Presumably no holdings were displayed, so we have
3219                            * to move the position two files to the right to
3220                            * create room for them!
3221                            */
3222                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3223                           /* Get a move list just to see the header, which
3224                              will tell us whether this is really bug or zh */
3225                           if (ics_getting_history == H_FALSE) {
3226                             ics_getting_history = H_REQUESTED;
3227                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3228                             SendToICS(str);
3229                           }
3230                         }
3231                         new_piece[0] = NULLCHAR;
3232                         sscanf(parse, "game %d white [%s black [%s <- %s",
3233                                &gamenum, white_holding, black_holding,
3234                                new_piece);
3235                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3236                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3237                         /* [HGM] copy holdings to board holdings area */
3238                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3239                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3240 #if ZIPPY
3241                         if (appData.zippyPlay && first.initDone) {
3242                             ZippyHoldings(white_holding, black_holding,
3243                                           new_piece);
3244                         }
3245 #endif /*ZIPPY*/
3246                         if (tinyLayout || smallLayout) {
3247                             char wh[16], bh[16];
3248                             PackHolding(wh, white_holding);
3249                             PackHolding(bh, black_holding);
3250                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3251                                     gameInfo.white, gameInfo.black);
3252                         } else {
3253                             sprintf(str, "%s [%s] vs. %s [%s]",
3254                                     gameInfo.white, white_holding,
3255                                     gameInfo.black, black_holding);
3256                         }
3257
3258                         DrawPosition(FALSE, boards[currentMove]);
3259                         DisplayTitle(str);
3260                     }
3261                     /* Suppress following prompt */
3262                     if (looking_at(buf, &i, "*% ")) {
3263                         savingComment = FALSE;
3264                     }
3265                     next_out = i;
3266                 }
3267                 continue;
3268             }
3269
3270             i++;                /* skip unparsed character and loop back */
3271         }
3272         
3273         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3274             started != STARTED_HOLDINGS && i > next_out) {
3275             SendToPlayer(&buf[next_out], i - next_out);
3276             next_out = i;
3277         }
3278         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3279         
3280         leftover_len = buf_len - leftover_start;
3281         /* if buffer ends with something we couldn't parse,
3282            reparse it after appending the next read */
3283         
3284     } else if (count == 0) {
3285         RemoveInputSource(isr);
3286         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3287     } else {
3288         DisplayFatalError(_("Error reading from ICS"), error, 1);
3289     }
3290 }
3291
3292
3293 /* Board style 12 looks like this:
3294    
3295    <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
3296    
3297  * The "<12> " is stripped before it gets to this routine.  The two
3298  * trailing 0's (flip state and clock ticking) are later addition, and
3299  * some chess servers may not have them, or may have only the first.
3300  * Additional trailing fields may be added in the future.  
3301  */
3302
3303 #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"
3304
3305 #define RELATION_OBSERVING_PLAYED    0
3306 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3307 #define RELATION_PLAYING_MYMOVE      1
3308 #define RELATION_PLAYING_NOTMYMOVE  -1
3309 #define RELATION_EXAMINING           2
3310 #define RELATION_ISOLATED_BOARD     -3
3311 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3312
3313 void
3314 ParseBoard12(string)
3315      char *string;
3316
3317     GameMode newGameMode;
3318     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3319     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3320     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3321     char to_play, board_chars[200];
3322     char move_str[500], str[500], elapsed_time[500];
3323     char black[32], white[32];
3324     Board board;
3325     int prevMove = currentMove;
3326     int ticking = 2;
3327     ChessMove moveType;
3328     int fromX, fromY, toX, toY;
3329     char promoChar;
3330     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3331     char *bookHit = NULL; // [HGM] book
3332
3333     fromX = fromY = toX = toY = -1;
3334     
3335     newGame = FALSE;
3336
3337     if (appData.debugMode)
3338       fprintf(debugFP, _("Parsing board: %s\n"), string);
3339
3340     move_str[0] = NULLCHAR;
3341     elapsed_time[0] = NULLCHAR;
3342     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3343         int  i = 0, j;
3344         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3345             if(string[i] == ' ') { ranks++; files = 0; }
3346             else files++;
3347             i++;
3348         }
3349         for(j = 0; j <i; j++) board_chars[j] = string[j];
3350         board_chars[i] = '\0';
3351         string += i + 1;
3352     }
3353     n = sscanf(string, PATTERN, &to_play, &double_push,
3354                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3355                &gamenum, white, black, &relation, &basetime, &increment,
3356                &white_stren, &black_stren, &white_time, &black_time,
3357                &moveNum, str, elapsed_time, move_str, &ics_flip,
3358                &ticking);
3359
3360     if (n < 21) {
3361         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3362         DisplayError(str, 0);
3363         return;
3364     }
3365
3366     /* Convert the move number to internal form */
3367     moveNum = (moveNum - 1) * 2;
3368     if (to_play == 'B') moveNum++;
3369     if (moveNum >= MAX_MOVES) {
3370       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3371                         0, 1);
3372       return;
3373     }
3374     
3375     switch (relation) {
3376       case RELATION_OBSERVING_PLAYED:
3377       case RELATION_OBSERVING_STATIC:
3378         if (gamenum == -1) {
3379             /* Old ICC buglet */
3380             relation = RELATION_OBSERVING_STATIC;
3381         }
3382         newGameMode = IcsObserving;
3383         break;
3384       case RELATION_PLAYING_MYMOVE:
3385       case RELATION_PLAYING_NOTMYMOVE:
3386         newGameMode =
3387           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3388             IcsPlayingWhite : IcsPlayingBlack;
3389         break;
3390       case RELATION_EXAMINING:
3391         newGameMode = IcsExamining;
3392         break;
3393       case RELATION_ISOLATED_BOARD:
3394       default:
3395         /* Just display this board.  If user was doing something else,
3396            we will forget about it until the next board comes. */ 
3397         newGameMode = IcsIdle;
3398         break;
3399       case RELATION_STARTING_POSITION:
3400         newGameMode = gameMode;
3401         break;
3402     }
3403     
3404     /* Modify behavior for initial board display on move listing
3405        of wild games.
3406        */
3407     switch (ics_getting_history) {
3408       case H_FALSE:
3409       case H_REQUESTED:
3410         break;
3411       case H_GOT_REQ_HEADER:
3412       case H_GOT_UNREQ_HEADER:
3413         /* This is the initial position of the current game */
3414         gamenum = ics_gamenum;
3415         moveNum = 0;            /* old ICS bug workaround */
3416         if (to_play == 'B') {
3417           startedFromSetupPosition = TRUE;
3418           blackPlaysFirst = TRUE;
3419           moveNum = 1;
3420           if (forwardMostMove == 0) forwardMostMove = 1;
3421           if (backwardMostMove == 0) backwardMostMove = 1;
3422           if (currentMove == 0) currentMove = 1;
3423         }
3424         newGameMode = gameMode;
3425         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3426         break;
3427       case H_GOT_UNWANTED_HEADER:
3428         /* This is an initial board that we don't want */
3429         return;
3430       case H_GETTING_MOVES:
3431         /* Should not happen */
3432         DisplayError(_("Error gathering move list: extra board"), 0);
3433         ics_getting_history = H_FALSE;
3434         return;
3435     }
3436     
3437     /* Take action if this is the first board of a new game, or of a
3438        different game than is currently being displayed.  */
3439     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3440         relation == RELATION_ISOLATED_BOARD) {
3441         
3442         /* Forget the old game and get the history (if any) of the new one */
3443         if (gameMode != BeginningOfGame) {
3444           Reset(FALSE, TRUE);
3445         }
3446         newGame = TRUE;
3447         if (appData.autoRaiseBoard) BoardToTop();
3448         prevMove = -3;
3449         if (gamenum == -1) {
3450             newGameMode = IcsIdle;
3451         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3452                    appData.getMoveList) {
3453             /* Need to get game history */
3454             ics_getting_history = H_REQUESTED;
3455             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3456             SendToICS(str);
3457         }
3458         
3459         /* Initially flip the board to have black on the bottom if playing
3460            black or if the ICS flip flag is set, but let the user change
3461            it with the Flip View button. */
3462         flipView = appData.autoFlipView ? 
3463           (newGameMode == IcsPlayingBlack) || ics_flip :
3464           appData.flipView;
3465         
3466         /* Done with values from previous mode; copy in new ones */
3467         gameMode = newGameMode;
3468         ModeHighlight();
3469         ics_gamenum = gamenum;
3470         if (gamenum == gs_gamenum) {
3471             int klen = strlen(gs_kind);
3472             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3473             sprintf(str, "ICS %s", gs_kind);
3474             gameInfo.event = StrSave(str);
3475         } else {
3476             gameInfo.event = StrSave("ICS game");
3477         }
3478         gameInfo.site = StrSave(appData.icsHost);
3479         gameInfo.date = PGNDate();
3480         gameInfo.round = StrSave("-");
3481         gameInfo.white = StrSave(white);
3482         gameInfo.black = StrSave(black);
3483         timeControl = basetime * 60 * 1000;
3484         timeControl_2 = 0;
3485         timeIncrement = increment * 1000;
3486         movesPerSession = 0;
3487         gameInfo.timeControl = TimeControlTagValue();
3488         VariantSwitch(board, StringToVariant(gameInfo.event) );
3489   if (appData.debugMode) {
3490     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3491     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3492     setbuf(debugFP, NULL);
3493   }
3494
3495         gameInfo.outOfBook = NULL;
3496         
3497         /* Do we have the ratings? */
3498         if (strcmp(player1Name, white) == 0 &&
3499             strcmp(player2Name, black) == 0) {
3500             if (appData.debugMode)
3501               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3502                       player1Rating, player2Rating);
3503             gameInfo.whiteRating = player1Rating;
3504             gameInfo.blackRating = player2Rating;
3505         } else if (strcmp(player2Name, white) == 0 &&
3506                    strcmp(player1Name, black) == 0) {
3507             if (appData.debugMode)
3508               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3509                       player2Rating, player1Rating);
3510             gameInfo.whiteRating = player2Rating;
3511             gameInfo.blackRating = player1Rating;
3512         }
3513         player1Name[0] = player2Name[0] = NULLCHAR;
3514
3515         /* Silence shouts if requested */
3516         if (appData.quietPlay &&
3517             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3518             SendToICS(ics_prefix);
3519             SendToICS("set shout 0\n");
3520         }
3521     }
3522     
3523     /* Deal with midgame name changes */
3524     if (!newGame) {
3525         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3526             if (gameInfo.white) free(gameInfo.white);
3527             gameInfo.white = StrSave(white);
3528         }
3529         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3530             if (gameInfo.black) free(gameInfo.black);
3531             gameInfo.black = StrSave(black);
3532         }
3533     }
3534     
3535     /* Throw away game result if anything actually changes in examine mode */
3536     if (gameMode == IcsExamining && !newGame) {
3537         gameInfo.result = GameUnfinished;
3538         if (gameInfo.resultDetails != NULL) {
3539             free(gameInfo.resultDetails);
3540             gameInfo.resultDetails = NULL;
3541         }
3542     }
3543     
3544     /* In pausing && IcsExamining mode, we ignore boards coming
3545        in if they are in a different variation than we are. */
3546     if (pauseExamInvalid) return;
3547     if (pausing && gameMode == IcsExamining) {
3548         if (moveNum <= pauseExamForwardMostMove) {
3549             pauseExamInvalid = TRUE;
3550             forwardMostMove = pauseExamForwardMostMove;
3551             return;
3552         }
3553     }
3554     
3555   if (appData.debugMode) {
3556     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3557   }
3558     /* Parse the board */
3559     for (k = 0; k < ranks; k++) {
3560       for (j = 0; j < files; j++)
3561         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3562       if(gameInfo.holdingsWidth > 1) {
3563            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3564            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3565       }
3566     }
3567     CopyBoard(boards[moveNum], board);
3568     if (moveNum == 0) {
3569         startedFromSetupPosition =
3570           !CompareBoards(board, initialPosition);
3571         if(startedFromSetupPosition)
3572             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3573     }
3574
3575     /* [HGM] Set castling rights. Take the outermost Rooks,
3576        to make it also work for FRC opening positions. Note that board12
3577        is really defective for later FRC positions, as it has no way to
3578        indicate which Rook can castle if they are on the same side of King.
3579        For the initial position we grant rights to the outermost Rooks,
3580        and remember thos rights, and we then copy them on positions
3581        later in an FRC game. This means WB might not recognize castlings with
3582        Rooks that have moved back to their original position as illegal,
3583        but in ICS mode that is not its job anyway.
3584     */
3585     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3586     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3587
3588         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3589             if(board[0][i] == WhiteRook) j = i;
3590         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3591         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3592             if(board[0][i] == WhiteRook) j = i;
3593         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3594         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3595             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3596         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3597         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3598             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3599         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3600
3601         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3602         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3603             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3604         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3605             if(board[BOARD_HEIGHT-1][k] == bKing)
3606                 initialRights[5] = castlingRights[moveNum][5] = k;
3607     } else { int r;
3608         r = castlingRights[moveNum][0] = initialRights[0];
3609         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3610         r = castlingRights[moveNum][1] = initialRights[1];
3611         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3612         r = castlingRights[moveNum][3] = initialRights[3];
3613         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3614         r = castlingRights[moveNum][4] = initialRights[4];
3615         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3616         /* wildcastle kludge: always assume King has rights */
3617         r = castlingRights[moveNum][2] = initialRights[2];
3618         r = castlingRights[moveNum][5] = initialRights[5];
3619     }
3620     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3621     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3622
3623     
3624     if (ics_getting_history == H_GOT_REQ_HEADER ||
3625         ics_getting_history == H_GOT_UNREQ_HEADER) {
3626         /* This was an initial position from a move list, not
3627            the current position */
3628         return;
3629     }
3630     
3631     /* Update currentMove and known move number limits */
3632     newMove = newGame || moveNum > forwardMostMove;
3633
3634     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3635     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3636         takeback = forwardMostMove - moveNum;
3637         for (i = 0; i < takeback; i++) {
3638              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3639              SendToProgram("undo\n", &first);
3640         }
3641     }
3642
3643     if (newGame) {
3644         forwardMostMove = backwardMostMove = currentMove = moveNum;
3645         if (gameMode == IcsExamining && moveNum == 0) {
3646           /* Workaround for ICS limitation: we are not told the wild
3647              type when starting to examine a game.  But if we ask for
3648              the move list, the move list header will tell us */
3649             ics_getting_history = H_REQUESTED;
3650             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3651             SendToICS(str);
3652         }
3653     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3654                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3655         forwardMostMove = moveNum;
3656         if (!pausing || currentMove > forwardMostMove)
3657           currentMove = forwardMostMove;
3658     } else {
3659         /* New part of history that is not contiguous with old part */ 
3660         if (pausing && gameMode == IcsExamining) {
3661             pauseExamInvalid = TRUE;
3662             forwardMostMove = pauseExamForwardMostMove;
3663             return;
3664         }
3665         forwardMostMove = backwardMostMove = currentMove = moveNum;
3666         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3667             ics_getting_history = H_REQUESTED;
3668             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3669             SendToICS(str);
3670         }
3671     }
3672     
3673     /* Update the clocks */
3674     if (strchr(elapsed_time, '.')) {
3675       /* Time is in ms */
3676       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3677       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3678     } else {
3679       /* Time is in seconds */
3680       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3681       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3682     }
3683       
3684
3685 #if ZIPPY
3686     if (appData.zippyPlay && newGame &&
3687         gameMode != IcsObserving && gameMode != IcsIdle &&
3688         gameMode != IcsExamining)
3689       ZippyFirstBoard(moveNum, basetime, increment);
3690 #endif
3691     
3692     /* Put the move on the move list, first converting
3693        to canonical algebraic form. */
3694     if (moveNum > 0) {
3695   if (appData.debugMode) {
3696     if (appData.debugMode) { int f = forwardMostMove;
3697         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3698                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3699     }
3700     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3701     fprintf(debugFP, "moveNum = %d\n", moveNum);
3702     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3703     setbuf(debugFP, NULL);
3704   }
3705         if (moveNum <= backwardMostMove) {
3706             /* We don't know what the board looked like before
3707                this move.  Punt. */
3708             strcpy(parseList[moveNum - 1], move_str);
3709             strcat(parseList[moveNum - 1], " ");
3710             strcat(parseList[moveNum - 1], elapsed_time);
3711             moveList[moveNum - 1][0] = NULLCHAR;
3712         } else if (strcmp(move_str, "none") == 0) {
3713             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3714             /* Again, we don't know what the board looked like;
3715                this is really the start of the game. */
3716             parseList[moveNum - 1][0] = NULLCHAR;
3717             moveList[moveNum - 1][0] = NULLCHAR;
3718             backwardMostMove = moveNum;
3719             startedFromSetupPosition = TRUE;
3720             fromX = fromY = toX = toY = -1;
3721         } else {
3722           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3723           //                 So we parse the long-algebraic move string in stead of the SAN move
3724           int valid; char buf[MSG_SIZ], *prom;
3725
3726           // str looks something like "Q/a1-a2"; kill the slash
3727           if(str[1] == '/') 
3728                 sprintf(buf, "%c%s", str[0], str+2);
3729           else  strcpy(buf, str); // might be castling
3730           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3731                 strcat(buf, prom); // long move lacks promo specification!
3732           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3733                 if(appData.debugMode) 
3734                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3735                 strcpy(move_str, buf);
3736           }
3737           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3738                                 &fromX, &fromY, &toX, &toY, &promoChar)
3739                || ParseOneMove(buf, moveNum - 1, &moveType,
3740                                 &fromX, &fromY, &toX, &toY, &promoChar);
3741           // end of long SAN patch
3742           if (valid) {
3743             (void) CoordsToAlgebraic(boards[moveNum - 1],
3744                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3745                                      fromY, fromX, toY, toX, promoChar,
3746                                      parseList[moveNum-1]);
3747             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3748                              castlingRights[moveNum]) ) {
3749               case MT_NONE:
3750               case MT_STALEMATE:
3751               default:
3752                 break;
3753               case MT_CHECK:
3754                 if(gameInfo.variant != VariantShogi)
3755                     strcat(parseList[moveNum - 1], "+");
3756                 break;
3757               case MT_CHECKMATE:
3758               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3759                 strcat(parseList[moveNum - 1], "#");
3760                 break;
3761             }
3762             strcat(parseList[moveNum - 1], " ");
3763             strcat(parseList[moveNum - 1], elapsed_time);
3764             /* currentMoveString is set as a side-effect of ParseOneMove */
3765             strcpy(moveList[moveNum - 1], currentMoveString);
3766             strcat(moveList[moveNum - 1], "\n");
3767           } else {
3768             /* Move from ICS was illegal!?  Punt. */
3769   if (appData.debugMode) {
3770     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3771     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3772   }
3773             strcpy(parseList[moveNum - 1], move_str);
3774             strcat(parseList[moveNum - 1], " ");
3775             strcat(parseList[moveNum - 1], elapsed_time);
3776             moveList[moveNum - 1][0] = NULLCHAR;
3777             fromX = fromY = toX = toY = -1;
3778           }
3779         }
3780   if (appData.debugMode) {
3781     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3782     setbuf(debugFP, NULL);
3783   }
3784
3785 #if ZIPPY
3786         /* Send move to chess program (BEFORE animating it). */
3787         if (appData.zippyPlay && !newGame && newMove && 
3788            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3789
3790             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3791                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3792                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3793                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3794                             move_str);
3795                     DisplayError(str, 0);
3796                 } else {
3797                     if (first.sendTime) {
3798                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3799                     }
3800                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3801                     if (firstMove && !bookHit) {
3802                         firstMove = FALSE;
3803                         if (first.useColors) {
3804                           SendToProgram(gameMode == IcsPlayingWhite ?
3805                                         "white\ngo\n" :
3806                                         "black\ngo\n", &first);
3807                         } else {
3808                           SendToProgram("go\n", &first);
3809                         }
3810                         first.maybeThinking = TRUE;
3811                     }
3812                 }
3813             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3814               if (moveList[moveNum - 1][0] == NULLCHAR) {
3815                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3816                 DisplayError(str, 0);
3817               } else {
3818                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3819                 SendMoveToProgram(moveNum - 1, &first);
3820               }
3821             }
3822         }
3823 #endif
3824     }
3825
3826     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3827         /* If move comes from a remote source, animate it.  If it
3828            isn't remote, it will have already been animated. */
3829         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3830             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3831         }
3832         if (!pausing && appData.highlightLastMove) {
3833             SetHighlights(fromX, fromY, toX, toY);
3834         }
3835     }
3836     
3837     /* Start the clocks */
3838     whiteFlag = blackFlag = FALSE;
3839     appData.clockMode = !(basetime == 0 && increment == 0);
3840     if (ticking == 0) {
3841       ics_clock_paused = TRUE;
3842       StopClocks();
3843     } else if (ticking == 1) {
3844       ics_clock_paused = FALSE;
3845     }
3846     if (gameMode == IcsIdle ||
3847         relation == RELATION_OBSERVING_STATIC ||
3848         relation == RELATION_EXAMINING ||
3849         ics_clock_paused)
3850       DisplayBothClocks();
3851     else
3852       StartClocks();
3853     
3854     /* Display opponents and material strengths */
3855     if (gameInfo.variant != VariantBughouse &&
3856         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3857         if (tinyLayout || smallLayout) {
3858             if(gameInfo.variant == VariantNormal)
3859                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3860                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3861                     basetime, increment);
3862             else
3863                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3864                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3865                     basetime, increment, (int) gameInfo.variant);
3866         } else {
3867             if(gameInfo.variant == VariantNormal)
3868                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3869                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3870                     basetime, increment);
3871             else
3872                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3873                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3874                     basetime, increment, VariantName(gameInfo.variant));
3875         }
3876         DisplayTitle(str);
3877   if (appData.debugMode) {
3878     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3879   }
3880     }
3881
3882    
3883     /* Display the board */
3884     if (!pausing && !appData.noGUI) {
3885       
3886       if (appData.premove)
3887           if (!gotPremove || 
3888              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3889              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3890               ClearPremoveHighlights();
3891
3892       DrawPosition(FALSE, boards[currentMove]);
3893       DisplayMove(moveNum - 1);
3894       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3895             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3896               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3897         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3898       }
3899     }
3900
3901     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3902 #if ZIPPY
3903     if(bookHit) { // [HGM] book: simulate book reply
3904         static char bookMove[MSG_SIZ]; // a bit generous?
3905
3906         programStats.nodes = programStats.depth = programStats.time = 
3907         programStats.score = programStats.got_only_move = 0;
3908         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3909
3910         strcpy(bookMove, "move ");
3911         strcat(bookMove, bookHit);
3912         HandleMachineMove(bookMove, &first);
3913     }
3914 #endif
3915 }
3916
3917 void
3918 GetMoveListEvent()
3919 {
3920     char buf[MSG_SIZ];
3921     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3922         ics_getting_history = H_REQUESTED;
3923         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3924         SendToICS(buf);
3925     }
3926 }
3927
3928 void
3929 AnalysisPeriodicEvent(force)
3930      int force;
3931 {
3932     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3933          && !force) || !appData.periodicUpdates)
3934       return;
3935
3936     /* Send . command to Crafty to collect stats */
3937     SendToProgram(".\n", &first);
3938
3939     /* Don't send another until we get a response (this makes
3940        us stop sending to old Crafty's which don't understand
3941        the "." command (sending illegal cmds resets node count & time,
3942        which looks bad)) */
3943     programStats.ok_to_send = 0;
3944 }
3945
3946 void ics_update_width(new_width)
3947         int new_width;
3948 {
3949         ics_printf("set width %d\n", new_width);
3950 }
3951
3952 void
3953 SendMoveToProgram(moveNum, cps)
3954      int moveNum;
3955      ChessProgramState *cps;
3956 {
3957     char buf[MSG_SIZ];
3958
3959     if (cps->useUsermove) {
3960       SendToProgram("usermove ", cps);
3961     }
3962     if (cps->useSAN) {
3963       char *space;
3964       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3965         int len = space - parseList[moveNum];
3966         memcpy(buf, parseList[moveNum], len);
3967         buf[len++] = '\n';
3968         buf[len] = NULLCHAR;
3969       } else {
3970         sprintf(buf, "%s\n", parseList[moveNum]);
3971       }
3972       SendToProgram(buf, cps);
3973     } else {
3974       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3975         AlphaRank(moveList[moveNum], 4);
3976         SendToProgram(moveList[moveNum], cps);
3977         AlphaRank(moveList[moveNum], 4); // and back
3978       } else
3979       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3980        * the engine. It would be nice to have a better way to identify castle 
3981        * moves here. */
3982       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3983                                                                          && cps->useOOCastle) {
3984         int fromX = moveList[moveNum][0] - AAA; 
3985         int fromY = moveList[moveNum][1] - ONE;
3986         int toX = moveList[moveNum][2] - AAA; 
3987         int toY = moveList[moveNum][3] - ONE;
3988         if((boards[moveNum][fromY][fromX] == WhiteKing 
3989             && boards[moveNum][toY][toX] == WhiteRook)
3990            || (boards[moveNum][fromY][fromX] == BlackKing 
3991                && boards[moveNum][toY][toX] == BlackRook)) {
3992           if(toX > fromX) SendToProgram("O-O\n", cps);
3993           else SendToProgram("O-O-O\n", cps);
3994         }
3995         else SendToProgram(moveList[moveNum], cps);
3996       }
3997       else SendToProgram(moveList[moveNum], cps);
3998       /* End of additions by Tord */
3999     }
4000
4001     /* [HGM] setting up the opening has brought engine in force mode! */
4002     /*       Send 'go' if we are in a mode where machine should play. */
4003     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4004         (gameMode == TwoMachinesPlay   ||
4005 #ifdef ZIPPY
4006          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4007 #endif
4008          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4009         SendToProgram("go\n", cps);
4010   if (appData.debugMode) {
4011     fprintf(debugFP, "(extra)\n");
4012   }
4013     }
4014     setboardSpoiledMachineBlack = 0;
4015 }
4016
4017 void
4018 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4019      ChessMove moveType;
4020      int fromX, fromY, toX, toY;
4021 {
4022     char user_move[MSG_SIZ];
4023
4024     switch (moveType) {
4025       default:
4026         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4027                 (int)moveType, fromX, fromY, toX, toY);
4028         DisplayError(user_move + strlen("say "), 0);
4029         break;
4030       case WhiteKingSideCastle:
4031       case BlackKingSideCastle:
4032       case WhiteQueenSideCastleWild:
4033       case BlackQueenSideCastleWild:
4034       /* PUSH Fabien */
4035       case WhiteHSideCastleFR:
4036       case BlackHSideCastleFR:
4037       /* POP Fabien */
4038         sprintf(user_move, "o-o\n");
4039         break;
4040       case WhiteQueenSideCastle:
4041       case BlackQueenSideCastle:
4042       case WhiteKingSideCastleWild:
4043       case BlackKingSideCastleWild:
4044       /* PUSH Fabien */
4045       case WhiteASideCastleFR:
4046       case BlackASideCastleFR:
4047       /* POP Fabien */
4048         sprintf(user_move, "o-o-o\n");
4049         break;
4050       case WhitePromotionQueen:
4051       case BlackPromotionQueen:
4052       case WhitePromotionRook:
4053       case BlackPromotionRook:
4054       case WhitePromotionBishop:
4055       case BlackPromotionBishop:
4056       case WhitePromotionKnight:
4057       case BlackPromotionKnight:
4058       case WhitePromotionKing:
4059       case BlackPromotionKing:
4060       case WhitePromotionChancellor:
4061       case BlackPromotionChancellor:
4062       case WhitePromotionArchbishop:
4063       case BlackPromotionArchbishop:
4064         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4065             sprintf(user_move, "%c%c%c%c=%c\n",
4066                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4067                 PieceToChar(WhiteFerz));
4068         else if(gameInfo.variant == VariantGreat)
4069             sprintf(user_move, "%c%c%c%c=%c\n",
4070                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4071                 PieceToChar(WhiteMan));
4072         else
4073             sprintf(user_move, "%c%c%c%c=%c\n",
4074                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4075                 PieceToChar(PromoPiece(moveType)));
4076         break;
4077       case WhiteDrop:
4078       case BlackDrop:
4079         sprintf(user_move, "%c@%c%c\n",
4080                 ToUpper(PieceToChar((ChessSquare) fromX)),
4081                 AAA + toX, ONE + toY);
4082         break;
4083       case NormalMove:
4084       case WhiteCapturesEnPassant:
4085       case BlackCapturesEnPassant:
4086       case IllegalMove:  /* could be a variant we don't quite understand */
4087         sprintf(user_move, "%c%c%c%c\n",
4088                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4089         break;
4090     }
4091     SendToICS(user_move);
4092     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4093         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4094 }
4095
4096 void
4097 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4098      int rf, ff, rt, ft;
4099      char promoChar;
4100      char move[7];
4101 {
4102     if (rf == DROP_RANK) {
4103         sprintf(move, "%c@%c%c\n",
4104                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4105     } else {
4106         if (promoChar == 'x' || promoChar == NULLCHAR) {
4107             sprintf(move, "%c%c%c%c\n",
4108                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4109         } else {
4110             sprintf(move, "%c%c%c%c%c\n",
4111                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4112         }
4113     }
4114 }
4115
4116 void
4117 ProcessICSInitScript(f)
4118      FILE *f;
4119 {
4120     char buf[MSG_SIZ];
4121
4122     while (fgets(buf, MSG_SIZ, f)) {
4123         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4124     }
4125
4126     fclose(f);
4127 }
4128
4129
4130 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4131 void
4132 AlphaRank(char *move, int n)
4133 {
4134 //    char *p = move, c; int x, y;
4135
4136     if (appData.debugMode) {
4137         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4138     }
4139
4140     if(move[1]=='*' && 
4141        move[2]>='0' && move[2]<='9' &&
4142        move[3]>='a' && move[3]<='x'    ) {
4143         move[1] = '@';
4144         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4145         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4146     } else
4147     if(move[0]>='0' && move[0]<='9' &&
4148        move[1]>='a' && move[1]<='x' &&
4149        move[2]>='0' && move[2]<='9' &&
4150        move[3]>='a' && move[3]<='x'    ) {
4151         /* input move, Shogi -> normal */
4152         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4153         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4154         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4155         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4156     } else
4157     if(move[1]=='@' &&
4158        move[3]>='0' && move[3]<='9' &&
4159        move[2]>='a' && move[2]<='x'    ) {
4160         move[1] = '*';
4161         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4162         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4163     } else
4164     if(
4165        move[0]>='a' && move[0]<='x' &&
4166        move[3]>='0' && move[3]<='9' &&
4167        move[2]>='a' && move[2]<='x'    ) {
4168          /* output move, normal -> Shogi */
4169         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4170         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4171         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4172         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4173         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4174     }
4175     if (appData.debugMode) {
4176         fprintf(debugFP, "   out = '%s'\n", move);
4177     }
4178 }
4179
4180 /* Parser for moves from gnuchess, ICS, or user typein box */
4181 Boolean
4182 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4183      char *move;
4184      int moveNum;
4185      ChessMove *moveType;
4186      int *fromX, *fromY, *toX, *toY;
4187      char *promoChar;
4188 {       
4189     if (appData.debugMode) {
4190         fprintf(debugFP, "move to parse: %s\n", move);
4191     }
4192     *moveType = yylexstr(moveNum, move);
4193
4194     switch (*moveType) {
4195       case WhitePromotionChancellor:
4196       case BlackPromotionChancellor:
4197       case WhitePromotionArchbishop:
4198       case BlackPromotionArchbishop:
4199       case WhitePromotionQueen:
4200       case BlackPromotionQueen:
4201       case WhitePromotionRook:
4202       case BlackPromotionRook:
4203       case WhitePromotionBishop:
4204       case BlackPromotionBishop:
4205       case WhitePromotionKnight:
4206       case BlackPromotionKnight:
4207       case WhitePromotionKing:
4208       case BlackPromotionKing:
4209       case NormalMove:
4210       case WhiteCapturesEnPassant:
4211       case BlackCapturesEnPassant:
4212       case WhiteKingSideCastle:
4213       case WhiteQueenSideCastle:
4214       case BlackKingSideCastle:
4215       case BlackQueenSideCastle:
4216       case WhiteKingSideCastleWild:
4217       case WhiteQueenSideCastleWild:
4218       case BlackKingSideCastleWild:
4219       case BlackQueenSideCastleWild:
4220       /* Code added by Tord: */
4221       case WhiteHSideCastleFR:
4222       case WhiteASideCastleFR:
4223       case BlackHSideCastleFR:
4224       case BlackASideCastleFR:
4225       /* End of code added by Tord */
4226       case IllegalMove:         /* bug or odd chess variant */
4227         *fromX = currentMoveString[0] - AAA;
4228         *fromY = currentMoveString[1] - ONE;
4229         *toX = currentMoveString[2] - AAA;
4230         *toY = currentMoveString[3] - ONE;
4231         *promoChar = currentMoveString[4];
4232         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4233             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4234     if (appData.debugMode) {
4235         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4236     }
4237             *fromX = *fromY = *toX = *toY = 0;
4238             return FALSE;
4239         }
4240         if (appData.testLegality) {
4241           return (*moveType != IllegalMove);
4242         } else {
4243           return !(fromX == fromY && toX == toY);
4244         }
4245
4246       case WhiteDrop:
4247       case BlackDrop:
4248         *fromX = *moveType == WhiteDrop ?
4249           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4250           (int) CharToPiece(ToLower(currentMoveString[0]));
4251         *fromY = DROP_RANK;
4252         *toX = currentMoveString[2] - AAA;
4253         *toY = currentMoveString[3] - ONE;
4254         *promoChar = NULLCHAR;
4255         return TRUE;
4256
4257       case AmbiguousMove:
4258       case ImpossibleMove:
4259       case (ChessMove) 0:       /* end of file */
4260       case ElapsedTime:
4261       case Comment:
4262       case PGNTag:
4263       case NAG:
4264       case WhiteWins:
4265       case BlackWins:
4266       case GameIsDrawn:
4267       default:
4268     if (appData.debugMode) {
4269         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4270     }
4271         /* bug? */
4272         *fromX = *fromY = *toX = *toY = 0;
4273         *promoChar = NULLCHAR;
4274         return FALSE;
4275     }
4276 }
4277
4278 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4279 // All positions will have equal probability, but the current method will not provide a unique
4280 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4281 #define DARK 1
4282 #define LITE 2
4283 #define ANY 3
4284
4285 int squaresLeft[4];
4286 int piecesLeft[(int)BlackPawn];
4287 int seed, nrOfShuffles;
4288
4289 void GetPositionNumber()
4290 {       // sets global variable seed
4291         int i;
4292
4293         seed = appData.defaultFrcPosition;
4294         if(seed < 0) { // randomize based on time for negative FRC position numbers
4295                 for(i=0; i<50; i++) seed += random();
4296                 seed = random() ^ random() >> 8 ^ random() << 8;
4297                 if(seed<0) seed = -seed;
4298         }
4299 }
4300
4301 int put(Board board, int pieceType, int rank, int n, int shade)
4302 // put the piece on the (n-1)-th empty squares of the given shade
4303 {
4304         int i;
4305
4306         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4307                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4308                         board[rank][i] = (ChessSquare) pieceType;
4309                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4310                         squaresLeft[ANY]--;
4311                         piecesLeft[pieceType]--; 
4312                         return i;
4313                 }
4314         }
4315         return -1;
4316 }
4317
4318
4319 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4320 // calculate where the next piece goes, (any empty square), and put it there
4321 {
4322         int i;
4323
4324         i = seed % squaresLeft[shade];
4325         nrOfShuffles *= squaresLeft[shade];
4326         seed /= squaresLeft[shade];
4327         put(board, pieceType, rank, i, shade);
4328 }
4329
4330 void AddTwoPieces(Board board, int pieceType, int rank)
4331 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4332 {
4333         int i, n=squaresLeft[ANY], j=n-1, k;
4334
4335         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4336         i = seed % k;  // pick one
4337         nrOfShuffles *= k;
4338         seed /= k;
4339         while(i >= j) i -= j--;
4340         j = n - 1 - j; i += j;
4341         put(board, pieceType, rank, j, ANY);
4342         put(board, pieceType, rank, i, ANY);
4343 }
4344
4345 void SetUpShuffle(Board board, int number)
4346 {
4347         int i, p, first=1;
4348
4349         GetPositionNumber(); nrOfShuffles = 1;
4350
4351         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4352         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4353         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4354
4355         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4356
4357         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4358             p = (int) board[0][i];
4359             if(p < (int) BlackPawn) piecesLeft[p] ++;
4360             board[0][i] = EmptySquare;
4361         }
4362
4363         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4364             // shuffles restricted to allow normal castling put KRR first
4365             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4366                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4367             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4368                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4369             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4370                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4371             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4372                 put(board, WhiteRook, 0, 0, ANY);
4373             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4374         }
4375
4376         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4377             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4378             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4379                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4380                 while(piecesLeft[p] >= 2) {
4381                     AddOnePiece(board, p, 0, LITE);
4382                     AddOnePiece(board, p, 0, DARK);
4383                 }
4384                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4385             }
4386
4387         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4388             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4389             // but we leave King and Rooks for last, to possibly obey FRC restriction
4390             if(p == (int)WhiteRook) continue;
4391             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4392             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4393         }
4394
4395         // now everything is placed, except perhaps King (Unicorn) and Rooks
4396
4397         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4398             // Last King gets castling rights
4399             while(piecesLeft[(int)WhiteUnicorn]) {
4400                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4401                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4402             }
4403
4404             while(piecesLeft[(int)WhiteKing]) {
4405                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4406                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4407             }
4408
4409
4410         } else {
4411             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4412             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4413         }
4414
4415         // Only Rooks can be left; simply place them all
4416         while(piecesLeft[(int)WhiteRook]) {
4417                 i = put(board, WhiteRook, 0, 0, ANY);
4418                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4419                         if(first) {
4420                                 first=0;
4421                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4422                         }
4423                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4424                 }
4425         }
4426         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4427             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4428         }
4429
4430         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4431 }
4432
4433 int SetCharTable( char *table, const char * map )
4434 /* [HGM] moved here from winboard.c because of its general usefulness */
4435 /*       Basically a safe strcpy that uses the last character as King */
4436 {
4437     int result = FALSE; int NrPieces;
4438
4439     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4440                     && NrPieces >= 12 && !(NrPieces&1)) {
4441         int i; /* [HGM] Accept even length from 12 to 34 */
4442
4443         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4444         for( i=0; i<NrPieces/2-1; i++ ) {
4445             table[i] = map[i];
4446             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4447         }
4448         table[(int) WhiteKing]  = map[NrPieces/2-1];
4449         table[(int) BlackKing]  = map[NrPieces-1];
4450
4451         result = TRUE;
4452     }
4453
4454     return result;
4455 }
4456
4457 void Prelude(Board board)
4458 {       // [HGM] superchess: random selection of exo-pieces
4459         int i, j, k; ChessSquare p; 
4460         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4461
4462         GetPositionNumber(); // use FRC position number
4463
4464         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4465             SetCharTable(pieceToChar, appData.pieceToCharTable);
4466             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4467                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4468         }
4469
4470         j = seed%4;                 seed /= 4; 
4471         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4472         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4473         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4474         j = seed%3 + (seed%3 >= j); seed /= 3; 
4475         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4476         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4477         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4478         j = seed%3;                 seed /= 3; 
4479         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4480         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4481         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4482         j = seed%2 + (seed%2 >= j); seed /= 2; 
4483         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4484         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4485         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4486         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4487         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4488         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4489         put(board, exoPieces[0],    0, 0, ANY);
4490         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4491 }
4492
4493 void
4494 InitPosition(redraw)
4495      int redraw;
4496 {
4497     ChessSquare (* pieces)[BOARD_SIZE];
4498     int i, j, pawnRow, overrule,
4499     oldx = gameInfo.boardWidth,
4500     oldy = gameInfo.boardHeight,
4501     oldh = gameInfo.holdingsWidth,
4502     oldv = gameInfo.variant;
4503
4504     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4505
4506     /* [AS] Initialize pv info list [HGM] and game status */
4507     {
4508         for( i=0; i<MAX_MOVES; i++ ) {
4509             pvInfoList[i].depth = 0;
4510             epStatus[i]=EP_NONE;
4511             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4512         }
4513
4514         initialRulePlies = 0; /* 50-move counter start */
4515
4516         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4517         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4518     }
4519
4520     
4521     /* [HGM] logic here is completely changed. In stead of full positions */
4522     /* the initialized data only consist of the two backranks. The switch */
4523     /* selects which one we will use, which is than copied to the Board   */
4524     /* initialPosition, which for the rest is initialized by Pawns and    */
4525     /* empty squares. This initial position is then copied to boards[0],  */
4526     /* possibly after shuffling, so that it remains available.            */
4527
4528     gameInfo.holdingsWidth = 0; /* default board sizes */
4529     gameInfo.boardWidth    = 8;
4530     gameInfo.boardHeight   = 8;
4531     gameInfo.holdingsSize  = 0;
4532     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4533     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4534     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4535
4536     switch (gameInfo.variant) {
4537     case VariantFischeRandom:
4538       shuffleOpenings = TRUE;
4539     default:
4540       pieces = FIDEArray;
4541       break;
4542     case VariantShatranj:
4543       pieces = ShatranjArray;
4544       nrCastlingRights = 0;
4545       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4546       break;
4547     case VariantTwoKings:
4548       pieces = twoKingsArray;
4549       break;
4550     case VariantCapaRandom:
4551       shuffleOpenings = TRUE;
4552     case VariantCapablanca:
4553       pieces = CapablancaArray;
4554       gameInfo.boardWidth = 10;
4555       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4556       break;
4557     case VariantGothic:
4558       pieces = GothicArray;
4559       gameInfo.boardWidth = 10;
4560       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4561       break;
4562     case VariantJanus:
4563       pieces = JanusArray;
4564       gameInfo.boardWidth = 10;
4565       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4566       nrCastlingRights = 6;
4567         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4568         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4569         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4570         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4571         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4572         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4573       break;
4574     case VariantFalcon:
4575       pieces = FalconArray;
4576       gameInfo.boardWidth = 10;
4577       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4578       break;
4579     case VariantXiangqi:
4580       pieces = XiangqiArray;
4581       gameInfo.boardWidth  = 9;
4582       gameInfo.boardHeight = 10;
4583       nrCastlingRights = 0;
4584       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4585       break;
4586     case VariantShogi:
4587       pieces = ShogiArray;
4588       gameInfo.boardWidth  = 9;
4589       gameInfo.boardHeight = 9;
4590       gameInfo.holdingsSize = 7;
4591       nrCastlingRights = 0;
4592       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4593       break;
4594     case VariantCourier:
4595       pieces = CourierArray;
4596       gameInfo.boardWidth  = 12;
4597       nrCastlingRights = 0;
4598       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4599       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4600       break;
4601     case VariantKnightmate:
4602       pieces = KnightmateArray;
4603       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4604       break;
4605     case VariantFairy:
4606       pieces = fairyArray;
4607       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4608       break;
4609     case VariantGreat:
4610       pieces = GreatArray;
4611       gameInfo.boardWidth = 10;
4612       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4613       gameInfo.holdingsSize = 8;
4614       break;
4615     case VariantSuper:
4616       pieces = FIDEArray;
4617       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4618       gameInfo.holdingsSize = 8;
4619       startedFromSetupPosition = TRUE;
4620       break;
4621     case VariantCrazyhouse:
4622     case VariantBughouse:
4623       pieces = FIDEArray;
4624       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4625       gameInfo.holdingsSize = 5;
4626       break;
4627     case VariantWildCastle:
4628       pieces = FIDEArray;
4629       /* !!?shuffle with kings guaranteed to be on d or e file */
4630       shuffleOpenings = 1;
4631       break;
4632     case VariantNoCastle:
4633       pieces = FIDEArray;
4634       nrCastlingRights = 0;
4635       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4636       /* !!?unconstrained back-rank shuffle */
4637       shuffleOpenings = 1;
4638       break;
4639     }
4640
4641     overrule = 0;
4642     if(appData.NrFiles >= 0) {
4643         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4644         gameInfo.boardWidth = appData.NrFiles;
4645     }
4646     if(appData.NrRanks >= 0) {
4647         gameInfo.boardHeight = appData.NrRanks;
4648     }
4649     if(appData.holdingsSize >= 0) {
4650         i = appData.holdingsSize;
4651         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4652         gameInfo.holdingsSize = i;
4653     }
4654     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4655     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4656         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4657
4658     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4659     if(pawnRow < 1) pawnRow = 1;
4660
4661     /* User pieceToChar list overrules defaults */
4662     if(appData.pieceToCharTable != NULL)
4663         SetCharTable(pieceToChar, appData.pieceToCharTable);
4664
4665     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4666
4667         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4668             s = (ChessSquare) 0; /* account holding counts in guard band */
4669         for( i=0; i<BOARD_HEIGHT; i++ )
4670             initialPosition[i][j] = s;
4671
4672         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4673         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4674         initialPosition[pawnRow][j] = WhitePawn;
4675         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4676         if(gameInfo.variant == VariantXiangqi) {
4677             if(j&1) {
4678                 initialPosition[pawnRow][j] = 
4679                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4680                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4681                    initialPosition[2][j] = WhiteCannon;
4682                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4683                 }
4684             }
4685         }
4686         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4687     }
4688     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4689
4690             j=BOARD_LEFT+1;
4691             initialPosition[1][j] = WhiteBishop;
4692             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4693             j=BOARD_RGHT-2;
4694             initialPosition[1][j] = WhiteRook;
4695             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4696     }
4697
4698     if( nrCastlingRights == -1) {
4699         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4700         /*       This sets default castling rights from none to normal corners   */
4701         /* Variants with other castling rights must set them themselves above    */
4702         nrCastlingRights = 6;
4703        
4704         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4705         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4706         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4707         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4708         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4709         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4710      }
4711
4712      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4713      if(gameInfo.variant == VariantGreat) { // promotion commoners
4714         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4715         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4716         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4717         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4718      }
4719   if (appData.debugMode) {
4720     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4721   }
4722     if(shuffleOpenings) {
4723         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4724         startedFromSetupPosition = TRUE;
4725     }
4726     if(startedFromPositionFile) {
4727       /* [HGM] loadPos: use PositionFile for every new game */
4728       CopyBoard(initialPosition, filePosition);
4729       for(i=0; i<nrCastlingRights; i++)
4730           castlingRights[0][i] = initialRights[i] = fileRights[i];
4731       startedFromSetupPosition = TRUE;
4732     }
4733
4734     CopyBoard(boards[0], initialPosition);
4735
4736     if(oldx != gameInfo.boardWidth ||
4737        oldy != gameInfo.boardHeight ||
4738        oldh != gameInfo.holdingsWidth
4739 #ifdef GOTHIC
4740        || oldv == VariantGothic ||        // For licensing popups
4741        gameInfo.variant == VariantGothic
4742 #endif
4743 #ifdef FALCON
4744        || oldv == VariantFalcon ||
4745        gameInfo.variant == VariantFalcon
4746 #endif
4747                                          )
4748             InitDrawingSizes(-2 ,0);
4749
4750     if (redraw)
4751       DrawPosition(TRUE, boards[currentMove]);
4752 }
4753
4754 void
4755 SendBoard(cps, moveNum)
4756      ChessProgramState *cps;
4757      int moveNum;
4758 {
4759     char message[MSG_SIZ];
4760     
4761     if (cps->useSetboard) {
4762       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4763       sprintf(message, "setboard %s\n", fen);
4764       SendToProgram(message, cps);
4765       free(fen);
4766
4767     } else {
4768       ChessSquare *bp;
4769       int i, j;
4770       /* Kludge to set black to move, avoiding the troublesome and now
4771        * deprecated "black" command.
4772        */
4773       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4774
4775       SendToProgram("edit\n", cps);
4776       SendToProgram("#\n", cps);
4777       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4778         bp = &boards[moveNum][i][BOARD_LEFT];
4779         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4780           if ((int) *bp < (int) BlackPawn) {
4781             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4782                     AAA + j, ONE + i);
4783             if(message[0] == '+' || message[0] == '~') {
4784                 sprintf(message, "%c%c%c+\n",
4785                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4786                         AAA + j, ONE + i);
4787             }
4788             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4789                 message[1] = BOARD_RGHT   - 1 - j + '1';
4790                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4791             }
4792             SendToProgram(message, cps);
4793           }
4794         }
4795       }
4796     
4797       SendToProgram("c\n", cps);
4798       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4799         bp = &boards[moveNum][i][BOARD_LEFT];
4800         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4801           if (((int) *bp != (int) EmptySquare)
4802               && ((int) *bp >= (int) BlackPawn)) {
4803             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4804                     AAA + j, ONE + i);
4805             if(message[0] == '+' || message[0] == '~') {
4806                 sprintf(message, "%c%c%c+\n",
4807                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4808                         AAA + j, ONE + i);
4809             }
4810             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4811                 message[1] = BOARD_RGHT   - 1 - j + '1';
4812                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4813             }
4814             SendToProgram(message, cps);
4815           }
4816         }
4817       }
4818     
4819       SendToProgram(".\n", cps);
4820     }
4821     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4822 }
4823
4824 int
4825 IsPromotion(fromX, fromY, toX, toY)
4826      int fromX, fromY, toX, toY;
4827 {
4828     /* [HGM] add Shogi promotions */
4829     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4830     ChessSquare piece;
4831
4832     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4833       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4834    /* [HGM] Note to self: line above also weeds out drops */
4835     piece = boards[currentMove][fromY][fromX];
4836     if(gameInfo.variant == VariantShogi) {
4837         promotionZoneSize = 3;
4838         highestPromotingPiece = (int)WhiteKing;
4839         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4840            and if in normal chess we then allow promotion to King, why not
4841            allow promotion of other piece in Shogi?                         */
4842     }
4843     if((int)piece >= BlackPawn) {
4844         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4845              return FALSE;
4846         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4847     } else {
4848         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4849            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4850     }
4851     return ( (int)piece <= highestPromotingPiece );
4852 }
4853
4854 int
4855 InPalace(row, column)
4856      int row, column;
4857 {   /* [HGM] for Xiangqi */
4858     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4859          column < (BOARD_WIDTH + 4)/2 &&
4860          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4861     return FALSE;
4862 }
4863
4864 int
4865 PieceForSquare (x, y)
4866      int x;
4867      int y;
4868 {
4869   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4870      return -1;
4871   else
4872      return boards[currentMove][y][x];
4873 }
4874
4875 int
4876 OKToStartUserMove(x, y)
4877      int x, y;
4878 {
4879     ChessSquare from_piece;
4880     int white_piece;
4881
4882     if (matchMode) return FALSE;
4883     if (gameMode == EditPosition) return TRUE;
4884
4885     if (x >= 0 && y >= 0)
4886       from_piece = boards[currentMove][y][x];
4887     else
4888       from_piece = EmptySquare;
4889
4890     if (from_piece == EmptySquare) return FALSE;
4891
4892     white_piece = (int)from_piece >= (int)WhitePawn &&
4893       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4894
4895     switch (gameMode) {
4896       case PlayFromGameFile:
4897       case AnalyzeFile:
4898       case TwoMachinesPlay:
4899       case EndOfGame:
4900         return FALSE;
4901
4902       case IcsObserving:
4903       case IcsIdle:
4904         return FALSE;
4905
4906       case MachinePlaysWhite:
4907       case IcsPlayingBlack:
4908         if (appData.zippyPlay) return FALSE;
4909         if (white_piece) {
4910             DisplayMoveError(_("You are playing Black"));
4911             return FALSE;
4912         }
4913         break;
4914
4915       case MachinePlaysBlack:
4916       case IcsPlayingWhite:
4917         if (appData.zippyPlay) return FALSE;
4918         if (!white_piece) {
4919             DisplayMoveError(_("You are playing White"));
4920             return FALSE;
4921         }
4922         break;
4923
4924       case EditGame:
4925         if (!white_piece && WhiteOnMove(currentMove)) {
4926             DisplayMoveError(_("It is White's turn"));
4927             return FALSE;
4928         }           
4929         if (white_piece && !WhiteOnMove(currentMove)) {
4930             DisplayMoveError(_("It is Black's turn"));
4931             return FALSE;
4932         }           
4933         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4934             /* Editing correspondence game history */
4935             /* Could disallow this or prompt for confirmation */
4936             cmailOldMove = -1;
4937         }
4938         if (currentMove < forwardMostMove) {
4939             /* Discarding moves */
4940             /* Could prompt for confirmation here,
4941                but I don't think that's such a good idea */
4942             forwardMostMove = currentMove;
4943         }
4944         break;
4945
4946       case BeginningOfGame:
4947         if (appData.icsActive) return FALSE;
4948         if (!appData.noChessProgram) {
4949             if (!white_piece) {
4950                 DisplayMoveError(_("You are playing White"));
4951                 return FALSE;
4952             }
4953         }
4954         break;
4955         
4956       case Training:
4957         if (!white_piece && WhiteOnMove(currentMove)) {
4958             DisplayMoveError(_("It is White's turn"));
4959             return FALSE;
4960         }           
4961         if (white_piece && !WhiteOnMove(currentMove)) {
4962             DisplayMoveError(_("It is Black's turn"));
4963             return FALSE;
4964         }           
4965         break;
4966
4967       default:
4968       case IcsExamining:
4969         break;
4970     }
4971     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4972         && gameMode != AnalyzeFile && gameMode != Training) {
4973         DisplayMoveError(_("Displayed position is not current"));
4974         return FALSE;
4975     }
4976     return TRUE;
4977 }
4978
4979 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4980 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4981 int lastLoadGameUseList = FALSE;
4982 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4983 ChessMove lastLoadGameStart = (ChessMove) 0;
4984
4985 ChessMove
4986 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
4987      int fromX, fromY, toX, toY;
4988      int promoChar;
4989      Boolean captureOwn;
4990 {
4991     ChessMove moveType;
4992     ChessSquare pdown, pup;
4993
4994     if (fromX < 0 || fromY < 0) return ImpossibleMove;
4995
4996     /* [HGM] suppress all moves into holdings area and guard band */
4997     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
4998             return ImpossibleMove;
4999
5000     /* [HGM] <sameColor> moved to here from winboard.c */
5001     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5002     pdown = boards[currentMove][fromY][fromX];
5003     pup = boards[currentMove][toY][toX];
5004     if (    gameMode != EditPosition && !captureOwn &&
5005             (WhitePawn <= pdown && pdown < BlackPawn &&
5006              WhitePawn <= pup && pup < BlackPawn  ||
5007              BlackPawn <= pdown && pdown < EmptySquare &&
5008              BlackPawn <= pup && pup < EmptySquare 
5009             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5010                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5011                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5012                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5013                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5014         )           )
5015          return Comment;
5016
5017     /* Check if the user is playing in turn.  This is complicated because we
5018        let the user "pick up" a piece before it is his turn.  So the piece he
5019        tried to pick up may have been captured by the time he puts it down!
5020        Therefore we use the color the user is supposed to be playing in this
5021        test, not the color of the piece that is currently on the starting
5022        square---except in EditGame mode, where the user is playing both
5023        sides; fortunately there the capture race can't happen.  (It can
5024        now happen in IcsExamining mode, but that's just too bad.  The user
5025        will get a somewhat confusing message in that case.)
5026        */
5027
5028     switch (gameMode) {
5029       case PlayFromGameFile:
5030       case AnalyzeFile:
5031       case TwoMachinesPlay:
5032       case EndOfGame:
5033       case IcsObserving:
5034       case IcsIdle:
5035         /* We switched into a game mode where moves are not accepted,
5036            perhaps while the mouse button was down. */
5037         return ImpossibleMove;
5038
5039       case MachinePlaysWhite:
5040         /* User is moving for Black */
5041         if (WhiteOnMove(currentMove)) {
5042             DisplayMoveError(_("It is White's turn"));
5043             return ImpossibleMove;
5044         }
5045         break;
5046
5047       case MachinePlaysBlack:
5048         /* User is moving for White */
5049         if (!WhiteOnMove(currentMove)) {
5050             DisplayMoveError(_("It is Black's turn"));
5051             return ImpossibleMove;
5052         }
5053         break;
5054
5055       case EditGame:
5056       case IcsExamining:
5057       case BeginningOfGame:
5058       case AnalyzeMode:
5059       case Training:
5060         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5061             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5062             /* User is moving for Black */
5063             if (WhiteOnMove(currentMove)) {
5064                 DisplayMoveError(_("It is White's turn"));
5065                 return ImpossibleMove;
5066             }
5067         } else {
5068             /* User is moving for White */
5069             if (!WhiteOnMove(currentMove)) {
5070                 DisplayMoveError(_("It is Black's turn"));
5071                 return ImpossibleMove;
5072             }
5073         }
5074         break;
5075
5076       case IcsPlayingBlack:
5077         /* User is moving for Black */
5078         if (WhiteOnMove(currentMove)) {
5079             if (!appData.premove) {
5080                 DisplayMoveError(_("It is White's turn"));
5081             } else if (toX >= 0 && toY >= 0) {
5082                 premoveToX = toX;
5083                 premoveToY = toY;
5084                 premoveFromX = fromX;
5085                 premoveFromY = fromY;
5086                 premovePromoChar = promoChar;
5087                 gotPremove = 1;
5088                 if (appData.debugMode) 
5089                     fprintf(debugFP, "Got premove: fromX %d,"
5090                             "fromY %d, toX %d, toY %d\n",
5091                             fromX, fromY, toX, toY);
5092             }
5093             return ImpossibleMove;
5094         }
5095         break;
5096
5097       case IcsPlayingWhite:
5098         /* User is moving for White */
5099         if (!WhiteOnMove(currentMove)) {
5100             if (!appData.premove) {
5101                 DisplayMoveError(_("It is Black's turn"));
5102             } else if (toX >= 0 && toY >= 0) {
5103                 premoveToX = toX;
5104                 premoveToY = toY;
5105                 premoveFromX = fromX;
5106                 premoveFromY = fromY;
5107                 premovePromoChar = promoChar;
5108                 gotPremove = 1;
5109                 if (appData.debugMode) 
5110                     fprintf(debugFP, "Got premove: fromX %d,"
5111                             "fromY %d, toX %d, toY %d\n",
5112                             fromX, fromY, toX, toY);
5113             }
5114             return ImpossibleMove;
5115         }
5116         break;
5117
5118       default:
5119         break;
5120
5121       case EditPosition:
5122         /* EditPosition, empty square, or different color piece;
5123            click-click move is possible */
5124         if (toX == -2 || toY == -2) {
5125             boards[0][fromY][fromX] = EmptySquare;
5126             return AmbiguousMove;
5127         } else if (toX >= 0 && toY >= 0) {
5128             boards[0][toY][toX] = boards[0][fromY][fromX];
5129             boards[0][fromY][fromX] = EmptySquare;
5130             return AmbiguousMove;
5131         }
5132         return ImpossibleMove;
5133     }
5134
5135     /* [HGM] If move started in holdings, it means a drop */
5136     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5137          if( pup != EmptySquare ) return ImpossibleMove;
5138          if(appData.testLegality) {
5139              /* it would be more logical if LegalityTest() also figured out
5140               * which drops are legal. For now we forbid pawns on back rank.
5141               * Shogi is on its own here...
5142               */
5143              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5144                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5145                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5146          }
5147          return WhiteDrop; /* Not needed to specify white or black yet */
5148     }
5149
5150     userOfferedDraw = FALSE;
5151         
5152     /* [HGM] always test for legality, to get promotion info */
5153     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5154                           epStatus[currentMove], castlingRights[currentMove],
5155                                          fromY, fromX, toY, toX, promoChar);
5156     /* [HGM] but possibly ignore an IllegalMove result */
5157     if (appData.testLegality) {
5158         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5159             DisplayMoveError(_("Illegal move"));
5160             return ImpossibleMove;
5161         }
5162     }
5163 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5164     return moveType;
5165     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5166        function is made into one that returns an OK move type if FinishMove
5167        should be called. This to give the calling driver routine the
5168        opportunity to finish the userMove input with a promotion popup,
5169        without bothering the user with this for invalid or illegal moves */
5170
5171 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5172 }
5173
5174 /* Common tail of UserMoveEvent and DropMenuEvent */
5175 int
5176 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5177      ChessMove moveType;
5178      int fromX, fromY, toX, toY;
5179      /*char*/int promoChar;
5180 {
5181     char *bookHit = 0;
5182 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5183     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5184         // [HGM] superchess: suppress promotions to non-available piece
5185         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5186         if(WhiteOnMove(currentMove)) {
5187             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5188         } else {
5189             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5190         }
5191     }
5192
5193     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5194        move type in caller when we know the move is a legal promotion */
5195     if(moveType == NormalMove && promoChar)
5196         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5197 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5198     /* [HGM] convert drag-and-drop piece drops to standard form */
5199     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5200          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5201            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5202                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5203 //         fromX = boards[currentMove][fromY][fromX];
5204            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5205            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5206            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5207            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5208          fromY = DROP_RANK;
5209     }
5210
5211     /* [HGM] <popupFix> The following if has been moved here from
5212        UserMoveEvent(). Because it seemed to belon here (why not allow
5213        piece drops in training games?), and because it can only be
5214        performed after it is known to what we promote. */
5215     if (gameMode == Training) {
5216       /* compare the move played on the board to the next move in the
5217        * game. If they match, display the move and the opponent's response. 
5218        * If they don't match, display an error message.
5219        */
5220       int saveAnimate;
5221       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5222       CopyBoard(testBoard, boards[currentMove]);
5223       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5224
5225       if (CompareBoards(testBoard, boards[currentMove+1])) {
5226         ForwardInner(currentMove+1);
5227
5228         /* Autoplay the opponent's response.
5229          * if appData.animate was TRUE when Training mode was entered,
5230          * the response will be animated.
5231          */
5232         saveAnimate = appData.animate;
5233         appData.animate = animateTraining;
5234         ForwardInner(currentMove+1);
5235         appData.animate = saveAnimate;
5236
5237         /* check for the end of the game */
5238         if (currentMove >= forwardMostMove) {
5239           gameMode = PlayFromGameFile;
5240           ModeHighlight();
5241           SetTrainingModeOff();
5242           DisplayInformation(_("End of game"));
5243         }
5244       } else {
5245         DisplayError(_("Incorrect move"), 0);
5246       }
5247       return 1;
5248     }
5249
5250   /* Ok, now we know that the move is good, so we can kill
5251      the previous line in Analysis Mode */
5252   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5253     forwardMostMove = currentMove;
5254   }
5255
5256   /* If we need the chess program but it's dead, restart it */
5257   ResurrectChessProgram();
5258
5259   /* A user move restarts a paused game*/
5260   if (pausing)
5261     PauseEvent();
5262
5263   thinkOutput[0] = NULLCHAR;
5264
5265   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5266
5267   if (gameMode == BeginningOfGame) {
5268     if (appData.noChessProgram) {
5269       gameMode = EditGame;
5270       SetGameInfo();
5271     } else {
5272       char buf[MSG_SIZ];
5273       gameMode = MachinePlaysBlack;
5274       StartClocks();
5275       SetGameInfo();
5276       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5277       DisplayTitle(buf);
5278       if (first.sendName) {
5279         sprintf(buf, "name %s\n", gameInfo.white);
5280         SendToProgram(buf, &first);
5281       }
5282       StartClocks();
5283     }
5284     ModeHighlight();
5285   }
5286 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5287   /* Relay move to ICS or chess engine */
5288   if (appData.icsActive) {
5289     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5290         gameMode == IcsExamining) {
5291       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5292       ics_user_moved = 1;
5293     }
5294   } else {
5295     if (first.sendTime && (gameMode == BeginningOfGame ||
5296                            gameMode == MachinePlaysWhite ||
5297                            gameMode == MachinePlaysBlack)) {
5298       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5299     }
5300     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5301          // [HGM] book: if program might be playing, let it use book
5302         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5303         first.maybeThinking = TRUE;
5304     } else SendMoveToProgram(forwardMostMove-1, &first);
5305     if (currentMove == cmailOldMove + 1) {
5306       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5307     }
5308   }
5309
5310   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5311
5312   switch (gameMode) {
5313   case EditGame:
5314     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5315                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5316     case MT_NONE:
5317     case MT_CHECK:
5318       break;
5319     case MT_CHECKMATE:
5320     case MT_STAINMATE:
5321       if (WhiteOnMove(currentMove)) {
5322         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5323       } else {
5324         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5325       }
5326       break;
5327     case MT_STALEMATE:
5328       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5329       break;
5330     }
5331     break;
5332     
5333   case MachinePlaysBlack:
5334   case MachinePlaysWhite:
5335     /* disable certain menu options while machine is thinking */
5336     SetMachineThinkingEnables();
5337     break;
5338
5339   default:
5340     break;
5341   }
5342
5343   if(bookHit) { // [HGM] book: simulate book reply
5344         static char bookMove[MSG_SIZ]; // a bit generous?
5345
5346         programStats.nodes = programStats.depth = programStats.time = 
5347         programStats.score = programStats.got_only_move = 0;
5348         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5349
5350         strcpy(bookMove, "move ");
5351         strcat(bookMove, bookHit);
5352         HandleMachineMove(bookMove, &first);
5353   }
5354   return 1;
5355 }
5356
5357 void
5358 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5359      int fromX, fromY, toX, toY;
5360      int promoChar;
5361 {
5362     /* [HGM] This routine was added to allow calling of its two logical
5363        parts from other modules in the old way. Before, UserMoveEvent()
5364        automatically called FinishMove() if the move was OK, and returned
5365        otherwise. I separated the two, in order to make it possible to
5366        slip a promotion popup in between. But that it always needs two
5367        calls, to the first part, (now called UserMoveTest() ), and to
5368        FinishMove if the first part succeeded. Calls that do not need
5369        to do anything in between, can call this routine the old way. 
5370     */
5371     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5372 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5373     if(moveType == AmbiguousMove)
5374         DrawPosition(FALSE, boards[currentMove]);
5375     else if(moveType != ImpossibleMove && moveType != Comment)
5376         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5377 }
5378
5379 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5380 {
5381 //    char * hint = lastHint;
5382     FrontEndProgramStats stats;
5383
5384     stats.which = cps == &first ? 0 : 1;
5385     stats.depth = cpstats->depth;
5386     stats.nodes = cpstats->nodes;
5387     stats.score = cpstats->score;
5388     stats.time = cpstats->time;
5389     stats.pv = cpstats->movelist;
5390     stats.hint = lastHint;
5391     stats.an_move_index = 0;
5392     stats.an_move_count = 0;
5393
5394     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5395         stats.hint = cpstats->move_name;
5396         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5397         stats.an_move_count = cpstats->nr_moves;
5398     }
5399
5400     SetProgramStats( &stats );
5401 }
5402
5403 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5404 {   // [HGM] book: this routine intercepts moves to simulate book replies
5405     char *bookHit = NULL;
5406
5407     //first determine if the incoming move brings opponent into his book
5408     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5409         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5410     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5411     if(bookHit != NULL && !cps->bookSuspend) {
5412         // make sure opponent is not going to reply after receiving move to book position
5413         SendToProgram("force\n", cps);
5414         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5415     }
5416     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5417     // now arrange restart after book miss
5418     if(bookHit) {
5419         // after a book hit we never send 'go', and the code after the call to this routine
5420         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5421         char buf[MSG_SIZ];
5422         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5423         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5424         SendToProgram(buf, cps);
5425         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5426     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5427         SendToProgram("go\n", cps);
5428         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5429     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5430         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5431             SendToProgram("go\n", cps); 
5432         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5433     }
5434     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5435 }
5436
5437 char *savedMessage;
5438 ChessProgramState *savedState;
5439 void DeferredBookMove(void)
5440 {
5441         if(savedState->lastPing != savedState->lastPong)
5442                     ScheduleDelayedEvent(DeferredBookMove, 10);
5443         else
5444         HandleMachineMove(savedMessage, savedState);
5445 }
5446
5447 void
5448 HandleMachineMove(message, cps)
5449      char *message;
5450      ChessProgramState *cps;
5451 {
5452     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5453     char realname[MSG_SIZ];
5454     int fromX, fromY, toX, toY;
5455     ChessMove moveType;
5456     char promoChar;
5457     char *p;
5458     int machineWhite;
5459     char *bookHit;
5460
5461 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5462     /*
5463      * Kludge to ignore BEL characters
5464      */
5465     while (*message == '\007') message++;
5466
5467     /*
5468      * [HGM] engine debug message: ignore lines starting with '#' character
5469      */
5470     if(cps->debug && *message == '#') return;
5471
5472     /*
5473      * Look for book output
5474      */
5475     if (cps == &first && bookRequested) {
5476         if (message[0] == '\t' || message[0] == ' ') {
5477             /* Part of the book output is here; append it */
5478             strcat(bookOutput, message);
5479             strcat(bookOutput, "  \n");
5480             return;
5481         } else if (bookOutput[0] != NULLCHAR) {
5482             /* All of book output has arrived; display it */
5483             char *p = bookOutput;
5484             while (*p != NULLCHAR) {
5485                 if (*p == '\t') *p = ' ';
5486                 p++;
5487             }
5488             DisplayInformation(bookOutput);
5489             bookRequested = FALSE;
5490             /* Fall through to parse the current output */
5491         }
5492     }
5493
5494     /*
5495      * Look for machine move.
5496      */
5497     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5498         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5499     {
5500         /* This method is only useful on engines that support ping */
5501         if (cps->lastPing != cps->lastPong) {
5502           if (gameMode == BeginningOfGame) {
5503             /* Extra move from before last new; ignore */
5504             if (appData.debugMode) {
5505                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5506             }
5507           } else {
5508             if (appData.debugMode) {
5509                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5510                         cps->which, gameMode);
5511             }
5512
5513             SendToProgram("undo\n", cps);
5514           }
5515           return;
5516         }
5517
5518         switch (gameMode) {
5519           case BeginningOfGame:
5520             /* Extra move from before last reset; ignore */
5521             if (appData.debugMode) {
5522                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5523             }
5524             return;
5525
5526           case EndOfGame:
5527           case IcsIdle:
5528           default:
5529             /* Extra move after we tried to stop.  The mode test is
5530                not a reliable way of detecting this problem, but it's
5531                the best we can do on engines that don't support ping.
5532             */
5533             if (appData.debugMode) {
5534                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5535                         cps->which, gameMode);
5536             }
5537             SendToProgram("undo\n", cps);
5538             return;
5539
5540           case MachinePlaysWhite:
5541           case IcsPlayingWhite:
5542             machineWhite = TRUE;
5543             break;
5544
5545           case MachinePlaysBlack:
5546           case IcsPlayingBlack:
5547             machineWhite = FALSE;
5548             break;
5549
5550           case TwoMachinesPlay:
5551             machineWhite = (cps->twoMachinesColor[0] == 'w');
5552             break;
5553         }
5554         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5555             if (appData.debugMode) {
5556                 fprintf(debugFP,
5557                         "Ignoring move out of turn by %s, gameMode %d"
5558                         ", forwardMost %d\n",
5559                         cps->which, gameMode, forwardMostMove);
5560             }
5561             return;
5562         }
5563
5564     if (appData.debugMode) { int f = forwardMostMove;
5565         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5566                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5567     }
5568         if(cps->alphaRank) AlphaRank(machineMove, 4);
5569         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5570                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5571             /* Machine move could not be parsed; ignore it. */
5572             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5573                     machineMove, cps->which);
5574             DisplayError(buf1, 0);
5575             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5576                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5577             if (gameMode == TwoMachinesPlay) {
5578               GameEnds(machineWhite ? BlackWins : WhiteWins,
5579                        buf1, GE_XBOARD);
5580             }
5581             return;
5582         }
5583
5584         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5585         /* So we have to redo legality test with true e.p. status here,  */
5586         /* to make sure an illegal e.p. capture does not slip through,   */
5587         /* to cause a forfeit on a justified illegal-move complaint      */
5588         /* of the opponent.                                              */
5589         if( gameMode==TwoMachinesPlay && appData.testLegality
5590             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5591                                                               ) {
5592            ChessMove moveType;
5593            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5594                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5595                              fromY, fromX, toY, toX, promoChar);
5596             if (appData.debugMode) {
5597                 int i;
5598                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5599                     castlingRights[forwardMostMove][i], castlingRank[i]);
5600                 fprintf(debugFP, "castling rights\n");
5601             }
5602             if(moveType == IllegalMove) {
5603                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5604                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5605                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5606                            buf1, GE_XBOARD);
5607                 return;
5608            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5609            /* [HGM] Kludge to handle engines that send FRC-style castling
5610               when they shouldn't (like TSCP-Gothic) */
5611            switch(moveType) {
5612              case WhiteASideCastleFR:
5613              case BlackASideCastleFR:
5614                toX+=2;
5615                currentMoveString[2]++;
5616                break;
5617              case WhiteHSideCastleFR:
5618              case BlackHSideCastleFR:
5619                toX--;
5620                currentMoveString[2]--;
5621                break;
5622              default: ; // nothing to do, but suppresses warning of pedantic compilers
5623            }
5624         }
5625         hintRequested = FALSE;
5626         lastHint[0] = NULLCHAR;
5627         bookRequested = FALSE;
5628         /* Program may be pondering now */
5629         cps->maybeThinking = TRUE;
5630         if (cps->sendTime == 2) cps->sendTime = 1;
5631         if (cps->offeredDraw) cps->offeredDraw--;
5632
5633 #if ZIPPY
5634         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5635             first.initDone) {
5636           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5637           ics_user_moved = 1;
5638           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5639                 char buf[3*MSG_SIZ];
5640
5641                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5642                         programStats.score / 100.,
5643                         programStats.depth,
5644                         programStats.time / 100.,
5645                         (unsigned int)programStats.nodes,
5646                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5647                         programStats.movelist);
5648                 SendToICS(buf);
5649 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5650           }
5651         }
5652 #endif
5653         /* currentMoveString is set as a side-effect of ParseOneMove */
5654         strcpy(machineMove, currentMoveString);
5655         strcat(machineMove, "\n");
5656         strcpy(moveList[forwardMostMove], machineMove);
5657
5658         /* [AS] Save move info and clear stats for next move */
5659         pvInfoList[ forwardMostMove ].score = programStats.score;
5660         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5661         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5662         ClearProgramStats();
5663         thinkOutput[0] = NULLCHAR;
5664         hiddenThinkOutputState = 0;
5665
5666         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5667
5668         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5669         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5670             int count = 0;
5671
5672             while( count < adjudicateLossPlies ) {
5673                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5674
5675                 if( count & 1 ) {
5676                     score = -score; /* Flip score for winning side */
5677                 }
5678
5679                 if( score > adjudicateLossThreshold ) {
5680                     break;
5681                 }
5682
5683                 count++;
5684             }
5685
5686             if( count >= adjudicateLossPlies ) {
5687                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5688
5689                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5690                     "Xboard adjudication", 
5691                     GE_XBOARD );
5692
5693                 return;
5694             }
5695         }
5696
5697         if( gameMode == TwoMachinesPlay ) {
5698           // [HGM] some adjudications useful with buggy engines
5699             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5700           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5701
5702
5703             if( appData.testLegality )
5704             {   /* [HGM] Some more adjudications for obstinate engines */
5705                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5706                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5707                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5708                 static int moveCount = 6;
5709                 ChessMove result;
5710                 char *reason = NULL;
5711
5712                 /* Count what is on board. */
5713                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5714                 {   ChessSquare p = boards[forwardMostMove][i][j];
5715                     int m=i;
5716
5717                     switch((int) p)
5718                     {   /* count B,N,R and other of each side */
5719                         case WhiteKing:
5720                         case BlackKing:
5721                              NrK++; break; // [HGM] atomic: count Kings
5722                         case WhiteKnight:
5723                              NrWN++; break;
5724                         case WhiteBishop:
5725                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5726                              bishopsColor |= 1 << ((i^j)&1);
5727                              NrWB++; break;
5728                         case BlackKnight:
5729                              NrBN++; break;
5730                         case BlackBishop:
5731                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5732                              bishopsColor |= 1 << ((i^j)&1);
5733                              NrBB++; break;
5734                         case WhiteRook:
5735                              NrWR++; break;
5736                         case BlackRook:
5737                              NrBR++; break;
5738                         case WhiteQueen:
5739                              NrWQ++; break;
5740                         case BlackQueen:
5741                              NrBQ++; break;
5742                         case EmptySquare: 
5743                              break;
5744                         case BlackPawn:
5745                              m = 7-i;
5746                         case WhitePawn:
5747                              PawnAdvance += m; NrPawns++;
5748                     }
5749                     NrPieces += (p != EmptySquare);
5750                     NrW += ((int)p < (int)BlackPawn);
5751                     if(gameInfo.variant == VariantXiangqi && 
5752                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5753                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5754                         NrW -= ((int)p < (int)BlackPawn);
5755                     }
5756                 }
5757
5758                 /* Some material-based adjudications that have to be made before stalemate test */
5759                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5760                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5761                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5762                      if(appData.checkMates) {
5763                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5764                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5765                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5766                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5767                          return;
5768                      }
5769                 }
5770
5771                 /* Bare King in Shatranj (loses) or Losers (wins) */
5772                 if( NrW == 1 || NrPieces - NrW == 1) {
5773                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5774                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5775                      if(appData.checkMates) {
5776                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5777                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5778                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5779                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5780                          return;
5781                      }
5782                   } else
5783                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5784                   {    /* bare King */
5785                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5786                         if(appData.checkMates) {
5787                             /* but only adjudicate if adjudication enabled */
5788                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5789                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5790                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5791                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5792                             return;
5793                         }
5794                   }
5795                 } else bare = 1;
5796
5797
5798             // don't wait for engine to announce game end if we can judge ourselves
5799             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5800                                        castlingRights[forwardMostMove]) ) {
5801               case MT_CHECK:
5802                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5803                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5804                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5805                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5806                             checkCnt++;
5807                         if(checkCnt >= 2) {
5808                             reason = "Xboard adjudication: 3rd check";
5809                             epStatus[forwardMostMove] = EP_CHECKMATE;
5810                             break;
5811                         }
5812                     }
5813                 }
5814               case MT_NONE:
5815               default:
5816                 break;
5817               case MT_STALEMATE:
5818               case MT_STAINMATE:
5819                 reason = "Xboard adjudication: Stalemate";
5820                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5821                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5822                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5823                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5824                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5825                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5826                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5827                                                                         EP_CHECKMATE : EP_WINS);
5828                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5829                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5830                 }
5831                 break;
5832               case MT_CHECKMATE:
5833                 reason = "Xboard adjudication: Checkmate";
5834                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5835                 break;
5836             }
5837
5838                 switch(i = epStatus[forwardMostMove]) {
5839                     case EP_STALEMATE:
5840                         result = GameIsDrawn; break;
5841                     case EP_CHECKMATE:
5842                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5843                     case EP_WINS:
5844                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5845                     default:
5846                         result = (ChessMove) 0;
5847                 }
5848                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5849                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5850                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5851                     GameEnds( result, reason, GE_XBOARD );
5852                     return;
5853                 }
5854
5855                 /* Next absolutely insufficient mating material. */
5856                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5857                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5858                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5859                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5860                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5861
5862                      /* always flag draws, for judging claims */
5863                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5864
5865                      if(appData.materialDraws) {
5866                          /* but only adjudicate them if adjudication enabled */
5867                          SendToProgram("force\n", cps->other); // suppress reply
5868                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5869                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5870                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5871                          return;
5872                      }
5873                 }
5874
5875                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5876                 if(NrPieces == 4 && 
5877                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5878                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5879                    || NrWN==2 || NrBN==2     /* KNNK */
5880                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5881                   ) ) {
5882                      if(--moveCount < 0 && appData.trivialDraws)
5883                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5884                           SendToProgram("force\n", cps->other); // suppress reply
5885                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5886                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5887                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5888                           return;
5889                      }
5890                 } else moveCount = 6;
5891             }
5892           }
5893           
5894           if (appData.debugMode) { int i;
5895             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5896                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5897                     appData.drawRepeats);
5898             for( i=forwardMostMove; i>=backwardMostMove; i-- )
5899               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5900             
5901           }
5902
5903                 /* Check for rep-draws */
5904                 count = 0;
5905                 for(k = forwardMostMove-2;
5906                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5907                         epStatus[k] < EP_UNKNOWN &&
5908                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5909                     k-=2)
5910                 {   int rights=0;
5911                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5912                         /* compare castling rights */
5913                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5914                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5915                                 rights++; /* King lost rights, while rook still had them */
5916                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5917                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5918                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5919                                    rights++; /* but at least one rook lost them */
5920                         }
5921                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5922                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5923                                 rights++; 
5924                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5925                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5926                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5927                                    rights++;
5928                         }
5929                         if( rights == 0 && ++count > appData.drawRepeats-2
5930                             && appData.drawRepeats > 1) {
5931                              /* adjudicate after user-specified nr of repeats */
5932                              SendToProgram("force\n", cps->other); // suppress reply
5933                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5934                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5935                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5936                                 // [HGM] xiangqi: check for forbidden perpetuals
5937                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5938                                 for(m=forwardMostMove; m>k; m-=2) {
5939                                     if(MateTest(boards[m], PosFlags(m), 
5940                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5941                                         ourPerpetual = 0; // the current mover did not always check
5942                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5943                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5944                                         hisPerpetual = 0; // the opponent did not always check
5945                                 }
5946                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5947                                                                         ourPerpetual, hisPerpetual);
5948                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5949                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5950                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5951                                     return;
5952                                 }
5953                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5954                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5955                                 // Now check for perpetual chases
5956                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5957                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5958                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5959                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5960                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5961                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5962                                         return;
5963                                     }
5964                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5965                                         break; // Abort repetition-checking loop.
5966                                 }
5967                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5968                              }
5969                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5970                              return;
5971                         }
5972                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5973                              epStatus[forwardMostMove] = EP_REP_DRAW;
5974                     }
5975                 }
5976
5977                 /* Now we test for 50-move draws. Determine ply count */
5978                 count = forwardMostMove;
5979                 /* look for last irreversble move */
5980                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5981                     count--;
5982                 /* if we hit starting position, add initial plies */
5983                 if( count == backwardMostMove )
5984                     count -= initialRulePlies;
5985                 count = forwardMostMove - count; 
5986                 if( count >= 100)
5987                          epStatus[forwardMostMove] = EP_RULE_DRAW;
5988                          /* this is used to judge if draw claims are legal */
5989                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
5990                          SendToProgram("force\n", cps->other); // suppress reply
5991                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5992                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5993                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
5994                          return;
5995                 }
5996
5997                 /* if draw offer is pending, treat it as a draw claim
5998                  * when draw condition present, to allow engines a way to
5999                  * claim draws before making their move to avoid a race
6000                  * condition occurring after their move
6001                  */
6002                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6003                          char *p = NULL;
6004                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6005                              p = "Draw claim: 50-move rule";
6006                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6007                              p = "Draw claim: 3-fold repetition";
6008                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6009                              p = "Draw claim: insufficient mating material";
6010                          if( p != NULL ) {
6011                              SendToProgram("force\n", cps->other); // suppress reply
6012                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6013                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6014                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6015                              return;
6016                          }
6017                 }
6018
6019
6020                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6021                     SendToProgram("force\n", cps->other); // suppress reply
6022                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6023                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6024
6025                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6026
6027                     return;
6028                 }
6029         }
6030
6031         bookHit = NULL;
6032         if (gameMode == TwoMachinesPlay) {
6033             /* [HGM] relaying draw offers moved to after reception of move */
6034             /* and interpreting offer as claim if it brings draw condition */
6035             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6036                 SendToProgram("draw\n", cps->other);
6037             }
6038             if (cps->other->sendTime) {
6039                 SendTimeRemaining(cps->other,
6040                                   cps->other->twoMachinesColor[0] == 'w');
6041             }
6042             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6043             if (firstMove && !bookHit) {
6044                 firstMove = FALSE;
6045                 if (cps->other->useColors) {
6046                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6047                 }
6048                 SendToProgram("go\n", cps->other);
6049             }
6050             cps->other->maybeThinking = TRUE;
6051         }
6052
6053         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6054         
6055         if (!pausing && appData.ringBellAfterMoves) {
6056             RingBell();
6057         }
6058
6059         /* 
6060          * Reenable menu items that were disabled while
6061          * machine was thinking
6062          */
6063         if (gameMode != TwoMachinesPlay)
6064             SetUserThinkingEnables();
6065
6066         // [HGM] book: after book hit opponent has received move and is now in force mode
6067         // force the book reply into it, and then fake that it outputted this move by jumping
6068         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6069         if(bookHit) {
6070                 static char bookMove[MSG_SIZ]; // a bit generous?
6071
6072                 strcpy(bookMove, "move ");
6073                 strcat(bookMove, bookHit);
6074                 message = bookMove;
6075                 cps = cps->other;
6076                 programStats.nodes = programStats.depth = programStats.time = 
6077                 programStats.score = programStats.got_only_move = 0;
6078                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6079
6080                 if(cps->lastPing != cps->lastPong) {
6081                     savedMessage = message; // args for deferred call
6082                     savedState = cps;
6083                     ScheduleDelayedEvent(DeferredBookMove, 10);
6084                     return;
6085                 }
6086                 goto FakeBookMove;
6087         }
6088
6089         return;
6090     }
6091
6092     /* Set special modes for chess engines.  Later something general
6093      *  could be added here; for now there is just one kludge feature,
6094      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6095      *  when "xboard" is given as an interactive command.
6096      */
6097     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6098         cps->useSigint = FALSE;
6099         cps->useSigterm = FALSE;
6100     }
6101     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6102       ParseFeatures(message+8, cps);
6103       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6104     }
6105
6106     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6107      * want this, I was asked to put it in, and obliged.
6108      */
6109     if (!strncmp(message, "setboard ", 9)) {
6110         Board initial_position; int i;
6111
6112         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6113
6114         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6115             DisplayError(_("Bad FEN received from engine"), 0);
6116             return ;
6117         } else {
6118            Reset(FALSE, FALSE);
6119            CopyBoard(boards[0], initial_position);
6120            initialRulePlies = FENrulePlies;
6121            epStatus[0] = FENepStatus;
6122            for( i=0; i<nrCastlingRights; i++ )
6123                 castlingRights[0][i] = FENcastlingRights[i];
6124            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6125            else gameMode = MachinePlaysBlack;                 
6126            DrawPosition(FALSE, boards[currentMove]);
6127         }
6128         return;
6129     }
6130
6131     /*
6132      * Look for communication commands
6133      */
6134     if (!strncmp(message, "telluser ", 9)) {
6135         DisplayNote(message + 9);
6136         return;
6137     }
6138     if (!strncmp(message, "tellusererror ", 14)) {
6139         DisplayError(message + 14, 0);
6140         return;
6141     }
6142     if (!strncmp(message, "tellopponent ", 13)) {
6143       if (appData.icsActive) {
6144         if (loggedOn) {
6145           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6146           SendToICS(buf1);
6147         }
6148       } else {
6149         DisplayNote(message + 13);
6150       }
6151       return;
6152     }
6153     if (!strncmp(message, "tellothers ", 11)) {
6154       if (appData.icsActive) {
6155         if (loggedOn) {
6156           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6157           SendToICS(buf1);
6158         }
6159       }
6160       return;
6161     }
6162     if (!strncmp(message, "tellall ", 8)) {
6163       if (appData.icsActive) {
6164         if (loggedOn) {
6165           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6166           SendToICS(buf1);
6167         }
6168       } else {
6169         DisplayNote(message + 8);
6170       }
6171       return;
6172     }
6173     if (strncmp(message, "warning", 7) == 0) {
6174         /* Undocumented feature, use tellusererror in new code */
6175         DisplayError(message, 0);
6176         return;
6177     }
6178     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6179         strcpy(realname, cps->tidy);
6180         strcat(realname, " query");
6181         AskQuestion(realname, buf2, buf1, cps->pr);
6182         return;
6183     }
6184     /* Commands from the engine directly to ICS.  We don't allow these to be 
6185      *  sent until we are logged on. Crafty kibitzes have been known to 
6186      *  interfere with the login process.
6187      */
6188     if (loggedOn) {
6189         if (!strncmp(message, "tellics ", 8)) {
6190             SendToICS(message + 8);
6191             SendToICS("\n");
6192             return;
6193         }
6194         if (!strncmp(message, "tellicsnoalias ", 15)) {
6195             SendToICS(ics_prefix);
6196             SendToICS(message + 15);
6197             SendToICS("\n");
6198             return;
6199         }
6200         /* The following are for backward compatibility only */
6201         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6202             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6203             SendToICS(ics_prefix);
6204             SendToICS(message);
6205             SendToICS("\n");
6206             return;
6207         }
6208     }
6209     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6210         return;
6211     }
6212     /*
6213      * If the move is illegal, cancel it and redraw the board.
6214      * Also deal with other error cases.  Matching is rather loose
6215      * here to accommodate engines written before the spec.
6216      */
6217     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6218         strncmp(message, "Error", 5) == 0) {
6219         if (StrStr(message, "name") || 
6220             StrStr(message, "rating") || StrStr(message, "?") ||
6221             StrStr(message, "result") || StrStr(message, "board") ||
6222             StrStr(message, "bk") || StrStr(message, "computer") ||
6223             StrStr(message, "variant") || StrStr(message, "hint") ||
6224             StrStr(message, "random") || StrStr(message, "depth") ||
6225             StrStr(message, "accepted")) {
6226             return;
6227         }
6228         if (StrStr(message, "protover")) {
6229           /* Program is responding to input, so it's apparently done
6230              initializing, and this error message indicates it is
6231              protocol version 1.  So we don't need to wait any longer
6232              for it to initialize and send feature commands. */
6233           FeatureDone(cps, 1);
6234           cps->protocolVersion = 1;
6235           return;
6236         }
6237         cps->maybeThinking = FALSE;
6238
6239         if (StrStr(message, "draw")) {
6240             /* Program doesn't have "draw" command */
6241             cps->sendDrawOffers = 0;
6242             return;
6243         }
6244         if (cps->sendTime != 1 &&
6245             (StrStr(message, "time") || StrStr(message, "otim"))) {
6246           /* Program apparently doesn't have "time" or "otim" command */
6247           cps->sendTime = 0;
6248           return;
6249         }
6250         if (StrStr(message, "analyze")) {
6251             cps->analysisSupport = FALSE;
6252             cps->analyzing = FALSE;
6253             Reset(FALSE, TRUE);
6254             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6255             DisplayError(buf2, 0);
6256             return;
6257         }
6258         if (StrStr(message, "(no matching move)st")) {
6259           /* Special kludge for GNU Chess 4 only */
6260           cps->stKludge = TRUE;
6261           SendTimeControl(cps, movesPerSession, timeControl,
6262                           timeIncrement, appData.searchDepth,
6263                           searchTime);
6264           return;
6265         }
6266         if (StrStr(message, "(no matching move)sd")) {
6267           /* Special kludge for GNU Chess 4 only */
6268           cps->sdKludge = TRUE;
6269           SendTimeControl(cps, movesPerSession, timeControl,
6270                           timeIncrement, appData.searchDepth,
6271                           searchTime);
6272           return;
6273         }
6274         if (!StrStr(message, "llegal")) {
6275             return;
6276         }
6277         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6278             gameMode == IcsIdle) return;
6279         if (forwardMostMove <= backwardMostMove) return;
6280         if (pausing) PauseEvent();
6281       if(appData.forceIllegal) {
6282             // [HGM] illegal: machine refused move; force position after move into it
6283           SendToProgram("force\n", cps);
6284           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6285                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6286                 // when black is to move, while there might be nothing on a2 or black
6287                 // might already have the move. So send the board as if white has the move.
6288                 // But first we must change the stm of the engine, as it refused the last move
6289                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6290                 if(WhiteOnMove(forwardMostMove)) {
6291                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6292                     SendBoard(cps, forwardMostMove); // kludgeless board
6293                 } else {
6294                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6295                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6296                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6297                 }
6298           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6299             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6300                  gameMode == TwoMachinesPlay)
6301               SendToProgram("go\n", cps);
6302             return;
6303       } else
6304         if (gameMode == PlayFromGameFile) {
6305             /* Stop reading this game file */
6306             gameMode = EditGame;
6307             ModeHighlight();
6308         }
6309         currentMove = --forwardMostMove;
6310         DisplayMove(currentMove-1); /* before DisplayMoveError */
6311         SwitchClocks();
6312         DisplayBothClocks();
6313         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6314                 parseList[currentMove], cps->which);
6315         DisplayMoveError(buf1);
6316         DrawPosition(FALSE, boards[currentMove]);
6317
6318         /* [HGM] illegal-move claim should forfeit game when Xboard */
6319         /* only passes fully legal moves                            */
6320         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6321             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6322                                 "False illegal-move claim", GE_XBOARD );
6323         }
6324         return;
6325     }
6326     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6327         /* Program has a broken "time" command that
6328            outputs a string not ending in newline.
6329            Don't use it. */
6330         cps->sendTime = 0;
6331     }
6332     
6333     /*
6334      * If chess program startup fails, exit with an error message.
6335      * Attempts to recover here are futile.
6336      */
6337     if ((StrStr(message, "unknown host") != NULL)
6338         || (StrStr(message, "No remote directory") != NULL)
6339         || (StrStr(message, "not found") != NULL)
6340         || (StrStr(message, "No such file") != NULL)
6341         || (StrStr(message, "can't alloc") != NULL)
6342         || (StrStr(message, "Permission denied") != NULL)) {
6343
6344         cps->maybeThinking = FALSE;
6345         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6346                 cps->which, cps->program, cps->host, message);
6347         RemoveInputSource(cps->isr);
6348         DisplayFatalError(buf1, 0, 1);
6349         return;
6350     }
6351     
6352     /* 
6353      * Look for hint output
6354      */
6355     if (sscanf(message, "Hint: %s", buf1) == 1) {
6356         if (cps == &first && hintRequested) {
6357             hintRequested = FALSE;
6358             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6359                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6360                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6361                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6362                                     fromY, fromX, toY, toX, promoChar, buf1);
6363                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6364                 DisplayInformation(buf2);
6365             } else {
6366                 /* Hint move could not be parsed!? */
6367               snprintf(buf2, sizeof(buf2),
6368                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6369                         buf1, cps->which);
6370                 DisplayError(buf2, 0);
6371             }
6372         } else {
6373             strcpy(lastHint, buf1);
6374         }
6375         return;
6376     }
6377
6378     /*
6379      * Ignore other messages if game is not in progress
6380      */
6381     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6382         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6383
6384     /*
6385      * look for win, lose, draw, or draw offer
6386      */
6387     if (strncmp(message, "1-0", 3) == 0) {
6388         char *p, *q, *r = "";
6389         p = strchr(message, '{');
6390         if (p) {
6391             q = strchr(p, '}');
6392             if (q) {
6393                 *q = NULLCHAR;
6394                 r = p + 1;
6395             }
6396         }
6397         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6398         return;
6399     } else if (strncmp(message, "0-1", 3) == 0) {
6400         char *p, *q, *r = "";
6401         p = strchr(message, '{');
6402         if (p) {
6403             q = strchr(p, '}');
6404             if (q) {
6405                 *q = NULLCHAR;
6406                 r = p + 1;
6407             }
6408         }
6409         /* Kludge for Arasan 4.1 bug */
6410         if (strcmp(r, "Black resigns") == 0) {
6411             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6412             return;
6413         }
6414         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6415         return;
6416     } else if (strncmp(message, "1/2", 3) == 0) {
6417         char *p, *q, *r = "";
6418         p = strchr(message, '{');
6419         if (p) {
6420             q = strchr(p, '}');
6421             if (q) {
6422                 *q = NULLCHAR;
6423                 r = p + 1;
6424             }
6425         }
6426             
6427         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6428         return;
6429
6430     } else if (strncmp(message, "White resign", 12) == 0) {
6431         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6432         return;
6433     } else if (strncmp(message, "Black resign", 12) == 0) {
6434         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6435         return;
6436     } else if (strncmp(message, "White matches", 13) == 0 ||
6437                strncmp(message, "Black matches", 13) == 0   ) {
6438         /* [HGM] ignore GNUShogi noises */
6439         return;
6440     } else if (strncmp(message, "White", 5) == 0 &&
6441                message[5] != '(' &&
6442                StrStr(message, "Black") == NULL) {
6443         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6444         return;
6445     } else if (strncmp(message, "Black", 5) == 0 &&
6446                message[5] != '(') {
6447         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6448         return;
6449     } else if (strcmp(message, "resign") == 0 ||
6450                strcmp(message, "computer resigns") == 0) {
6451         switch (gameMode) {
6452           case MachinePlaysBlack:
6453           case IcsPlayingBlack:
6454             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6455             break;
6456           case MachinePlaysWhite:
6457           case IcsPlayingWhite:
6458             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6459             break;
6460           case TwoMachinesPlay:
6461             if (cps->twoMachinesColor[0] == 'w')
6462               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6463             else
6464               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6465             break;
6466           default:
6467             /* can't happen */
6468             break;
6469         }
6470         return;
6471     } else if (strncmp(message, "opponent mates", 14) == 0) {
6472         switch (gameMode) {
6473           case MachinePlaysBlack:
6474           case IcsPlayingBlack:
6475             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6476             break;
6477           case MachinePlaysWhite:
6478           case IcsPlayingWhite:
6479             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6480             break;
6481           case TwoMachinesPlay:
6482             if (cps->twoMachinesColor[0] == 'w')
6483               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6484             else
6485               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6486             break;
6487           default:
6488             /* can't happen */
6489             break;
6490         }
6491         return;
6492     } else if (strncmp(message, "computer mates", 14) == 0) {
6493         switch (gameMode) {
6494           case MachinePlaysBlack:
6495           case IcsPlayingBlack:
6496             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6497             break;
6498           case MachinePlaysWhite:
6499           case IcsPlayingWhite:
6500             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6501             break;
6502           case TwoMachinesPlay:
6503             if (cps->twoMachinesColor[0] == 'w')
6504               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6505             else
6506               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6507             break;
6508           default:
6509             /* can't happen */
6510             break;
6511         }
6512         return;
6513     } else if (strncmp(message, "checkmate", 9) == 0) {
6514         if (WhiteOnMove(forwardMostMove)) {
6515             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6516         } else {
6517             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6518         }
6519         return;
6520     } else if (strstr(message, "Draw") != NULL ||
6521                strstr(message, "game is a draw") != NULL) {
6522         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6523         return;
6524     } else if (strstr(message, "offer") != NULL &&
6525                strstr(message, "draw") != NULL) {
6526 #if ZIPPY
6527         if (appData.zippyPlay && first.initDone) {
6528             /* Relay offer to ICS */
6529             SendToICS(ics_prefix);
6530             SendToICS("draw\n");
6531         }
6532 #endif
6533         cps->offeredDraw = 2; /* valid until this engine moves twice */
6534         if (gameMode == TwoMachinesPlay) {
6535             if (cps->other->offeredDraw) {
6536                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6537             /* [HGM] in two-machine mode we delay relaying draw offer      */
6538             /* until after we also have move, to see if it is really claim */
6539             }
6540         } else if (gameMode == MachinePlaysWhite ||
6541                    gameMode == MachinePlaysBlack) {
6542           if (userOfferedDraw) {
6543             DisplayInformation(_("Machine accepts your draw offer"));
6544             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6545           } else {
6546             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6547           }
6548         }
6549     }
6550
6551     
6552     /*
6553      * Look for thinking output
6554      */
6555     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6556           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6557                                 ) {
6558         int plylev, mvleft, mvtot, curscore, time;
6559         char mvname[MOVE_LEN];
6560         u64 nodes; // [DM]
6561         char plyext;
6562         int ignore = FALSE;
6563         int prefixHint = FALSE;
6564         mvname[0] = NULLCHAR;
6565
6566         switch (gameMode) {
6567           case MachinePlaysBlack:
6568           case IcsPlayingBlack:
6569             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6570             break;
6571           case MachinePlaysWhite:
6572           case IcsPlayingWhite:
6573             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6574             break;
6575           case AnalyzeMode:
6576           case AnalyzeFile:
6577             break;
6578           case IcsObserving: /* [DM] icsEngineAnalyze */
6579             if (!appData.icsEngineAnalyze) ignore = TRUE;
6580             break;
6581           case TwoMachinesPlay:
6582             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6583                 ignore = TRUE;
6584             }
6585             break;
6586           default:
6587             ignore = TRUE;
6588             break;
6589         }
6590
6591         if (!ignore) {
6592             buf1[0] = NULLCHAR;
6593             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6594                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6595
6596                 if (plyext != ' ' && plyext != '\t') {
6597                     time *= 100;
6598                 }
6599
6600                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6601                 if( cps->scoreIsAbsolute && 
6602                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6603                 {
6604                     curscore = -curscore;
6605                 }
6606
6607
6608                 programStats.depth = plylev;
6609                 programStats.nodes = nodes;
6610                 programStats.time = time;
6611                 programStats.score = curscore;
6612                 programStats.got_only_move = 0;
6613
6614                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6615                         int ticklen;
6616
6617                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6618                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6619                         if(WhiteOnMove(forwardMostMove)) 
6620                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6621                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6622                 }
6623
6624                 /* Buffer overflow protection */
6625                 if (buf1[0] != NULLCHAR) {
6626                     if (strlen(buf1) >= sizeof(programStats.movelist)
6627                         && appData.debugMode) {
6628                         fprintf(debugFP,
6629                                 "PV is too long; using the first %d bytes.\n",
6630                                 sizeof(programStats.movelist) - 1);
6631                     }
6632
6633                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6634                 } else {
6635                     sprintf(programStats.movelist, " no PV\n");
6636                 }
6637
6638                 if (programStats.seen_stat) {
6639                     programStats.ok_to_send = 1;
6640                 }
6641
6642                 if (strchr(programStats.movelist, '(') != NULL) {
6643                     programStats.line_is_book = 1;
6644                     programStats.nr_moves = 0;
6645                     programStats.moves_left = 0;
6646                 } else {
6647                     programStats.line_is_book = 0;
6648                 }
6649
6650                 SendProgramStatsToFrontend( cps, &programStats );
6651
6652                 /* 
6653                     [AS] Protect the thinkOutput buffer from overflow... this
6654                     is only useful if buf1 hasn't overflowed first!
6655                 */
6656                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6657                         plylev, 
6658                         (gameMode == TwoMachinesPlay ?
6659                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6660                         ((double) curscore) / 100.0,
6661                         prefixHint ? lastHint : "",
6662                         prefixHint ? " " : "" );
6663
6664                 if( buf1[0] != NULLCHAR ) {
6665                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6666
6667                     if( strlen(buf1) > max_len ) {
6668                         if( appData.debugMode) {
6669                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6670                         }
6671                         buf1[max_len+1] = '\0';
6672                     }
6673
6674                     strcat( thinkOutput, buf1 );
6675                 }
6676
6677                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6678                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6679                     DisplayMove(currentMove - 1);
6680                     DisplayAnalysis();
6681                 }
6682                 return;
6683
6684             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6685                 /* crafty (9.25+) says "(only move) <move>"
6686                  * if there is only 1 legal move
6687                  */
6688                 sscanf(p, "(only move) %s", buf1);
6689                 sprintf(thinkOutput, "%s (only move)", buf1);
6690                 sprintf(programStats.movelist, "%s (only move)", buf1);
6691                 programStats.depth = 1;
6692                 programStats.nr_moves = 1;
6693                 programStats.moves_left = 1;
6694                 programStats.nodes = 1;
6695                 programStats.time = 1;
6696                 programStats.got_only_move = 1;
6697
6698                 /* Not really, but we also use this member to
6699                    mean "line isn't going to change" (Crafty
6700                    isn't searching, so stats won't change) */
6701                 programStats.line_is_book = 1;
6702
6703                 SendProgramStatsToFrontend( cps, &programStats );
6704                 
6705                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6706                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6707                     DisplayMove(currentMove - 1);
6708                     DisplayAnalysis();
6709                 }
6710                 return;
6711             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6712                               &time, &nodes, &plylev, &mvleft,
6713                               &mvtot, mvname) >= 5) {
6714                 /* The stat01: line is from Crafty (9.29+) in response
6715                    to the "." command */
6716                 programStats.seen_stat = 1;
6717                 cps->maybeThinking = TRUE;
6718
6719                 if (programStats.got_only_move || !appData.periodicUpdates)
6720                   return;
6721
6722                 programStats.depth = plylev;
6723                 programStats.time = time;
6724                 programStats.nodes = nodes;
6725                 programStats.moves_left = mvleft;
6726                 programStats.nr_moves = mvtot;
6727                 strcpy(programStats.move_name, mvname);
6728                 programStats.ok_to_send = 1;
6729                 programStats.movelist[0] = '\0';
6730
6731                 SendProgramStatsToFrontend( cps, &programStats );
6732
6733                 DisplayAnalysis();
6734                 return;
6735
6736             } else if (strncmp(message,"++",2) == 0) {
6737                 /* Crafty 9.29+ outputs this */
6738                 programStats.got_fail = 2;
6739                 return;
6740
6741             } else if (strncmp(message,"--",2) == 0) {
6742                 /* Crafty 9.29+ outputs this */
6743                 programStats.got_fail = 1;
6744                 return;
6745
6746             } else if (thinkOutput[0] != NULLCHAR &&
6747                        strncmp(message, "    ", 4) == 0) {
6748                 unsigned message_len;
6749
6750                 p = message;
6751                 while (*p && *p == ' ') p++;
6752
6753                 message_len = strlen( p );
6754
6755                 /* [AS] Avoid buffer overflow */
6756                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6757                     strcat(thinkOutput, " ");
6758                     strcat(thinkOutput, p);
6759                 }
6760
6761                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6762                     strcat(programStats.movelist, " ");
6763                     strcat(programStats.movelist, p);
6764                 }
6765
6766                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6767                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6768                     DisplayMove(currentMove - 1);
6769                     DisplayAnalysis();
6770                 }
6771                 return;
6772             }
6773         }
6774         else {
6775             buf1[0] = NULLCHAR;
6776
6777             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6778                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6779             {
6780                 ChessProgramStats cpstats;
6781
6782                 if (plyext != ' ' && plyext != '\t') {
6783                     time *= 100;
6784                 }
6785
6786                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6787                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6788                     curscore = -curscore;
6789                 }
6790
6791                 cpstats.depth = plylev;
6792                 cpstats.nodes = nodes;
6793                 cpstats.time = time;
6794                 cpstats.score = curscore;
6795                 cpstats.got_only_move = 0;
6796                 cpstats.movelist[0] = '\0';
6797
6798                 if (buf1[0] != NULLCHAR) {
6799                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6800                 }
6801
6802                 cpstats.ok_to_send = 0;
6803                 cpstats.line_is_book = 0;
6804                 cpstats.nr_moves = 0;
6805                 cpstats.moves_left = 0;
6806
6807                 SendProgramStatsToFrontend( cps, &cpstats );
6808             }
6809         }
6810     }
6811 }
6812
6813
6814 /* Parse a game score from the character string "game", and
6815    record it as the history of the current game.  The game
6816    score is NOT assumed to start from the standard position. 
6817    The display is not updated in any way.
6818    */
6819 void
6820 ParseGameHistory(game)
6821      char *game;
6822 {
6823     ChessMove moveType;
6824     int fromX, fromY, toX, toY, boardIndex;
6825     char promoChar;
6826     char *p, *q;
6827     char buf[MSG_SIZ];
6828
6829     if (appData.debugMode)
6830       fprintf(debugFP, "Parsing game history: %s\n", game);
6831
6832     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6833     gameInfo.site = StrSave(appData.icsHost);
6834     gameInfo.date = PGNDate();
6835     gameInfo.round = StrSave("-");
6836
6837     /* Parse out names of players */
6838     while (*game == ' ') game++;
6839     p = buf;
6840     while (*game != ' ') *p++ = *game++;
6841     *p = NULLCHAR;
6842     gameInfo.white = StrSave(buf);
6843     while (*game == ' ') game++;
6844     p = buf;
6845     while (*game != ' ' && *game != '\n') *p++ = *game++;
6846     *p = NULLCHAR;
6847     gameInfo.black = StrSave(buf);
6848
6849     /* Parse moves */
6850     boardIndex = blackPlaysFirst ? 1 : 0;
6851     yynewstr(game);
6852     for (;;) {
6853         yyboardindex = boardIndex;
6854         moveType = (ChessMove) yylex();
6855         switch (moveType) {
6856           case IllegalMove:             /* maybe suicide chess, etc. */
6857   if (appData.debugMode) {
6858     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6859     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6860     setbuf(debugFP, NULL);
6861   }
6862           case WhitePromotionChancellor:
6863           case BlackPromotionChancellor:
6864           case WhitePromotionArchbishop:
6865           case BlackPromotionArchbishop:
6866           case WhitePromotionQueen:
6867           case BlackPromotionQueen:
6868           case WhitePromotionRook:
6869           case BlackPromotionRook:
6870           case WhitePromotionBishop:
6871           case BlackPromotionBishop:
6872           case WhitePromotionKnight:
6873           case BlackPromotionKnight:
6874           case WhitePromotionKing:
6875           case BlackPromotionKing:
6876           case NormalMove:
6877           case WhiteCapturesEnPassant:
6878           case BlackCapturesEnPassant:
6879           case WhiteKingSideCastle:
6880           case WhiteQueenSideCastle:
6881           case BlackKingSideCastle:
6882           case BlackQueenSideCastle:
6883           case WhiteKingSideCastleWild:
6884           case WhiteQueenSideCastleWild:
6885           case BlackKingSideCastleWild:
6886           case BlackQueenSideCastleWild:
6887           /* PUSH Fabien */
6888           case WhiteHSideCastleFR:
6889           case WhiteASideCastleFR:
6890           case BlackHSideCastleFR:
6891           case BlackASideCastleFR:
6892           /* POP Fabien */
6893             fromX = currentMoveString[0] - AAA;
6894             fromY = currentMoveString[1] - ONE;
6895             toX = currentMoveString[2] - AAA;
6896             toY = currentMoveString[3] - ONE;
6897             promoChar = currentMoveString[4];
6898             break;
6899           case WhiteDrop:
6900           case BlackDrop:
6901             fromX = moveType == WhiteDrop ?
6902               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6903             (int) CharToPiece(ToLower(currentMoveString[0]));
6904             fromY = DROP_RANK;
6905             toX = currentMoveString[2] - AAA;
6906             toY = currentMoveString[3] - ONE;
6907             promoChar = NULLCHAR;
6908             break;
6909           case AmbiguousMove:
6910             /* bug? */
6911             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6912   if (appData.debugMode) {
6913     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6914     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6915     setbuf(debugFP, NULL);
6916   }
6917             DisplayError(buf, 0);
6918             return;
6919           case ImpossibleMove:
6920             /* bug? */
6921             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6922   if (appData.debugMode) {
6923     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6924     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6925     setbuf(debugFP, NULL);
6926   }
6927             DisplayError(buf, 0);
6928             return;
6929           case (ChessMove) 0:   /* end of file */
6930             if (boardIndex < backwardMostMove) {
6931                 /* Oops, gap.  How did that happen? */
6932                 DisplayError(_("Gap in move list"), 0);
6933                 return;
6934             }
6935             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6936             if (boardIndex > forwardMostMove) {
6937                 forwardMostMove = boardIndex;
6938             }
6939             return;
6940           case ElapsedTime:
6941             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6942                 strcat(parseList[boardIndex-1], " ");
6943                 strcat(parseList[boardIndex-1], yy_text);
6944             }
6945             continue;
6946           case Comment:
6947           case PGNTag:
6948           case NAG:
6949           default:
6950             /* ignore */
6951             continue;
6952           case WhiteWins:
6953           case BlackWins:
6954           case GameIsDrawn:
6955           case GameUnfinished:
6956             if (gameMode == IcsExamining) {
6957                 if (boardIndex < backwardMostMove) {
6958                     /* Oops, gap.  How did that happen? */
6959                     return;
6960                 }
6961                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6962                 return;
6963             }
6964             gameInfo.result = moveType;
6965             p = strchr(yy_text, '{');
6966             if (p == NULL) p = strchr(yy_text, '(');
6967             if (p == NULL) {
6968                 p = yy_text;
6969                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6970             } else {
6971                 q = strchr(p, *p == '{' ? '}' : ')');
6972                 if (q != NULL) *q = NULLCHAR;
6973                 p++;
6974             }
6975             gameInfo.resultDetails = StrSave(p);
6976             continue;
6977         }
6978         if (boardIndex >= forwardMostMove &&
6979             !(gameMode == IcsObserving && ics_gamenum == -1)) {
6980             backwardMostMove = blackPlaysFirst ? 1 : 0;
6981             return;
6982         }
6983         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6984                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6985                                  parseList[boardIndex]);
6986         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6987         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6988         /* currentMoveString is set as a side-effect of yylex */
6989         strcpy(moveList[boardIndex], currentMoveString);
6990         strcat(moveList[boardIndex], "\n");
6991         boardIndex++;
6992         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
6993                                         castlingRights[boardIndex], &epStatus[boardIndex]);
6994         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
6995                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
6996           case MT_NONE:
6997           case MT_STALEMATE:
6998           default:
6999             break;
7000           case MT_CHECK:
7001             if(gameInfo.variant != VariantShogi)
7002                 strcat(parseList[boardIndex - 1], "+");
7003             break;
7004           case MT_CHECKMATE:
7005           case MT_STAINMATE:
7006             strcat(parseList[boardIndex - 1], "#");
7007             break;
7008         }
7009     }
7010 }
7011
7012
7013 /* Apply a move to the given board  */
7014 void
7015 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7016      int fromX, fromY, toX, toY;
7017      int promoChar;
7018      Board board;
7019      char *castling;
7020      char *ep;
7021 {
7022   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7023
7024     /* [HGM] compute & store e.p. status and castling rights for new position */
7025     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7026     { int i;
7027
7028       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7029       oldEP = *ep;
7030       *ep = EP_NONE;
7031
7032       if( board[toY][toX] != EmptySquare ) 
7033            *ep = EP_CAPTURE;  
7034
7035       if( board[fromY][fromX] == WhitePawn ) {
7036            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7037                *ep = EP_PAWN_MOVE;
7038            if( toY-fromY==2) {
7039                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7040                         gameInfo.variant != VariantBerolina || toX < fromX)
7041                       *ep = toX | berolina;
7042                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7043                         gameInfo.variant != VariantBerolina || toX > fromX) 
7044                       *ep = toX;
7045            }
7046       } else 
7047       if( board[fromY][fromX] == BlackPawn ) {
7048            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7049                *ep = EP_PAWN_MOVE; 
7050            if( toY-fromY== -2) {
7051                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7052                         gameInfo.variant != VariantBerolina || toX < fromX)
7053                       *ep = toX | berolina;
7054                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7055                         gameInfo.variant != VariantBerolina || toX > fromX) 
7056                       *ep = toX;
7057            }
7058        }
7059
7060        for(i=0; i<nrCastlingRights; i++) {
7061            if(castling[i] == fromX && castlingRank[i] == fromY ||
7062               castling[i] == toX   && castlingRank[i] == toY   
7063              ) castling[i] = -1; // revoke for moved or captured piece
7064        }
7065
7066     }
7067
7068   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7069   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7070        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7071          
7072   if (fromX == toX && fromY == toY) return;
7073
7074   if (fromY == DROP_RANK) {
7075         /* must be first */
7076         piece = board[toY][toX] = (ChessSquare) fromX;
7077   } else {
7078      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7079      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7080      if(gameInfo.variant == VariantKnightmate)
7081          king += (int) WhiteUnicorn - (int) WhiteKing;
7082
7083     /* Code added by Tord: */
7084     /* FRC castling assumed when king captures friendly rook. */
7085     if (board[fromY][fromX] == WhiteKing &&
7086              board[toY][toX] == WhiteRook) {
7087       board[fromY][fromX] = EmptySquare;
7088       board[toY][toX] = EmptySquare;
7089       if(toX > fromX) {
7090         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7091       } else {
7092         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7093       }
7094     } else if (board[fromY][fromX] == BlackKing &&
7095                board[toY][toX] == BlackRook) {
7096       board[fromY][fromX] = EmptySquare;
7097       board[toY][toX] = EmptySquare;
7098       if(toX > fromX) {
7099         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7100       } else {
7101         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7102       }
7103     /* End of code added by Tord */
7104
7105     } else if (board[fromY][fromX] == king
7106         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7107         && toY == fromY && toX > fromX+1) {
7108         board[fromY][fromX] = EmptySquare;
7109         board[toY][toX] = king;
7110         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7111         board[fromY][BOARD_RGHT-1] = EmptySquare;
7112     } else if (board[fromY][fromX] == king
7113         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7114                && toY == fromY && toX < fromX-1) {
7115         board[fromY][fromX] = EmptySquare;
7116         board[toY][toX] = king;
7117         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7118         board[fromY][BOARD_LEFT] = EmptySquare;
7119     } else if (board[fromY][fromX] == WhitePawn
7120                && toY == BOARD_HEIGHT-1
7121                && gameInfo.variant != VariantXiangqi
7122                ) {
7123         /* white pawn promotion */
7124         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7125         if (board[toY][toX] == EmptySquare) {
7126             board[toY][toX] = WhiteQueen;
7127         }
7128         if(gameInfo.variant==VariantBughouse ||
7129            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7130             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7131         board[fromY][fromX] = EmptySquare;
7132     } else if ((fromY == BOARD_HEIGHT-4)
7133                && (toX != fromX)
7134                && gameInfo.variant != VariantXiangqi
7135                && gameInfo.variant != VariantBerolina
7136                && (board[fromY][fromX] == WhitePawn)
7137                && (board[toY][toX] == EmptySquare)) {
7138         board[fromY][fromX] = EmptySquare;
7139         board[toY][toX] = WhitePawn;
7140         captured = board[toY - 1][toX];
7141         board[toY - 1][toX] = EmptySquare;
7142     } else if ((fromY == BOARD_HEIGHT-4)
7143                && (toX == fromX)
7144                && gameInfo.variant == VariantBerolina
7145                && (board[fromY][fromX] == WhitePawn)
7146                && (board[toY][toX] == EmptySquare)) {
7147         board[fromY][fromX] = EmptySquare;
7148         board[toY][toX] = WhitePawn;
7149         if(oldEP & EP_BEROLIN_A) {
7150                 captured = board[fromY][fromX-1];
7151                 board[fromY][fromX-1] = EmptySquare;
7152         }else{  captured = board[fromY][fromX+1];
7153                 board[fromY][fromX+1] = EmptySquare;
7154         }
7155     } else if (board[fromY][fromX] == king
7156         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7157                && toY == fromY && toX > fromX+1) {
7158         board[fromY][fromX] = EmptySquare;
7159         board[toY][toX] = king;
7160         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7161         board[fromY][BOARD_RGHT-1] = EmptySquare;
7162     } else if (board[fromY][fromX] == king
7163         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7164                && toY == fromY && toX < fromX-1) {
7165         board[fromY][fromX] = EmptySquare;
7166         board[toY][toX] = king;
7167         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7168         board[fromY][BOARD_LEFT] = EmptySquare;
7169     } else if (fromY == 7 && fromX == 3
7170                && board[fromY][fromX] == BlackKing
7171                && toY == 7 && toX == 5) {
7172         board[fromY][fromX] = EmptySquare;
7173         board[toY][toX] = BlackKing;
7174         board[fromY][7] = EmptySquare;
7175         board[toY][4] = BlackRook;
7176     } else if (fromY == 7 && fromX == 3
7177                && board[fromY][fromX] == BlackKing
7178                && toY == 7 && toX == 1) {
7179         board[fromY][fromX] = EmptySquare;
7180         board[toY][toX] = BlackKing;
7181         board[fromY][0] = EmptySquare;
7182         board[toY][2] = BlackRook;
7183     } else if (board[fromY][fromX] == BlackPawn
7184                && toY == 0
7185                && gameInfo.variant != VariantXiangqi
7186                ) {
7187         /* black pawn promotion */
7188         board[0][toX] = CharToPiece(ToLower(promoChar));
7189         if (board[0][toX] == EmptySquare) {
7190             board[0][toX] = BlackQueen;
7191         }
7192         if(gameInfo.variant==VariantBughouse ||
7193            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7194             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7195         board[fromY][fromX] = EmptySquare;
7196     } else if ((fromY == 3)
7197                && (toX != fromX)
7198                && gameInfo.variant != VariantXiangqi
7199                && gameInfo.variant != VariantBerolina
7200                && (board[fromY][fromX] == BlackPawn)
7201                && (board[toY][toX] == EmptySquare)) {
7202         board[fromY][fromX] = EmptySquare;
7203         board[toY][toX] = BlackPawn;
7204         captured = board[toY + 1][toX];
7205         board[toY + 1][toX] = EmptySquare;
7206     } else if ((fromY == 3)
7207                && (toX == fromX)
7208                && gameInfo.variant == VariantBerolina
7209                && (board[fromY][fromX] == BlackPawn)
7210                && (board[toY][toX] == EmptySquare)) {
7211         board[fromY][fromX] = EmptySquare;
7212         board[toY][toX] = BlackPawn;
7213         if(oldEP & EP_BEROLIN_A) {
7214                 captured = board[fromY][fromX-1];
7215                 board[fromY][fromX-1] = EmptySquare;
7216         }else{  captured = board[fromY][fromX+1];
7217                 board[fromY][fromX+1] = EmptySquare;
7218         }
7219     } else {
7220         board[toY][toX] = board[fromY][fromX];
7221         board[fromY][fromX] = EmptySquare;
7222     }
7223
7224     /* [HGM] now we promote for Shogi, if needed */
7225     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7226         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7227   }
7228
7229     if (gameInfo.holdingsWidth != 0) {
7230
7231       /* !!A lot more code needs to be written to support holdings  */
7232       /* [HGM] OK, so I have written it. Holdings are stored in the */
7233       /* penultimate board files, so they are automaticlly stored   */
7234       /* in the game history.                                       */
7235       if (fromY == DROP_RANK) {
7236         /* Delete from holdings, by decreasing count */
7237         /* and erasing image if necessary            */
7238         p = (int) fromX;
7239         if(p < (int) BlackPawn) { /* white drop */
7240              p -= (int)WhitePawn;
7241              if(p >= gameInfo.holdingsSize) p = 0;
7242              if(--board[p][BOARD_WIDTH-2] == 0)
7243                   board[p][BOARD_WIDTH-1] = EmptySquare;
7244         } else {                  /* black drop */
7245              p -= (int)BlackPawn;
7246              if(p >= gameInfo.holdingsSize) p = 0;
7247              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7248                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7249         }
7250       }
7251       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7252           && gameInfo.variant != VariantBughouse        ) {
7253         /* [HGM] holdings: Add to holdings, if holdings exist */
7254         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7255                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7256                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7257         }
7258         p = (int) captured;
7259         if (p >= (int) BlackPawn) {
7260           p -= (int)BlackPawn;
7261           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7262                   /* in Shogi restore piece to its original  first */
7263                   captured = (ChessSquare) (DEMOTED captured);
7264                   p = DEMOTED p;
7265           }
7266           p = PieceToNumber((ChessSquare)p);
7267           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7268           board[p][BOARD_WIDTH-2]++;
7269           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7270         } else {
7271           p -= (int)WhitePawn;
7272           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7273                   captured = (ChessSquare) (DEMOTED captured);
7274                   p = DEMOTED p;
7275           }
7276           p = PieceToNumber((ChessSquare)p);
7277           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7278           board[BOARD_HEIGHT-1-p][1]++;
7279           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7280         }
7281       }
7282
7283     } else if (gameInfo.variant == VariantAtomic) {
7284       if (captured != EmptySquare) {
7285         int y, x;
7286         for (y = toY-1; y <= toY+1; y++) {
7287           for (x = toX-1; x <= toX+1; x++) {
7288             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7289                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7290               board[y][x] = EmptySquare;
7291             }
7292           }
7293         }
7294         board[toY][toX] = EmptySquare;
7295       }
7296     }
7297     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7298         /* [HGM] Shogi promotions */
7299         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7300     }
7301
7302     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7303                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7304         // [HGM] superchess: take promotion piece out of holdings
7305         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7306         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7307             if(!--board[k][BOARD_WIDTH-2])
7308                 board[k][BOARD_WIDTH-1] = EmptySquare;
7309         } else {
7310             if(!--board[BOARD_HEIGHT-1-k][1])
7311                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7312         }
7313     }
7314
7315 }
7316
7317 /* Updates forwardMostMove */
7318 void
7319 MakeMove(fromX, fromY, toX, toY, promoChar)
7320      int fromX, fromY, toX, toY;
7321      int promoChar;
7322 {
7323 //    forwardMostMove++; // [HGM] bare: moved downstream
7324
7325     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7326         int timeLeft; static int lastLoadFlag=0; int king, piece;
7327         piece = boards[forwardMostMove][fromY][fromX];
7328         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7329         if(gameInfo.variant == VariantKnightmate)
7330             king += (int) WhiteUnicorn - (int) WhiteKing;
7331         if(forwardMostMove == 0) {
7332             if(blackPlaysFirst) 
7333                 fprintf(serverMoves, "%s;", second.tidy);
7334             fprintf(serverMoves, "%s;", first.tidy);
7335             if(!blackPlaysFirst) 
7336                 fprintf(serverMoves, "%s;", second.tidy);
7337         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7338         lastLoadFlag = loadFlag;
7339         // print base move
7340         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7341         // print castling suffix
7342         if( toY == fromY && piece == king ) {
7343             if(toX-fromX > 1)
7344                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7345             if(fromX-toX >1)
7346                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7347         }
7348         // e.p. suffix
7349         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7350              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7351              boards[forwardMostMove][toY][toX] == EmptySquare
7352              && fromX != toX )
7353                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7354         // promotion suffix
7355         if(promoChar != NULLCHAR)
7356                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7357         if(!loadFlag) {
7358             fprintf(serverMoves, "/%d/%d",
7359                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7360             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7361             else                      timeLeft = blackTimeRemaining/1000;
7362             fprintf(serverMoves, "/%d", timeLeft);
7363         }
7364         fflush(serverMoves);
7365     }
7366
7367     if (forwardMostMove+1 >= MAX_MOVES) {
7368       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7369                         0, 1);
7370       return;
7371     }
7372     if (commentList[forwardMostMove+1] != NULL) {
7373         free(commentList[forwardMostMove+1]);
7374         commentList[forwardMostMove+1] = NULL;
7375     }
7376     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7377     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7378     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7379                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7380     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7381     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7382     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7383     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7384     gameInfo.result = GameUnfinished;
7385     if (gameInfo.resultDetails != NULL) {
7386         free(gameInfo.resultDetails);
7387         gameInfo.resultDetails = NULL;
7388     }
7389     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7390                               moveList[forwardMostMove - 1]);
7391     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7392                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7393                              fromY, fromX, toY, toX, promoChar,
7394                              parseList[forwardMostMove - 1]);
7395     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7396                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7397                             castlingRights[forwardMostMove]) ) {
7398       case MT_NONE:
7399       case MT_STALEMATE:
7400       default:
7401         break;
7402       case MT_CHECK:
7403         if(gameInfo.variant != VariantShogi)
7404             strcat(parseList[forwardMostMove - 1], "+");
7405         break;
7406       case MT_CHECKMATE:
7407       case MT_STAINMATE:
7408         strcat(parseList[forwardMostMove - 1], "#");
7409         break;
7410     }
7411     if (appData.debugMode) {
7412         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7413     }
7414
7415 }
7416
7417 /* Updates currentMove if not pausing */
7418 void
7419 ShowMove(fromX, fromY, toX, toY)
7420 {
7421     int instant = (gameMode == PlayFromGameFile) ?
7422         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7423     if(appData.noGUI) return;
7424     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7425         if (!instant) {
7426             if (forwardMostMove == currentMove + 1) {
7427                 AnimateMove(boards[forwardMostMove - 1],
7428                             fromX, fromY, toX, toY);
7429             }
7430             if (appData.highlightLastMove) {
7431                 SetHighlights(fromX, fromY, toX, toY);
7432             }
7433         }
7434         currentMove = forwardMostMove;
7435     }
7436
7437     if (instant) return;
7438
7439     DisplayMove(currentMove - 1);
7440     DrawPosition(FALSE, boards[currentMove]);
7441     DisplayBothClocks();
7442     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7443 }
7444
7445 void SendEgtPath(ChessProgramState *cps)
7446 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7447         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7448
7449         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7450
7451         while(*p) {
7452             char c, *q = name+1, *r, *s;
7453
7454             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7455             while(*p && *p != ',') *q++ = *p++;
7456             *q++ = ':'; *q = 0;
7457             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7458                 strcmp(name, ",nalimov:") == 0 ) {
7459                 // take nalimov path from the menu-changeable option first, if it is defined
7460                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7461                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7462             } else
7463             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7464                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7465                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7466                 s = r = StrStr(s, ":") + 1; // beginning of path info
7467                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7468                 c = *r; *r = 0;             // temporarily null-terminate path info
7469                     *--q = 0;               // strip of trailig ':' from name
7470                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7471                 *r = c;
7472                 SendToProgram(buf,cps);     // send egtbpath command for this format
7473             }
7474             if(*p == ',') p++; // read away comma to position for next format name
7475         }
7476 }
7477
7478 void
7479 InitChessProgram(cps, setup)
7480      ChessProgramState *cps;
7481      int setup; /* [HGM] needed to setup FRC opening position */
7482 {
7483     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7484     if (appData.noChessProgram) return;
7485     hintRequested = FALSE;
7486     bookRequested = FALSE;
7487
7488     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7489     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7490     if(cps->memSize) { /* [HGM] memory */
7491         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7492         SendToProgram(buf, cps);
7493     }
7494     SendEgtPath(cps); /* [HGM] EGT */
7495     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7496         sprintf(buf, "cores %d\n", appData.smpCores);
7497         SendToProgram(buf, cps);
7498     }
7499
7500     SendToProgram(cps->initString, cps);
7501     if (gameInfo.variant != VariantNormal &&
7502         gameInfo.variant != VariantLoadable
7503         /* [HGM] also send variant if board size non-standard */
7504         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7505                                             ) {
7506       char *v = VariantName(gameInfo.variant);
7507       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7508         /* [HGM] in protocol 1 we have to assume all variants valid */
7509         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7510         DisplayFatalError(buf, 0, 1);
7511         return;
7512       }
7513
7514       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7515       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7516       if( gameInfo.variant == VariantXiangqi )
7517            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7518       if( gameInfo.variant == VariantShogi )
7519            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7520       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7521            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7522       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7523                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7524            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7525       if( gameInfo.variant == VariantCourier )
7526            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7527       if( gameInfo.variant == VariantSuper )
7528            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7529       if( gameInfo.variant == VariantGreat )
7530            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7531
7532       if(overruled) {
7533            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7534                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7535            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7536            if(StrStr(cps->variants, b) == NULL) { 
7537                // specific sized variant not known, check if general sizing allowed
7538                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7539                    if(StrStr(cps->variants, "boardsize") == NULL) {
7540                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7541                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7542                        DisplayFatalError(buf, 0, 1);
7543                        return;
7544                    }
7545                    /* [HGM] here we really should compare with the maximum supported board size */
7546                }
7547            }
7548       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7549       sprintf(buf, "variant %s\n", b);
7550       SendToProgram(buf, cps);
7551     }
7552     currentlyInitializedVariant = gameInfo.variant;
7553
7554     /* [HGM] send opening position in FRC to first engine */
7555     if(setup) {
7556           SendToProgram("force\n", cps);
7557           SendBoard(cps, 0);
7558           /* engine is now in force mode! Set flag to wake it up after first move. */
7559           setboardSpoiledMachineBlack = 1;
7560     }
7561
7562     if (cps->sendICS) {
7563       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7564       SendToProgram(buf, cps);
7565     }
7566     cps->maybeThinking = FALSE;
7567     cps->offeredDraw = 0;
7568     if (!appData.icsActive) {
7569         SendTimeControl(cps, movesPerSession, timeControl,
7570                         timeIncrement, appData.searchDepth,
7571                         searchTime);
7572     }
7573     if (appData.showThinking 
7574         // [HGM] thinking: four options require thinking output to be sent
7575         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7576                                 ) {
7577         SendToProgram("post\n", cps);
7578     }
7579     SendToProgram("hard\n", cps);
7580     if (!appData.ponderNextMove) {
7581         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7582            it without being sure what state we are in first.  "hard"
7583            is not a toggle, so that one is OK.
7584          */
7585         SendToProgram("easy\n", cps);
7586     }
7587     if (cps->usePing) {
7588       sprintf(buf, "ping %d\n", ++cps->lastPing);
7589       SendToProgram(buf, cps);
7590     }
7591     cps->initDone = TRUE;
7592 }   
7593
7594
7595 void
7596 StartChessProgram(cps)
7597      ChessProgramState *cps;
7598 {
7599     char buf[MSG_SIZ];
7600     int err;
7601
7602     if (appData.noChessProgram) return;
7603     cps->initDone = FALSE;
7604
7605     if (strcmp(cps->host, "localhost") == 0) {
7606         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7607     } else if (*appData.remoteShell == NULLCHAR) {
7608         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7609     } else {
7610         if (*appData.remoteUser == NULLCHAR) {
7611           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7612                     cps->program);
7613         } else {
7614           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7615                     cps->host, appData.remoteUser, cps->program);
7616         }
7617         err = StartChildProcess(buf, "", &cps->pr);
7618     }
7619     
7620     if (err != 0) {
7621         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7622         DisplayFatalError(buf, err, 1);
7623         cps->pr = NoProc;
7624         cps->isr = NULL;
7625         return;
7626     }
7627     
7628     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7629     if (cps->protocolVersion > 1) {
7630       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7631       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7632       cps->comboCnt = 0;  //                and values of combo boxes
7633       SendToProgram(buf, cps);
7634     } else {
7635       SendToProgram("xboard\n", cps);
7636     }
7637 }
7638
7639
7640 void
7641 TwoMachinesEventIfReady P((void))
7642 {
7643   if (first.lastPing != first.lastPong) {
7644     DisplayMessage("", _("Waiting for first chess program"));
7645     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7646     return;
7647   }
7648   if (second.lastPing != second.lastPong) {
7649     DisplayMessage("", _("Waiting for second chess program"));
7650     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7651     return;
7652   }
7653   ThawUI();
7654   TwoMachinesEvent();
7655 }
7656
7657 void
7658 NextMatchGame P((void))
7659 {
7660     int index; /* [HGM] autoinc: step lod index during match */
7661     Reset(FALSE, TRUE);
7662     if (*appData.loadGameFile != NULLCHAR) {
7663         index = appData.loadGameIndex;
7664         if(index < 0) { // [HGM] autoinc
7665             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7666             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7667         } 
7668         LoadGameFromFile(appData.loadGameFile,
7669                          index,
7670                          appData.loadGameFile, FALSE);
7671     } else if (*appData.loadPositionFile != NULLCHAR) {
7672         index = appData.loadPositionIndex;
7673         if(index < 0) { // [HGM] autoinc
7674             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7675             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7676         } 
7677         LoadPositionFromFile(appData.loadPositionFile,
7678                              index,
7679                              appData.loadPositionFile);
7680     }
7681     TwoMachinesEventIfReady();
7682 }
7683
7684 void UserAdjudicationEvent( int result )
7685 {
7686     ChessMove gameResult = GameIsDrawn;
7687
7688     if( result > 0 ) {
7689         gameResult = WhiteWins;
7690     }
7691     else if( result < 0 ) {
7692         gameResult = BlackWins;
7693     }
7694
7695     if( gameMode == TwoMachinesPlay ) {
7696         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7697     }
7698 }
7699
7700
7701 // [HGM] save: calculate checksum of game to make games easily identifiable
7702 int StringCheckSum(char *s)
7703 {
7704         int i = 0;
7705         if(s==NULL) return 0;
7706         while(*s) i = i*259 + *s++;
7707         return i;
7708 }
7709
7710 int GameCheckSum()
7711 {
7712         int i, sum=0;
7713         for(i=backwardMostMove; i<forwardMostMove; i++) {
7714                 sum += pvInfoList[i].depth;
7715                 sum += StringCheckSum(parseList[i]);
7716                 sum += StringCheckSum(commentList[i]);
7717                 sum *= 261;
7718         }
7719         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7720         return sum + StringCheckSum(commentList[i]);
7721 } // end of save patch
7722
7723 void
7724 GameEnds(result, resultDetails, whosays)
7725      ChessMove result;
7726      char *resultDetails;
7727      int whosays;
7728 {
7729     GameMode nextGameMode;
7730     int isIcsGame;
7731     char buf[MSG_SIZ];
7732
7733     if(endingGame) return; /* [HGM] crash: forbid recursion */
7734     endingGame = 1;
7735
7736     if (appData.debugMode) {
7737       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7738               result, resultDetails ? resultDetails : "(null)", whosays);
7739     }
7740
7741     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7742         /* If we are playing on ICS, the server decides when the
7743            game is over, but the engine can offer to draw, claim 
7744            a draw, or resign. 
7745          */
7746 #if ZIPPY
7747         if (appData.zippyPlay && first.initDone) {
7748             if (result == GameIsDrawn) {
7749                 /* In case draw still needs to be claimed */
7750                 SendToICS(ics_prefix);
7751                 SendToICS("draw\n");
7752             } else if (StrCaseStr(resultDetails, "resign")) {
7753                 SendToICS(ics_prefix);
7754                 SendToICS("resign\n");
7755             }
7756         }
7757 #endif
7758         endingGame = 0; /* [HGM] crash */
7759         return;
7760     }
7761
7762     /* If we're loading the game from a file, stop */
7763     if (whosays == GE_FILE) {
7764       (void) StopLoadGameTimer();
7765       gameFileFP = NULL;
7766     }
7767
7768     /* Cancel draw offers */
7769     first.offeredDraw = second.offeredDraw = 0;
7770
7771     /* If this is an ICS game, only ICS can really say it's done;
7772        if not, anyone can. */
7773     isIcsGame = (gameMode == IcsPlayingWhite || 
7774                  gameMode == IcsPlayingBlack || 
7775                  gameMode == IcsObserving    || 
7776                  gameMode == IcsExamining);
7777
7778     if (!isIcsGame || whosays == GE_ICS) {
7779         /* OK -- not an ICS game, or ICS said it was done */
7780         StopClocks();
7781         if (!isIcsGame && !appData.noChessProgram) 
7782           SetUserThinkingEnables();
7783     
7784         /* [HGM] if a machine claims the game end we verify this claim */
7785         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7786             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7787                 char claimer;
7788                 ChessMove trueResult = (ChessMove) -1;
7789
7790                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7791                                             first.twoMachinesColor[0] :
7792                                             second.twoMachinesColor[0] ;
7793
7794                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7795                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7796                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7797                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7798                 } else
7799                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7800                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7801                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7802                 } else
7803                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7804                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7805                 }
7806
7807                 // now verify win claims, but not in drop games, as we don't understand those yet
7808                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7809                                                  || gameInfo.variant == VariantGreat) &&
7810                     (result == WhiteWins && claimer == 'w' ||
7811                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7812                       if (appData.debugMode) {
7813                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7814                                 result, epStatus[forwardMostMove], forwardMostMove);
7815                       }
7816                       if(result != trueResult) {
7817                               sprintf(buf, "False win claim: '%s'", resultDetails);
7818                               result = claimer == 'w' ? BlackWins : WhiteWins;
7819                               resultDetails = buf;
7820                       }
7821                 } else
7822                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7823                     && (forwardMostMove <= backwardMostMove ||
7824                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7825                         (claimer=='b')==(forwardMostMove&1))
7826                                                                                   ) {
7827                       /* [HGM] verify: draws that were not flagged are false claims */
7828                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7829                       result = claimer == 'w' ? BlackWins : WhiteWins;
7830                       resultDetails = buf;
7831                 }
7832                 /* (Claiming a loss is accepted no questions asked!) */
7833             }
7834             /* [HGM] bare: don't allow bare King to win */
7835             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7836                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7837                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7838                && result != GameIsDrawn)
7839             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7840                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7841                         int p = (int)boards[forwardMostMove][i][j] - color;
7842                         if(p >= 0 && p <= (int)WhiteKing) k++;
7843                 }
7844                 if (appData.debugMode) {
7845                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7846                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7847                 }
7848                 if(k <= 1) {
7849                         result = GameIsDrawn;
7850                         sprintf(buf, "%s but bare king", resultDetails);
7851                         resultDetails = buf;
7852                 }
7853             }
7854         }
7855
7856
7857         if(serverMoves != NULL && !loadFlag) { char c = '=';
7858             if(result==WhiteWins) c = '+';
7859             if(result==BlackWins) c = '-';
7860             if(resultDetails != NULL)
7861                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7862         }
7863         if (resultDetails != NULL) {
7864             gameInfo.result = result;
7865             gameInfo.resultDetails = StrSave(resultDetails);
7866
7867             /* display last move only if game was not loaded from file */
7868             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7869                 DisplayMove(currentMove - 1);
7870     
7871             if (forwardMostMove != 0) {
7872                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7873                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7874                                                                 ) {
7875                     if (*appData.saveGameFile != NULLCHAR) {
7876                         SaveGameToFile(appData.saveGameFile, TRUE);
7877                     } else if (appData.autoSaveGames) {
7878                         AutoSaveGame();
7879                     }
7880                     if (*appData.savePositionFile != NULLCHAR) {
7881                         SavePositionToFile(appData.savePositionFile);
7882                     }
7883                 }
7884             }
7885
7886             /* Tell program how game ended in case it is learning */
7887             /* [HGM] Moved this to after saving the PGN, just in case */
7888             /* engine died and we got here through time loss. In that */
7889             /* case we will get a fatal error writing the pipe, which */
7890             /* would otherwise lose us the PGN.                       */
7891             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7892             /* output during GameEnds should never be fatal anymore   */
7893             if (gameMode == MachinePlaysWhite ||
7894                 gameMode == MachinePlaysBlack ||
7895                 gameMode == TwoMachinesPlay ||
7896                 gameMode == IcsPlayingWhite ||
7897                 gameMode == IcsPlayingBlack ||
7898                 gameMode == BeginningOfGame) {
7899                 char buf[MSG_SIZ];
7900                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7901                         resultDetails);
7902                 if (first.pr != NoProc) {
7903                     SendToProgram(buf, &first);
7904                 }
7905                 if (second.pr != NoProc &&
7906                     gameMode == TwoMachinesPlay) {
7907                     SendToProgram(buf, &second);
7908                 }
7909             }
7910         }
7911
7912         if (appData.icsActive) {
7913             if (appData.quietPlay &&
7914                 (gameMode == IcsPlayingWhite ||
7915                  gameMode == IcsPlayingBlack)) {
7916                 SendToICS(ics_prefix);
7917                 SendToICS("set shout 1\n");
7918             }
7919             nextGameMode = IcsIdle;
7920             ics_user_moved = FALSE;
7921             /* clean up premove.  It's ugly when the game has ended and the
7922              * premove highlights are still on the board.
7923              */
7924             if (gotPremove) {
7925               gotPremove = FALSE;
7926               ClearPremoveHighlights();
7927               DrawPosition(FALSE, boards[currentMove]);
7928             }
7929             if (whosays == GE_ICS) {
7930                 switch (result) {
7931                 case WhiteWins:
7932                     if (gameMode == IcsPlayingWhite)
7933                         PlayIcsWinSound();
7934                     else if(gameMode == IcsPlayingBlack)
7935                         PlayIcsLossSound();
7936                     break;
7937                 case BlackWins:
7938                     if (gameMode == IcsPlayingBlack)
7939                         PlayIcsWinSound();
7940                     else if(gameMode == IcsPlayingWhite)
7941                         PlayIcsLossSound();
7942                     break;
7943                 case GameIsDrawn:
7944                     PlayIcsDrawSound();
7945                     break;
7946                 default:
7947                     PlayIcsUnfinishedSound();
7948                 }
7949             }
7950         } else if (gameMode == EditGame ||
7951                    gameMode == PlayFromGameFile || 
7952                    gameMode == AnalyzeMode || 
7953                    gameMode == AnalyzeFile) {
7954             nextGameMode = gameMode;
7955         } else {
7956             nextGameMode = EndOfGame;
7957         }
7958         pausing = FALSE;
7959         ModeHighlight();
7960     } else {
7961         nextGameMode = gameMode;
7962     }
7963
7964     if (appData.noChessProgram) {
7965         gameMode = nextGameMode;
7966         ModeHighlight();
7967         endingGame = 0; /* [HGM] crash */
7968         return;
7969     }
7970
7971     if (first.reuse) {
7972         /* Put first chess program into idle state */
7973         if (first.pr != NoProc &&
7974             (gameMode == MachinePlaysWhite ||
7975              gameMode == MachinePlaysBlack ||
7976              gameMode == TwoMachinesPlay ||
7977              gameMode == IcsPlayingWhite ||
7978              gameMode == IcsPlayingBlack ||
7979              gameMode == BeginningOfGame)) {
7980             SendToProgram("force\n", &first);
7981             if (first.usePing) {
7982               char buf[MSG_SIZ];
7983               sprintf(buf, "ping %d\n", ++first.lastPing);
7984               SendToProgram(buf, &first);
7985             }
7986         }
7987     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7988         /* Kill off first chess program */
7989         if (first.isr != NULL)
7990           RemoveInputSource(first.isr);
7991         first.isr = NULL;
7992     
7993         if (first.pr != NoProc) {
7994             ExitAnalyzeMode();
7995             DoSleep( appData.delayBeforeQuit );
7996             SendToProgram("quit\n", &first);
7997             DoSleep( appData.delayAfterQuit );
7998             DestroyChildProcess(first.pr, first.useSigterm);
7999         }
8000         first.pr = NoProc;
8001     }
8002     if (second.reuse) {
8003         /* Put second chess program into idle state */
8004         if (second.pr != NoProc &&
8005             gameMode == TwoMachinesPlay) {
8006             SendToProgram("force\n", &second);
8007             if (second.usePing) {
8008               char buf[MSG_SIZ];
8009               sprintf(buf, "ping %d\n", ++second.lastPing);
8010               SendToProgram(buf, &second);
8011             }
8012         }
8013     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8014         /* Kill off second chess program */
8015         if (second.isr != NULL)
8016           RemoveInputSource(second.isr);
8017         second.isr = NULL;
8018     
8019         if (second.pr != NoProc) {
8020             DoSleep( appData.delayBeforeQuit );
8021             SendToProgram("quit\n", &second);
8022             DoSleep( appData.delayAfterQuit );
8023             DestroyChildProcess(second.pr, second.useSigterm);
8024         }
8025         second.pr = NoProc;
8026     }
8027
8028     if (matchMode && gameMode == TwoMachinesPlay) {
8029         switch (result) {
8030         case WhiteWins:
8031           if (first.twoMachinesColor[0] == 'w') {
8032             first.matchWins++;
8033           } else {
8034             second.matchWins++;
8035           }
8036           break;
8037         case BlackWins:
8038           if (first.twoMachinesColor[0] == 'b') {
8039             first.matchWins++;
8040           } else {
8041             second.matchWins++;
8042           }
8043           break;
8044         default:
8045           break;
8046         }
8047         if (matchGame < appData.matchGames) {
8048             char *tmp;
8049             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8050                 tmp = first.twoMachinesColor;
8051                 first.twoMachinesColor = second.twoMachinesColor;
8052                 second.twoMachinesColor = tmp;
8053             }
8054             gameMode = nextGameMode;
8055             matchGame++;
8056             if(appData.matchPause>10000 || appData.matchPause<10)
8057                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8058             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8059             endingGame = 0; /* [HGM] crash */
8060             return;
8061         } else {
8062             char buf[MSG_SIZ];
8063             gameMode = nextGameMode;
8064             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8065                     first.tidy, second.tidy,
8066                     first.matchWins, second.matchWins,
8067                     appData.matchGames - (first.matchWins + second.matchWins));
8068             DisplayFatalError(buf, 0, 0);
8069         }
8070     }
8071     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8072         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8073       ExitAnalyzeMode();
8074     gameMode = nextGameMode;
8075     ModeHighlight();
8076     endingGame = 0;  /* [HGM] crash */
8077 }
8078
8079 /* Assumes program was just initialized (initString sent).
8080    Leaves program in force mode. */
8081 void
8082 FeedMovesToProgram(cps, upto) 
8083      ChessProgramState *cps;
8084      int upto;
8085 {
8086     int i;
8087     
8088     if (appData.debugMode)
8089       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8090               startedFromSetupPosition ? "position and " : "",
8091               backwardMostMove, upto, cps->which);
8092     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8093         // [HGM] variantswitch: make engine aware of new variant
8094         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8095                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8096         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8097         SendToProgram(buf, cps);
8098         currentlyInitializedVariant = gameInfo.variant;
8099     }
8100     SendToProgram("force\n", cps);
8101     if (startedFromSetupPosition) {
8102         SendBoard(cps, backwardMostMove);
8103     if (appData.debugMode) {
8104         fprintf(debugFP, "feedMoves\n");
8105     }
8106     }
8107     for (i = backwardMostMove; i < upto; i++) {
8108         SendMoveToProgram(i, cps);
8109     }
8110 }
8111
8112
8113 void
8114 ResurrectChessProgram()
8115 {
8116      /* The chess program may have exited.
8117         If so, restart it and feed it all the moves made so far. */
8118
8119     if (appData.noChessProgram || first.pr != NoProc) return;
8120     
8121     StartChessProgram(&first);
8122     InitChessProgram(&first, FALSE);
8123     FeedMovesToProgram(&first, currentMove);
8124
8125     if (!first.sendTime) {
8126         /* can't tell gnuchess what its clock should read,
8127            so we bow to its notion. */
8128         ResetClocks();
8129         timeRemaining[0][currentMove] = whiteTimeRemaining;
8130         timeRemaining[1][currentMove] = blackTimeRemaining;
8131     }
8132
8133     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8134                 appData.icsEngineAnalyze) && first.analysisSupport) {
8135       SendToProgram("analyze\n", &first);
8136       first.analyzing = TRUE;
8137     }
8138 }
8139
8140 /*
8141  * Button procedures
8142  */
8143 void
8144 Reset(redraw, init)
8145      int redraw, init;
8146 {
8147     int i;
8148
8149     if (appData.debugMode) {
8150         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8151                 redraw, init, gameMode);
8152     }
8153     pausing = pauseExamInvalid = FALSE;
8154     startedFromSetupPosition = blackPlaysFirst = FALSE;
8155     firstMove = TRUE;
8156     whiteFlag = blackFlag = FALSE;
8157     userOfferedDraw = FALSE;
8158     hintRequested = bookRequested = FALSE;
8159     first.maybeThinking = FALSE;
8160     second.maybeThinking = FALSE;
8161     first.bookSuspend = FALSE; // [HGM] book
8162     second.bookSuspend = FALSE;
8163     thinkOutput[0] = NULLCHAR;
8164     lastHint[0] = NULLCHAR;
8165     ClearGameInfo(&gameInfo);
8166     gameInfo.variant = StringToVariant(appData.variant);
8167     ics_user_moved = ics_clock_paused = FALSE;
8168     ics_getting_history = H_FALSE;
8169     ics_gamenum = -1;
8170     white_holding[0] = black_holding[0] = NULLCHAR;
8171     ClearProgramStats();
8172     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8173     
8174     ResetFrontEnd();
8175     ClearHighlights();
8176     flipView = appData.flipView;
8177     ClearPremoveHighlights();
8178     gotPremove = FALSE;
8179     alarmSounded = FALSE;
8180
8181     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8182     if(appData.serverMovesName != NULL) {
8183         /* [HGM] prepare to make moves file for broadcasting */
8184         clock_t t = clock();
8185         if(serverMoves != NULL) fclose(serverMoves);
8186         serverMoves = fopen(appData.serverMovesName, "r");
8187         if(serverMoves != NULL) {
8188             fclose(serverMoves);
8189             /* delay 15 sec before overwriting, so all clients can see end */
8190             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8191         }
8192         serverMoves = fopen(appData.serverMovesName, "w");
8193     }
8194
8195     ExitAnalyzeMode();
8196     gameMode = BeginningOfGame;
8197     ModeHighlight();
8198     if(appData.icsActive) gameInfo.variant = VariantNormal;
8199     currentMove = forwardMostMove = backwardMostMove = 0;
8200     InitPosition(redraw);
8201     for (i = 0; i < MAX_MOVES; i++) {
8202         if (commentList[i] != NULL) {
8203             free(commentList[i]);
8204             commentList[i] = NULL;
8205         }
8206     }
8207     ResetClocks();
8208     timeRemaining[0][0] = whiteTimeRemaining;
8209     timeRemaining[1][0] = blackTimeRemaining;
8210     if (first.pr == NULL) {
8211         StartChessProgram(&first);
8212     }
8213     if (init) {
8214             InitChessProgram(&first, startedFromSetupPosition);
8215     }
8216     DisplayTitle("");
8217     DisplayMessage("", "");
8218     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8219     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8220 }
8221
8222 void
8223 AutoPlayGameLoop()
8224 {
8225     for (;;) {
8226         if (!AutoPlayOneMove())
8227           return;
8228         if (matchMode || appData.timeDelay == 0)
8229           continue;
8230         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8231           return;
8232         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8233         break;
8234     }
8235 }
8236
8237
8238 int
8239 AutoPlayOneMove()
8240 {
8241     int fromX, fromY, toX, toY;
8242
8243     if (appData.debugMode) {
8244       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8245     }
8246
8247     if (gameMode != PlayFromGameFile)
8248       return FALSE;
8249
8250     if (currentMove >= forwardMostMove) {
8251       gameMode = EditGame;
8252       ModeHighlight();
8253
8254       /* [AS] Clear current move marker at the end of a game */
8255       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8256
8257       return FALSE;
8258     }
8259     
8260     toX = moveList[currentMove][2] - AAA;
8261     toY = moveList[currentMove][3] - ONE;
8262
8263     if (moveList[currentMove][1] == '@') {
8264         if (appData.highlightLastMove) {
8265             SetHighlights(-1, -1, toX, toY);
8266         }
8267     } else {
8268         fromX = moveList[currentMove][0] - AAA;
8269         fromY = moveList[currentMove][1] - ONE;
8270
8271         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8272
8273         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8274
8275         if (appData.highlightLastMove) {
8276             SetHighlights(fromX, fromY, toX, toY);
8277         }
8278     }
8279     DisplayMove(currentMove);
8280     SendMoveToProgram(currentMove++, &first);
8281     DisplayBothClocks();
8282     DrawPosition(FALSE, boards[currentMove]);
8283     // [HGM] PV info: always display, routine tests if empty
8284     DisplayComment(currentMove - 1, commentList[currentMove]);
8285     return TRUE;
8286 }
8287
8288
8289 int
8290 LoadGameOneMove(readAhead)
8291      ChessMove readAhead;
8292 {
8293     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8294     char promoChar = NULLCHAR;
8295     ChessMove moveType;
8296     char move[MSG_SIZ];
8297     char *p, *q;
8298     
8299     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8300         gameMode != AnalyzeMode && gameMode != Training) {
8301         gameFileFP = NULL;
8302         return FALSE;
8303     }
8304     
8305     yyboardindex = forwardMostMove;
8306     if (readAhead != (ChessMove)0) {
8307       moveType = readAhead;
8308     } else {
8309       if (gameFileFP == NULL)
8310           return FALSE;
8311       moveType = (ChessMove) yylex();
8312     }
8313     
8314     done = FALSE;
8315     switch (moveType) {
8316       case Comment:
8317         if (appData.debugMode) 
8318           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8319         p = yy_text;
8320         if (*p == '{' || *p == '[' || *p == '(') {
8321             p[strlen(p) - 1] = NULLCHAR;
8322             p++;
8323         }
8324
8325         /* append the comment but don't display it */
8326         while (*p == '\n') p++;
8327         AppendComment(currentMove, p);
8328         return TRUE;
8329
8330       case WhiteCapturesEnPassant:
8331       case BlackCapturesEnPassant:
8332       case WhitePromotionChancellor:
8333       case BlackPromotionChancellor:
8334       case WhitePromotionArchbishop:
8335       case BlackPromotionArchbishop:
8336       case WhitePromotionCentaur:
8337       case BlackPromotionCentaur:
8338       case WhitePromotionQueen:
8339       case BlackPromotionQueen:
8340       case WhitePromotionRook:
8341       case BlackPromotionRook:
8342       case WhitePromotionBishop:
8343       case BlackPromotionBishop:
8344       case WhitePromotionKnight:
8345       case BlackPromotionKnight:
8346       case WhitePromotionKing:
8347       case BlackPromotionKing:
8348       case NormalMove:
8349       case WhiteKingSideCastle:
8350       case WhiteQueenSideCastle:
8351       case BlackKingSideCastle:
8352       case BlackQueenSideCastle:
8353       case WhiteKingSideCastleWild:
8354       case WhiteQueenSideCastleWild:
8355       case BlackKingSideCastleWild:
8356       case BlackQueenSideCastleWild:
8357       /* PUSH Fabien */
8358       case WhiteHSideCastleFR:
8359       case WhiteASideCastleFR:
8360       case BlackHSideCastleFR:
8361       case BlackASideCastleFR:
8362       /* POP Fabien */
8363         if (appData.debugMode)
8364           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8365         fromX = currentMoveString[0] - AAA;
8366         fromY = currentMoveString[1] - ONE;
8367         toX = currentMoveString[2] - AAA;
8368         toY = currentMoveString[3] - ONE;
8369         promoChar = currentMoveString[4];
8370         break;
8371
8372       case WhiteDrop:
8373       case BlackDrop:
8374         if (appData.debugMode)
8375           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8376         fromX = moveType == WhiteDrop ?
8377           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8378         (int) CharToPiece(ToLower(currentMoveString[0]));
8379         fromY = DROP_RANK;
8380         toX = currentMoveString[2] - AAA;
8381         toY = currentMoveString[3] - ONE;
8382         break;
8383
8384       case WhiteWins:
8385       case BlackWins:
8386       case GameIsDrawn:
8387       case GameUnfinished:
8388         if (appData.debugMode)
8389           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8390         p = strchr(yy_text, '{');
8391         if (p == NULL) p = strchr(yy_text, '(');
8392         if (p == NULL) {
8393             p = yy_text;
8394             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8395         } else {
8396             q = strchr(p, *p == '{' ? '}' : ')');
8397             if (q != NULL) *q = NULLCHAR;
8398             p++;
8399         }
8400         GameEnds(moveType, p, GE_FILE);
8401         done = TRUE;
8402         if (cmailMsgLoaded) {
8403             ClearHighlights();
8404             flipView = WhiteOnMove(currentMove);
8405             if (moveType == GameUnfinished) flipView = !flipView;
8406             if (appData.debugMode)
8407               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8408         }
8409         break;
8410
8411       case (ChessMove) 0:       /* end of file */
8412         if (appData.debugMode)
8413           fprintf(debugFP, "Parser hit end of file\n");
8414         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8415                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8416           case MT_NONE:
8417           case MT_CHECK:
8418             break;
8419           case MT_CHECKMATE:
8420           case MT_STAINMATE:
8421             if (WhiteOnMove(currentMove)) {
8422                 GameEnds(BlackWins, "Black mates", GE_FILE);
8423             } else {
8424                 GameEnds(WhiteWins, "White mates", GE_FILE);
8425             }
8426             break;
8427           case MT_STALEMATE:
8428             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8429             break;
8430         }
8431         done = TRUE;
8432         break;
8433
8434       case MoveNumberOne:
8435         if (lastLoadGameStart == GNUChessGame) {
8436             /* GNUChessGames have numbers, but they aren't move numbers */
8437             if (appData.debugMode)
8438               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8439                       yy_text, (int) moveType);
8440             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8441         }
8442         /* else fall thru */
8443
8444       case XBoardGame:
8445       case GNUChessGame:
8446       case PGNTag:
8447         /* Reached start of next game in file */
8448         if (appData.debugMode)
8449           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8450         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8451                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8452           case MT_NONE:
8453           case MT_CHECK:
8454             break;
8455           case MT_CHECKMATE:
8456           case MT_STAINMATE:
8457             if (WhiteOnMove(currentMove)) {
8458                 GameEnds(BlackWins, "Black mates", GE_FILE);
8459             } else {
8460                 GameEnds(WhiteWins, "White mates", GE_FILE);
8461             }
8462             break;
8463           case MT_STALEMATE:
8464             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8465             break;
8466         }
8467         done = TRUE;
8468         break;
8469
8470       case PositionDiagram:     /* should not happen; ignore */
8471       case ElapsedTime:         /* ignore */
8472       case NAG:                 /* ignore */
8473         if (appData.debugMode)
8474           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8475                   yy_text, (int) moveType);
8476         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8477
8478       case IllegalMove:
8479         if (appData.testLegality) {
8480             if (appData.debugMode)
8481               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8482             sprintf(move, _("Illegal move: %d.%s%s"),
8483                     (forwardMostMove / 2) + 1,
8484                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8485             DisplayError(move, 0);
8486             done = TRUE;
8487         } else {
8488             if (appData.debugMode)
8489               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8490                       yy_text, currentMoveString);
8491             fromX = currentMoveString[0] - AAA;
8492             fromY = currentMoveString[1] - ONE;
8493             toX = currentMoveString[2] - AAA;
8494             toY = currentMoveString[3] - ONE;
8495             promoChar = currentMoveString[4];
8496         }
8497         break;
8498
8499       case AmbiguousMove:
8500         if (appData.debugMode)
8501           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8502         sprintf(move, _("Ambiguous move: %d.%s%s"),
8503                 (forwardMostMove / 2) + 1,
8504                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8505         DisplayError(move, 0);
8506         done = TRUE;
8507         break;
8508
8509       default:
8510       case ImpossibleMove:
8511         if (appData.debugMode)
8512           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8513         sprintf(move, _("Illegal move: %d.%s%s"),
8514                 (forwardMostMove / 2) + 1,
8515                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8516         DisplayError(move, 0);
8517         done = TRUE;
8518         break;
8519     }
8520
8521     if (done) {
8522         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8523             DrawPosition(FALSE, boards[currentMove]);
8524             DisplayBothClocks();
8525             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8526               DisplayComment(currentMove - 1, commentList[currentMove]);
8527         }
8528         (void) StopLoadGameTimer();
8529         gameFileFP = NULL;
8530         cmailOldMove = forwardMostMove;
8531         return FALSE;
8532     } else {
8533         /* currentMoveString is set as a side-effect of yylex */
8534         strcat(currentMoveString, "\n");
8535         strcpy(moveList[forwardMostMove], currentMoveString);
8536         
8537         thinkOutput[0] = NULLCHAR;
8538         MakeMove(fromX, fromY, toX, toY, promoChar);
8539         currentMove = forwardMostMove;
8540         return TRUE;
8541     }
8542 }
8543
8544 /* Load the nth game from the given file */
8545 int
8546 LoadGameFromFile(filename, n, title, useList)
8547      char *filename;
8548      int n;
8549      char *title;
8550      /*Boolean*/ int useList;
8551 {
8552     FILE *f;
8553     char buf[MSG_SIZ];
8554
8555     if (strcmp(filename, "-") == 0) {
8556         f = stdin;
8557         title = "stdin";
8558     } else {
8559         f = fopen(filename, "rb");
8560         if (f == NULL) {
8561           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8562             DisplayError(buf, errno);
8563             return FALSE;
8564         }
8565     }
8566     if (fseek(f, 0, 0) == -1) {
8567         /* f is not seekable; probably a pipe */
8568         useList = FALSE;
8569     }
8570     if (useList && n == 0) {
8571         int error = GameListBuild(f);
8572         if (error) {
8573             DisplayError(_("Cannot build game list"), error);
8574         } else if (!ListEmpty(&gameList) &&
8575                    ((ListGame *) gameList.tailPred)->number > 1) {
8576             GameListPopUp(f, title);
8577             return TRUE;
8578         }
8579         GameListDestroy();
8580         n = 1;
8581     }
8582     if (n == 0) n = 1;
8583     return LoadGame(f, n, title, FALSE);
8584 }
8585
8586
8587 void
8588 MakeRegisteredMove()
8589 {
8590     int fromX, fromY, toX, toY;
8591     char promoChar;
8592     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8593         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8594           case CMAIL_MOVE:
8595           case CMAIL_DRAW:
8596             if (appData.debugMode)
8597               fprintf(debugFP, "Restoring %s for game %d\n",
8598                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8599     
8600             thinkOutput[0] = NULLCHAR;
8601             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8602             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8603             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8604             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8605             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8606             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8607             MakeMove(fromX, fromY, toX, toY, promoChar);
8608             ShowMove(fromX, fromY, toX, toY);
8609               
8610             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8611                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8612               case MT_NONE:
8613               case MT_CHECK:
8614                 break;
8615                 
8616               case MT_CHECKMATE:
8617               case MT_STAINMATE:
8618                 if (WhiteOnMove(currentMove)) {
8619                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8620                 } else {
8621                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8622                 }
8623                 break;
8624                 
8625               case MT_STALEMATE:
8626                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8627                 break;
8628             }
8629
8630             break;
8631             
8632           case CMAIL_RESIGN:
8633             if (WhiteOnMove(currentMove)) {
8634                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8635             } else {
8636                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8637             }
8638             break;
8639             
8640           case CMAIL_ACCEPT:
8641             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8642             break;
8643               
8644           default:
8645             break;
8646         }
8647     }
8648
8649     return;
8650 }
8651
8652 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8653 int
8654 CmailLoadGame(f, gameNumber, title, useList)
8655      FILE *f;
8656      int gameNumber;
8657      char *title;
8658      int useList;
8659 {
8660     int retVal;
8661
8662     if (gameNumber > nCmailGames) {
8663         DisplayError(_("No more games in this message"), 0);
8664         return FALSE;
8665     }
8666     if (f == lastLoadGameFP) {
8667         int offset = gameNumber - lastLoadGameNumber;
8668         if (offset == 0) {
8669             cmailMsg[0] = NULLCHAR;
8670             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8671                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8672                 nCmailMovesRegistered--;
8673             }
8674             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8675             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8676                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8677             }
8678         } else {
8679             if (! RegisterMove()) return FALSE;
8680         }
8681     }
8682
8683     retVal = LoadGame(f, gameNumber, title, useList);
8684
8685     /* Make move registered during previous look at this game, if any */
8686     MakeRegisteredMove();
8687
8688     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8689         commentList[currentMove]
8690           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8691         DisplayComment(currentMove - 1, commentList[currentMove]);
8692     }
8693
8694     return retVal;
8695 }
8696
8697 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8698 int
8699 ReloadGame(offset)
8700      int offset;
8701 {
8702     int gameNumber = lastLoadGameNumber + offset;
8703     if (lastLoadGameFP == NULL) {
8704         DisplayError(_("No game has been loaded yet"), 0);
8705         return FALSE;
8706     }
8707     if (gameNumber <= 0) {
8708         DisplayError(_("Can't back up any further"), 0);
8709         return FALSE;
8710     }
8711     if (cmailMsgLoaded) {
8712         return CmailLoadGame(lastLoadGameFP, gameNumber,
8713                              lastLoadGameTitle, lastLoadGameUseList);
8714     } else {
8715         return LoadGame(lastLoadGameFP, gameNumber,
8716                         lastLoadGameTitle, lastLoadGameUseList);
8717     }
8718 }
8719
8720
8721
8722 /* Load the nth game from open file f */
8723 int
8724 LoadGame(f, gameNumber, title, useList)
8725      FILE *f;
8726      int gameNumber;
8727      char *title;
8728      int useList;
8729 {
8730     ChessMove cm;
8731     char buf[MSG_SIZ];
8732     int gn = gameNumber;
8733     ListGame *lg = NULL;
8734     int numPGNTags = 0;
8735     int err;
8736     GameMode oldGameMode;
8737     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8738
8739     if (appData.debugMode) 
8740         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8741
8742     if (gameMode == Training )
8743         SetTrainingModeOff();
8744
8745     oldGameMode = gameMode;
8746     if (gameMode != BeginningOfGame) {
8747       Reset(FALSE, TRUE);
8748     }
8749
8750     gameFileFP = f;
8751     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8752         fclose(lastLoadGameFP);
8753     }
8754
8755     if (useList) {
8756         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8757         
8758         if (lg) {
8759             fseek(f, lg->offset, 0);
8760             GameListHighlight(gameNumber);
8761             gn = 1;
8762         }
8763         else {
8764             DisplayError(_("Game number out of range"), 0);
8765             return FALSE;
8766         }
8767     } else {
8768         GameListDestroy();
8769         if (fseek(f, 0, 0) == -1) {
8770             if (f == lastLoadGameFP ?
8771                 gameNumber == lastLoadGameNumber + 1 :
8772                 gameNumber == 1) {
8773                 gn = 1;
8774             } else {
8775                 DisplayError(_("Can't seek on game file"), 0);
8776                 return FALSE;
8777             }
8778         }
8779     }
8780     lastLoadGameFP = f;
8781     lastLoadGameNumber = gameNumber;
8782     strcpy(lastLoadGameTitle, title);
8783     lastLoadGameUseList = useList;
8784
8785     yynewfile(f);
8786
8787     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8788       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8789                 lg->gameInfo.black);
8790             DisplayTitle(buf);
8791     } else if (*title != NULLCHAR) {
8792         if (gameNumber > 1) {
8793             sprintf(buf, "%s %d", title, gameNumber);
8794             DisplayTitle(buf);
8795         } else {
8796             DisplayTitle(title);
8797         }
8798     }
8799
8800     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8801         gameMode = PlayFromGameFile;
8802         ModeHighlight();
8803     }
8804
8805     currentMove = forwardMostMove = backwardMostMove = 0;
8806     CopyBoard(boards[0], initialPosition);
8807     StopClocks();
8808
8809     /*
8810      * Skip the first gn-1 games in the file.
8811      * Also skip over anything that precedes an identifiable 
8812      * start of game marker, to avoid being confused by 
8813      * garbage at the start of the file.  Currently 
8814      * recognized start of game markers are the move number "1",
8815      * the pattern "gnuchess .* game", the pattern
8816      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8817      * A game that starts with one of the latter two patterns
8818      * will also have a move number 1, possibly
8819      * following a position diagram.
8820      * 5-4-02: Let's try being more lenient and allowing a game to
8821      * start with an unnumbered move.  Does that break anything?
8822      */
8823     cm = lastLoadGameStart = (ChessMove) 0;
8824     while (gn > 0) {
8825         yyboardindex = forwardMostMove;
8826         cm = (ChessMove) yylex();
8827         switch (cm) {
8828           case (ChessMove) 0:
8829             if (cmailMsgLoaded) {
8830                 nCmailGames = CMAIL_MAX_GAMES - gn;
8831             } else {
8832                 Reset(TRUE, TRUE);
8833                 DisplayError(_("Game not found in file"), 0);
8834             }
8835             return FALSE;
8836
8837           case GNUChessGame:
8838           case XBoardGame:
8839             gn--;
8840             lastLoadGameStart = cm;
8841             break;
8842             
8843           case MoveNumberOne:
8844             switch (lastLoadGameStart) {
8845               case GNUChessGame:
8846               case XBoardGame:
8847               case PGNTag:
8848                 break;
8849               case MoveNumberOne:
8850               case (ChessMove) 0:
8851                 gn--;           /* count this game */
8852                 lastLoadGameStart = cm;
8853                 break;
8854               default:
8855                 /* impossible */
8856                 break;
8857             }
8858             break;
8859
8860           case PGNTag:
8861             switch (lastLoadGameStart) {
8862               case GNUChessGame:
8863               case PGNTag:
8864               case MoveNumberOne:
8865               case (ChessMove) 0:
8866                 gn--;           /* count this game */
8867                 lastLoadGameStart = cm;
8868                 break;
8869               case XBoardGame:
8870                 lastLoadGameStart = cm; /* game counted already */
8871                 break;
8872               default:
8873                 /* impossible */
8874                 break;
8875             }
8876             if (gn > 0) {
8877                 do {
8878                     yyboardindex = forwardMostMove;
8879                     cm = (ChessMove) yylex();
8880                 } while (cm == PGNTag || cm == Comment);
8881             }
8882             break;
8883
8884           case WhiteWins:
8885           case BlackWins:
8886           case GameIsDrawn:
8887             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8888                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8889                     != CMAIL_OLD_RESULT) {
8890                     nCmailResults ++ ;
8891                     cmailResult[  CMAIL_MAX_GAMES
8892                                 - gn - 1] = CMAIL_OLD_RESULT;
8893                 }
8894             }
8895             break;
8896
8897           case NormalMove:
8898             /* Only a NormalMove can be at the start of a game
8899              * without a position diagram. */
8900             if (lastLoadGameStart == (ChessMove) 0) {
8901               gn--;
8902               lastLoadGameStart = MoveNumberOne;
8903             }
8904             break;
8905
8906           default:
8907             break;
8908         }
8909     }
8910     
8911     if (appData.debugMode)
8912       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8913
8914     if (cm == XBoardGame) {
8915         /* Skip any header junk before position diagram and/or move 1 */
8916         for (;;) {
8917             yyboardindex = forwardMostMove;
8918             cm = (ChessMove) yylex();
8919
8920             if (cm == (ChessMove) 0 ||
8921                 cm == GNUChessGame || cm == XBoardGame) {
8922                 /* Empty game; pretend end-of-file and handle later */
8923                 cm = (ChessMove) 0;
8924                 break;
8925             }
8926
8927             if (cm == MoveNumberOne || cm == PositionDiagram ||
8928                 cm == PGNTag || cm == Comment)
8929               break;
8930         }
8931     } else if (cm == GNUChessGame) {
8932         if (gameInfo.event != NULL) {
8933             free(gameInfo.event);
8934         }
8935         gameInfo.event = StrSave(yy_text);
8936     }   
8937
8938     startedFromSetupPosition = FALSE;
8939     while (cm == PGNTag) {
8940         if (appData.debugMode) 
8941           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8942         err = ParsePGNTag(yy_text, &gameInfo);
8943         if (!err) numPGNTags++;
8944
8945         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8946         if(gameInfo.variant != oldVariant) {
8947             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8948             InitPosition(TRUE);
8949             oldVariant = gameInfo.variant;
8950             if (appData.debugMode) 
8951               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8952         }
8953
8954
8955         if (gameInfo.fen != NULL) {
8956           Board initial_position;
8957           startedFromSetupPosition = TRUE;
8958           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8959             Reset(TRUE, TRUE);
8960             DisplayError(_("Bad FEN position in file"), 0);
8961             return FALSE;
8962           }
8963           CopyBoard(boards[0], initial_position);
8964           if (blackPlaysFirst) {
8965             currentMove = forwardMostMove = backwardMostMove = 1;
8966             CopyBoard(boards[1], initial_position);
8967             strcpy(moveList[0], "");
8968             strcpy(parseList[0], "");
8969             timeRemaining[0][1] = whiteTimeRemaining;
8970             timeRemaining[1][1] = blackTimeRemaining;
8971             if (commentList[0] != NULL) {
8972               commentList[1] = commentList[0];
8973               commentList[0] = NULL;
8974             }
8975           } else {
8976             currentMove = forwardMostMove = backwardMostMove = 0;
8977           }
8978           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8979           {   int i;
8980               initialRulePlies = FENrulePlies;
8981               epStatus[forwardMostMove] = FENepStatus;
8982               for( i=0; i< nrCastlingRights; i++ )
8983                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8984           }
8985           yyboardindex = forwardMostMove;
8986           free(gameInfo.fen);
8987           gameInfo.fen = NULL;
8988         }
8989
8990         yyboardindex = forwardMostMove;
8991         cm = (ChessMove) yylex();
8992
8993         /* Handle comments interspersed among the tags */
8994         while (cm == Comment) {
8995             char *p;
8996             if (appData.debugMode) 
8997               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8998             p = yy_text;
8999             if (*p == '{' || *p == '[' || *p == '(') {
9000                 p[strlen(p) - 1] = NULLCHAR;
9001                 p++;
9002             }
9003             while (*p == '\n') p++;
9004             AppendComment(currentMove, p);
9005             yyboardindex = forwardMostMove;
9006             cm = (ChessMove) yylex();
9007         }
9008     }
9009
9010     /* don't rely on existence of Event tag since if game was
9011      * pasted from clipboard the Event tag may not exist
9012      */
9013     if (numPGNTags > 0){
9014         char *tags;
9015         if (gameInfo.variant == VariantNormal) {
9016           gameInfo.variant = StringToVariant(gameInfo.event);
9017         }
9018         if (!matchMode) {
9019           if( appData.autoDisplayTags ) {
9020             tags = PGNTags(&gameInfo);
9021             TagsPopUp(tags, CmailMsg());
9022             free(tags);
9023           }
9024         }
9025     } else {
9026         /* Make something up, but don't display it now */
9027         SetGameInfo();
9028         TagsPopDown();
9029     }
9030
9031     if (cm == PositionDiagram) {
9032         int i, j;
9033         char *p;
9034         Board initial_position;
9035
9036         if (appData.debugMode)
9037           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9038
9039         if (!startedFromSetupPosition) {
9040             p = yy_text;
9041             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9042               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9043                 switch (*p) {
9044                   case '[':
9045                   case '-':
9046                   case ' ':
9047                   case '\t':
9048                   case '\n':
9049                   case '\r':
9050                     break;
9051                   default:
9052                     initial_position[i][j++] = CharToPiece(*p);
9053                     break;
9054                 }
9055             while (*p == ' ' || *p == '\t' ||
9056                    *p == '\n' || *p == '\r') p++;
9057         
9058             if (strncmp(p, "black", strlen("black"))==0)
9059               blackPlaysFirst = TRUE;
9060             else
9061               blackPlaysFirst = FALSE;
9062             startedFromSetupPosition = TRUE;
9063         
9064             CopyBoard(boards[0], initial_position);
9065             if (blackPlaysFirst) {
9066                 currentMove = forwardMostMove = backwardMostMove = 1;
9067                 CopyBoard(boards[1], initial_position);
9068                 strcpy(moveList[0], "");
9069                 strcpy(parseList[0], "");
9070                 timeRemaining[0][1] = whiteTimeRemaining;
9071                 timeRemaining[1][1] = blackTimeRemaining;
9072                 if (commentList[0] != NULL) {
9073                     commentList[1] = commentList[0];
9074                     commentList[0] = NULL;
9075                 }
9076             } else {
9077                 currentMove = forwardMostMove = backwardMostMove = 0;
9078             }
9079         }
9080         yyboardindex = forwardMostMove;
9081         cm = (ChessMove) yylex();
9082     }
9083
9084     if (first.pr == NoProc) {
9085         StartChessProgram(&first);
9086     }
9087     InitChessProgram(&first, FALSE);
9088     SendToProgram("force\n", &first);
9089     if (startedFromSetupPosition) {
9090         SendBoard(&first, forwardMostMove);
9091     if (appData.debugMode) {
9092         fprintf(debugFP, "Load Game\n");
9093     }
9094         DisplayBothClocks();
9095     }      
9096
9097     /* [HGM] server: flag to write setup moves in broadcast file as one */
9098     loadFlag = appData.suppressLoadMoves;
9099
9100     while (cm == Comment) {
9101         char *p;
9102         if (appData.debugMode) 
9103           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9104         p = yy_text;
9105         if (*p == '{' || *p == '[' || *p == '(') {
9106             p[strlen(p) - 1] = NULLCHAR;
9107             p++;
9108         }
9109         while (*p == '\n') p++;
9110         AppendComment(currentMove, p);
9111         yyboardindex = forwardMostMove;
9112         cm = (ChessMove) yylex();
9113     }
9114
9115     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9116         cm == WhiteWins || cm == BlackWins ||
9117         cm == GameIsDrawn || cm == GameUnfinished) {
9118         DisplayMessage("", _("No moves in game"));
9119         if (cmailMsgLoaded) {
9120             if (appData.debugMode)
9121               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9122             ClearHighlights();
9123             flipView = FALSE;
9124         }
9125         DrawPosition(FALSE, boards[currentMove]);
9126         DisplayBothClocks();
9127         gameMode = EditGame;
9128         ModeHighlight();
9129         gameFileFP = NULL;
9130         cmailOldMove = 0;
9131         return TRUE;
9132     }
9133
9134     // [HGM] PV info: routine tests if comment empty
9135     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9136         DisplayComment(currentMove - 1, commentList[currentMove]);
9137     }
9138     if (!matchMode && appData.timeDelay != 0) 
9139       DrawPosition(FALSE, boards[currentMove]);
9140
9141     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9142       programStats.ok_to_send = 1;
9143     }
9144
9145     /* if the first token after the PGN tags is a move
9146      * and not move number 1, retrieve it from the parser 
9147      */
9148     if (cm != MoveNumberOne)
9149         LoadGameOneMove(cm);
9150
9151     /* load the remaining moves from the file */
9152     while (LoadGameOneMove((ChessMove)0)) {
9153       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9154       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9155     }
9156
9157     /* rewind to the start of the game */
9158     currentMove = backwardMostMove;
9159
9160     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9161
9162     if (oldGameMode == AnalyzeFile ||
9163         oldGameMode == AnalyzeMode) {
9164       AnalyzeFileEvent();
9165     }
9166
9167     if (matchMode || appData.timeDelay == 0) {
9168       ToEndEvent();
9169       gameMode = EditGame;
9170       ModeHighlight();
9171     } else if (appData.timeDelay > 0) {
9172       AutoPlayGameLoop();
9173     }
9174
9175     if (appData.debugMode) 
9176         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9177
9178     loadFlag = 0; /* [HGM] true game starts */
9179     return TRUE;
9180 }
9181
9182 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9183 int
9184 ReloadPosition(offset)
9185      int offset;
9186 {
9187     int positionNumber = lastLoadPositionNumber + offset;
9188     if (lastLoadPositionFP == NULL) {
9189         DisplayError(_("No position has been loaded yet"), 0);
9190         return FALSE;
9191     }
9192     if (positionNumber <= 0) {
9193         DisplayError(_("Can't back up any further"), 0);
9194         return FALSE;
9195     }
9196     return LoadPosition(lastLoadPositionFP, positionNumber,
9197                         lastLoadPositionTitle);
9198 }
9199
9200 /* Load the nth position from the given file */
9201 int
9202 LoadPositionFromFile(filename, n, title)
9203      char *filename;
9204      int n;
9205      char *title;
9206 {
9207     FILE *f;
9208     char buf[MSG_SIZ];
9209
9210     if (strcmp(filename, "-") == 0) {
9211         return LoadPosition(stdin, n, "stdin");
9212     } else {
9213         f = fopen(filename, "rb");
9214         if (f == NULL) {
9215             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9216             DisplayError(buf, errno);
9217             return FALSE;
9218         } else {
9219             return LoadPosition(f, n, title);
9220         }
9221     }
9222 }
9223
9224 /* Load the nth position from the given open file, and close it */
9225 int
9226 LoadPosition(f, positionNumber, title)
9227      FILE *f;
9228      int positionNumber;
9229      char *title;
9230 {
9231     char *p, line[MSG_SIZ];
9232     Board initial_position;
9233     int i, j, fenMode, pn;
9234     
9235     if (gameMode == Training )
9236         SetTrainingModeOff();
9237
9238     if (gameMode != BeginningOfGame) {
9239         Reset(FALSE, TRUE);
9240     }
9241     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9242         fclose(lastLoadPositionFP);
9243     }
9244     if (positionNumber == 0) positionNumber = 1;
9245     lastLoadPositionFP = f;
9246     lastLoadPositionNumber = positionNumber;
9247     strcpy(lastLoadPositionTitle, title);
9248     if (first.pr == NoProc) {
9249       StartChessProgram(&first);
9250       InitChessProgram(&first, FALSE);
9251     }    
9252     pn = positionNumber;
9253     if (positionNumber < 0) {
9254         /* Negative position number means to seek to that byte offset */
9255         if (fseek(f, -positionNumber, 0) == -1) {
9256             DisplayError(_("Can't seek on position file"), 0);
9257             return FALSE;
9258         };
9259         pn = 1;
9260     } else {
9261         if (fseek(f, 0, 0) == -1) {
9262             if (f == lastLoadPositionFP ?
9263                 positionNumber == lastLoadPositionNumber + 1 :
9264                 positionNumber == 1) {
9265                 pn = 1;
9266             } else {
9267                 DisplayError(_("Can't seek on position file"), 0);
9268                 return FALSE;
9269             }
9270         }
9271     }
9272     /* See if this file is FEN or old-style xboard */
9273     if (fgets(line, MSG_SIZ, f) == NULL) {
9274         DisplayError(_("Position not found in file"), 0);
9275         return FALSE;
9276     }
9277     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9278     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9279
9280     if (pn >= 2) {
9281         if (fenMode || line[0] == '#') pn--;
9282         while (pn > 0) {
9283             /* skip positions before number pn */
9284             if (fgets(line, MSG_SIZ, f) == NULL) {
9285                 Reset(TRUE, TRUE);
9286                 DisplayError(_("Position not found in file"), 0);
9287                 return FALSE;
9288             }
9289             if (fenMode || line[0] == '#') pn--;
9290         }
9291     }
9292
9293     if (fenMode) {
9294         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9295             DisplayError(_("Bad FEN position in file"), 0);
9296             return FALSE;
9297         }
9298     } else {
9299         (void) fgets(line, MSG_SIZ, f);
9300         (void) fgets(line, MSG_SIZ, f);
9301     
9302         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9303             (void) fgets(line, MSG_SIZ, f);
9304             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9305                 if (*p == ' ')
9306                   continue;
9307                 initial_position[i][j++] = CharToPiece(*p);
9308             }
9309         }
9310     
9311         blackPlaysFirst = FALSE;
9312         if (!feof(f)) {
9313             (void) fgets(line, MSG_SIZ, f);
9314             if (strncmp(line, "black", strlen("black"))==0)
9315               blackPlaysFirst = TRUE;
9316         }
9317     }
9318     startedFromSetupPosition = TRUE;
9319     
9320     SendToProgram("force\n", &first);
9321     CopyBoard(boards[0], initial_position);
9322     if (blackPlaysFirst) {
9323         currentMove = forwardMostMove = backwardMostMove = 1;
9324         strcpy(moveList[0], "");
9325         strcpy(parseList[0], "");
9326         CopyBoard(boards[1], initial_position);
9327         DisplayMessage("", _("Black to play"));
9328     } else {
9329         currentMove = forwardMostMove = backwardMostMove = 0;
9330         DisplayMessage("", _("White to play"));
9331     }
9332           /* [HGM] copy FEN attributes as well */
9333           {   int i;
9334               initialRulePlies = FENrulePlies;
9335               epStatus[forwardMostMove] = FENepStatus;
9336               for( i=0; i< nrCastlingRights; i++ )
9337                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9338           }
9339     SendBoard(&first, forwardMostMove);
9340     if (appData.debugMode) {
9341 int i, j;
9342   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9343   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9344         fprintf(debugFP, "Load Position\n");
9345     }
9346
9347     if (positionNumber > 1) {
9348         sprintf(line, "%s %d", title, positionNumber);
9349         DisplayTitle(line);
9350     } else {
9351         DisplayTitle(title);
9352     }
9353     gameMode = EditGame;
9354     ModeHighlight();
9355     ResetClocks();
9356     timeRemaining[0][1] = whiteTimeRemaining;
9357     timeRemaining[1][1] = blackTimeRemaining;
9358     DrawPosition(FALSE, boards[currentMove]);
9359    
9360     return TRUE;
9361 }
9362
9363
9364 void
9365 CopyPlayerNameIntoFileName(dest, src)
9366      char **dest, *src;
9367 {
9368     while (*src != NULLCHAR && *src != ',') {
9369         if (*src == ' ') {
9370             *(*dest)++ = '_';
9371             src++;
9372         } else {
9373             *(*dest)++ = *src++;
9374         }
9375     }
9376 }
9377
9378 char *DefaultFileName(ext)
9379      char *ext;
9380 {
9381     static char def[MSG_SIZ];
9382     char *p;
9383
9384     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9385         p = def;
9386         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9387         *p++ = '-';
9388         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9389         *p++ = '.';
9390         strcpy(p, ext);
9391     } else {
9392         def[0] = NULLCHAR;
9393     }
9394     return def;
9395 }
9396
9397 /* Save the current game to the given file */
9398 int
9399 SaveGameToFile(filename, append)
9400      char *filename;
9401      int append;
9402 {
9403     FILE *f;
9404     char buf[MSG_SIZ];
9405
9406     if (strcmp(filename, "-") == 0) {
9407         return SaveGame(stdout, 0, NULL);
9408     } else {
9409         f = fopen(filename, append ? "a" : "w");
9410         if (f == NULL) {
9411             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9412             DisplayError(buf, errno);
9413             return FALSE;
9414         } else {
9415             return SaveGame(f, 0, NULL);
9416         }
9417     }
9418 }
9419
9420 char *
9421 SavePart(str)
9422      char *str;
9423 {
9424     static char buf[MSG_SIZ];
9425     char *p;
9426     
9427     p = strchr(str, ' ');
9428     if (p == NULL) return str;
9429     strncpy(buf, str, p - str);
9430     buf[p - str] = NULLCHAR;
9431     return buf;
9432 }
9433
9434 #define PGN_MAX_LINE 75
9435
9436 #define PGN_SIDE_WHITE  0
9437 #define PGN_SIDE_BLACK  1
9438
9439 /* [AS] */
9440 static int FindFirstMoveOutOfBook( int side )
9441 {
9442     int result = -1;
9443
9444     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9445         int index = backwardMostMove;
9446         int has_book_hit = 0;
9447
9448         if( (index % 2) != side ) {
9449             index++;
9450         }
9451
9452         while( index < forwardMostMove ) {
9453             /* Check to see if engine is in book */
9454             int depth = pvInfoList[index].depth;
9455             int score = pvInfoList[index].score;
9456             int in_book = 0;
9457
9458             if( depth <= 2 ) {
9459                 in_book = 1;
9460             }
9461             else if( score == 0 && depth == 63 ) {
9462                 in_book = 1; /* Zappa */
9463             }
9464             else if( score == 2 && depth == 99 ) {
9465                 in_book = 1; /* Abrok */
9466             }
9467
9468             has_book_hit += in_book;
9469
9470             if( ! in_book ) {
9471                 result = index;
9472
9473                 break;
9474             }
9475
9476             index += 2;
9477         }
9478     }
9479
9480     return result;
9481 }
9482
9483 /* [AS] */
9484 void GetOutOfBookInfo( char * buf )
9485 {
9486     int oob[2];
9487     int i;
9488     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9489
9490     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9491     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9492
9493     *buf = '\0';
9494
9495     if( oob[0] >= 0 || oob[1] >= 0 ) {
9496         for( i=0; i<2; i++ ) {
9497             int idx = oob[i];
9498
9499             if( idx >= 0 ) {
9500                 if( i > 0 && oob[0] >= 0 ) {
9501                     strcat( buf, "   " );
9502                 }
9503
9504                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9505                 sprintf( buf+strlen(buf), "%s%.2f", 
9506                     pvInfoList[idx].score >= 0 ? "+" : "",
9507                     pvInfoList[idx].score / 100.0 );
9508             }
9509         }
9510     }
9511 }
9512
9513 /* Save game in PGN style and close the file */
9514 int
9515 SaveGamePGN(f)
9516      FILE *f;
9517 {
9518     int i, offset, linelen, newblock;
9519     time_t tm;
9520 //    char *movetext;
9521     char numtext[32];
9522     int movelen, numlen, blank;
9523     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9524
9525     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9526     
9527     tm = time((time_t *) NULL);
9528     
9529     PrintPGNTags(f, &gameInfo);
9530     
9531     if (backwardMostMove > 0 || startedFromSetupPosition) {
9532         char *fen = PositionToFEN(backwardMostMove, NULL);
9533         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9534         fprintf(f, "\n{--------------\n");
9535         PrintPosition(f, backwardMostMove);
9536         fprintf(f, "--------------}\n");
9537         free(fen);
9538     }
9539     else {
9540         /* [AS] Out of book annotation */
9541         if( appData.saveOutOfBookInfo ) {
9542             char buf[64];
9543
9544             GetOutOfBookInfo( buf );
9545
9546             if( buf[0] != '\0' ) {
9547                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9548             }
9549         }
9550
9551         fprintf(f, "\n");
9552     }
9553
9554     i = backwardMostMove;
9555     linelen = 0;
9556     newblock = TRUE;
9557
9558     while (i < forwardMostMove) {
9559         /* Print comments preceding this move */
9560         if (commentList[i] != NULL) {
9561             if (linelen > 0) fprintf(f, "\n");
9562             fprintf(f, "{\n%s}\n", commentList[i]);
9563             linelen = 0;
9564             newblock = TRUE;
9565         }
9566
9567         /* Format move number */
9568         if ((i % 2) == 0) {
9569             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9570         } else {
9571             if (newblock) {
9572                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9573             } else {
9574                 numtext[0] = NULLCHAR;
9575             }
9576         }
9577         numlen = strlen(numtext);
9578         newblock = FALSE;
9579
9580         /* Print move number */
9581         blank = linelen > 0 && numlen > 0;
9582         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9583             fprintf(f, "\n");
9584             linelen = 0;
9585             blank = 0;
9586         }
9587         if (blank) {
9588             fprintf(f, " ");
9589             linelen++;
9590         }
9591         fprintf(f, numtext);
9592         linelen += numlen;
9593
9594         /* Get move */
9595         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9596         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9597
9598         /* Print move */
9599         blank = linelen > 0 && movelen > 0;
9600         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9601             fprintf(f, "\n");
9602             linelen = 0;
9603             blank = 0;
9604         }
9605         if (blank) {
9606             fprintf(f, " ");
9607             linelen++;
9608         }
9609         fprintf(f, move_buffer);
9610         linelen += movelen;
9611
9612         /* [AS] Add PV info if present */
9613         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9614             /* [HGM] add time */
9615             char buf[MSG_SIZ]; int seconds = 0;
9616
9617             if(i >= backwardMostMove) {
9618                 if(WhiteOnMove(i))
9619                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9620                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9621                 else
9622                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9623                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9624             }
9625             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9626
9627             if( seconds <= 0) buf[0] = 0; else
9628             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9629                 seconds = (seconds + 4)/10; // round to full seconds
9630                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9631                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9632             }
9633
9634             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9635                 pvInfoList[i].score >= 0 ? "+" : "",
9636                 pvInfoList[i].score / 100.0,
9637                 pvInfoList[i].depth,
9638                 buf );
9639
9640             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9641
9642             /* Print score/depth */
9643             blank = linelen > 0 && movelen > 0;
9644             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9645                 fprintf(f, "\n");
9646                 linelen = 0;
9647                 blank = 0;
9648             }
9649             if (blank) {
9650                 fprintf(f, " ");
9651                 linelen++;
9652             }
9653             fprintf(f, move_buffer);
9654             linelen += movelen;
9655         }
9656
9657         i++;
9658     }
9659     
9660     /* Start a new line */
9661     if (linelen > 0) fprintf(f, "\n");
9662
9663     /* Print comments after last move */
9664     if (commentList[i] != NULL) {
9665         fprintf(f, "{\n%s}\n", commentList[i]);
9666     }
9667
9668     /* Print result */
9669     if (gameInfo.resultDetails != NULL &&
9670         gameInfo.resultDetails[0] != NULLCHAR) {
9671         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9672                 PGNResult(gameInfo.result));
9673     } else {
9674         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9675     }
9676
9677     fclose(f);
9678     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9679     return TRUE;
9680 }
9681
9682 /* Save game in old style and close the file */
9683 int
9684 SaveGameOldStyle(f)
9685      FILE *f;
9686 {
9687     int i, offset;
9688     time_t tm;
9689     
9690     tm = time((time_t *) NULL);
9691     
9692     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9693     PrintOpponents(f);
9694     
9695     if (backwardMostMove > 0 || startedFromSetupPosition) {
9696         fprintf(f, "\n[--------------\n");
9697         PrintPosition(f, backwardMostMove);
9698         fprintf(f, "--------------]\n");
9699     } else {
9700         fprintf(f, "\n");
9701     }
9702
9703     i = backwardMostMove;
9704     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9705
9706     while (i < forwardMostMove) {
9707         if (commentList[i] != NULL) {
9708             fprintf(f, "[%s]\n", commentList[i]);
9709         }
9710
9711         if ((i % 2) == 1) {
9712             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9713             i++;
9714         } else {
9715             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9716             i++;
9717             if (commentList[i] != NULL) {
9718                 fprintf(f, "\n");
9719                 continue;
9720             }
9721             if (i >= forwardMostMove) {
9722                 fprintf(f, "\n");
9723                 break;
9724             }
9725             fprintf(f, "%s\n", parseList[i]);
9726             i++;
9727         }
9728     }
9729     
9730     if (commentList[i] != NULL) {
9731         fprintf(f, "[%s]\n", commentList[i]);
9732     }
9733
9734     /* This isn't really the old style, but it's close enough */
9735     if (gameInfo.resultDetails != NULL &&
9736         gameInfo.resultDetails[0] != NULLCHAR) {
9737         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9738                 gameInfo.resultDetails);
9739     } else {
9740         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9741     }
9742
9743     fclose(f);
9744     return TRUE;
9745 }
9746
9747 /* Save the current game to open file f and close the file */
9748 int
9749 SaveGame(f, dummy, dummy2)
9750      FILE *f;
9751      int dummy;
9752      char *dummy2;
9753 {
9754     if (gameMode == EditPosition) EditPositionDone();
9755     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9756     if (appData.oldSaveStyle)
9757       return SaveGameOldStyle(f);
9758     else
9759       return SaveGamePGN(f);
9760 }
9761
9762 /* Save the current position to the given file */
9763 int
9764 SavePositionToFile(filename)
9765      char *filename;
9766 {
9767     FILE *f;
9768     char buf[MSG_SIZ];
9769
9770     if (strcmp(filename, "-") == 0) {
9771         return SavePosition(stdout, 0, NULL);
9772     } else {
9773         f = fopen(filename, "a");
9774         if (f == NULL) {
9775             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9776             DisplayError(buf, errno);
9777             return FALSE;
9778         } else {
9779             SavePosition(f, 0, NULL);
9780             return TRUE;
9781         }
9782     }
9783 }
9784
9785 /* Save the current position to the given open file and close the file */
9786 int
9787 SavePosition(f, dummy, dummy2)
9788      FILE *f;
9789      int dummy;
9790      char *dummy2;
9791 {
9792     time_t tm;
9793     char *fen;
9794     
9795     if (appData.oldSaveStyle) {
9796         tm = time((time_t *) NULL);
9797     
9798         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9799         PrintOpponents(f);
9800         fprintf(f, "[--------------\n");
9801         PrintPosition(f, currentMove);
9802         fprintf(f, "--------------]\n");
9803     } else {
9804         fen = PositionToFEN(currentMove, NULL);
9805         fprintf(f, "%s\n", fen);
9806         free(fen);
9807     }
9808     fclose(f);
9809     return TRUE;
9810 }
9811
9812 void
9813 ReloadCmailMsgEvent(unregister)
9814      int unregister;
9815 {
9816 #if !WIN32
9817     static char *inFilename = NULL;
9818     static char *outFilename;
9819     int i;
9820     struct stat inbuf, outbuf;
9821     int status;
9822     
9823     /* Any registered moves are unregistered if unregister is set, */
9824     /* i.e. invoked by the signal handler */
9825     if (unregister) {
9826         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9827             cmailMoveRegistered[i] = FALSE;
9828             if (cmailCommentList[i] != NULL) {
9829                 free(cmailCommentList[i]);
9830                 cmailCommentList[i] = NULL;
9831             }
9832         }
9833         nCmailMovesRegistered = 0;
9834     }
9835
9836     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9837         cmailResult[i] = CMAIL_NOT_RESULT;
9838     }
9839     nCmailResults = 0;
9840
9841     if (inFilename == NULL) {
9842         /* Because the filenames are static they only get malloced once  */
9843         /* and they never get freed                                      */
9844         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9845         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9846
9847         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9848         sprintf(outFilename, "%s.out", appData.cmailGameName);
9849     }
9850     
9851     status = stat(outFilename, &outbuf);
9852     if (status < 0) {
9853         cmailMailedMove = FALSE;
9854     } else {
9855         status = stat(inFilename, &inbuf);
9856         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9857     }
9858     
9859     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9860        counts the games, notes how each one terminated, etc.
9861        
9862        It would be nice to remove this kludge and instead gather all
9863        the information while building the game list.  (And to keep it
9864        in the game list nodes instead of having a bunch of fixed-size
9865        parallel arrays.)  Note this will require getting each game's
9866        termination from the PGN tags, as the game list builder does
9867        not process the game moves.  --mann
9868        */
9869     cmailMsgLoaded = TRUE;
9870     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9871     
9872     /* Load first game in the file or popup game menu */
9873     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9874
9875 #endif /* !WIN32 */
9876     return;
9877 }
9878
9879 int
9880 RegisterMove()
9881 {
9882     FILE *f;
9883     char string[MSG_SIZ];
9884
9885     if (   cmailMailedMove
9886         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9887         return TRUE;            /* Allow free viewing  */
9888     }
9889
9890     /* Unregister move to ensure that we don't leave RegisterMove        */
9891     /* with the move registered when the conditions for registering no   */
9892     /* longer hold                                                       */
9893     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9894         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9895         nCmailMovesRegistered --;
9896
9897         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9898           {
9899               free(cmailCommentList[lastLoadGameNumber - 1]);
9900               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9901           }
9902     }
9903
9904     if (cmailOldMove == -1) {
9905         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9906         return FALSE;
9907     }
9908
9909     if (currentMove > cmailOldMove + 1) {
9910         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9911         return FALSE;
9912     }
9913
9914     if (currentMove < cmailOldMove) {
9915         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9916         return FALSE;
9917     }
9918
9919     if (forwardMostMove > currentMove) {
9920         /* Silently truncate extra moves */
9921         TruncateGame();
9922     }
9923
9924     if (   (currentMove == cmailOldMove + 1)
9925         || (   (currentMove == cmailOldMove)
9926             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9927                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9928         if (gameInfo.result != GameUnfinished) {
9929             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9930         }
9931
9932         if (commentList[currentMove] != NULL) {
9933             cmailCommentList[lastLoadGameNumber - 1]
9934               = StrSave(commentList[currentMove]);
9935         }
9936         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9937
9938         if (appData.debugMode)
9939           fprintf(debugFP, "Saving %s for game %d\n",
9940                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9941
9942         sprintf(string,
9943                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9944         
9945         f = fopen(string, "w");
9946         if (appData.oldSaveStyle) {
9947             SaveGameOldStyle(f); /* also closes the file */
9948             
9949             sprintf(string, "%s.pos.out", appData.cmailGameName);
9950             f = fopen(string, "w");
9951             SavePosition(f, 0, NULL); /* also closes the file */
9952         } else {
9953             fprintf(f, "{--------------\n");
9954             PrintPosition(f, currentMove);
9955             fprintf(f, "--------------}\n\n");
9956             
9957             SaveGame(f, 0, NULL); /* also closes the file*/
9958         }
9959         
9960         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9961         nCmailMovesRegistered ++;
9962     } else if (nCmailGames == 1) {
9963         DisplayError(_("You have not made a move yet"), 0);
9964         return FALSE;
9965     }
9966
9967     return TRUE;
9968 }
9969
9970 void
9971 MailMoveEvent()
9972 {
9973 #if !WIN32
9974     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9975     FILE *commandOutput;
9976     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9977     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
9978     int nBuffers;
9979     int i;
9980     int archived;
9981     char *arcDir;
9982
9983     if (! cmailMsgLoaded) {
9984         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9985         return;
9986     }
9987
9988     if (nCmailGames == nCmailResults) {
9989         DisplayError(_("No unfinished games"), 0);
9990         return;
9991     }
9992
9993 #if CMAIL_PROHIBIT_REMAIL
9994     if (cmailMailedMove) {
9995         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);
9996         DisplayError(msg, 0);
9997         return;
9998     }
9999 #endif
10000
10001     if (! (cmailMailedMove || RegisterMove())) return;
10002     
10003     if (   cmailMailedMove
10004         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10005         sprintf(string, partCommandString,
10006                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10007         commandOutput = popen(string, "r");
10008
10009         if (commandOutput == NULL) {
10010             DisplayError(_("Failed to invoke cmail"), 0);
10011         } else {
10012             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10013                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10014             }
10015             if (nBuffers > 1) {
10016                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10017                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10018                 nBytes = MSG_SIZ - 1;
10019             } else {
10020                 (void) memcpy(msg, buffer, nBytes);
10021             }
10022             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10023
10024             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10025                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10026
10027                 archived = TRUE;
10028                 for (i = 0; i < nCmailGames; i ++) {
10029                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10030                         archived = FALSE;
10031                     }
10032                 }
10033                 if (   archived
10034                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10035                         != NULL)) {
10036                     sprintf(buffer, "%s/%s.%s.archive",
10037                             arcDir,
10038                             appData.cmailGameName,
10039                             gameInfo.date);
10040                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10041                     cmailMsgLoaded = FALSE;
10042                 }
10043             }
10044
10045             DisplayInformation(msg);
10046             pclose(commandOutput);
10047         }
10048     } else {
10049         if ((*cmailMsg) != '\0') {
10050             DisplayInformation(cmailMsg);
10051         }
10052     }
10053
10054     return;
10055 #endif /* !WIN32 */
10056 }
10057
10058 char *
10059 CmailMsg()
10060 {
10061 #if WIN32
10062     return NULL;
10063 #else
10064     int  prependComma = 0;
10065     char number[5];
10066     char string[MSG_SIZ];       /* Space for game-list */
10067     int  i;
10068     
10069     if (!cmailMsgLoaded) return "";
10070
10071     if (cmailMailedMove) {
10072         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10073     } else {
10074         /* Create a list of games left */
10075         sprintf(string, "[");
10076         for (i = 0; i < nCmailGames; i ++) {
10077             if (! (   cmailMoveRegistered[i]
10078                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10079                 if (prependComma) {
10080                     sprintf(number, ",%d", i + 1);
10081                 } else {
10082                     sprintf(number, "%d", i + 1);
10083                     prependComma = 1;
10084                 }
10085                 
10086                 strcat(string, number);
10087             }
10088         }
10089         strcat(string, "]");
10090
10091         if (nCmailMovesRegistered + nCmailResults == 0) {
10092             switch (nCmailGames) {
10093               case 1:
10094                 sprintf(cmailMsg,
10095                         _("Still need to make move for game\n"));
10096                 break;
10097                 
10098               case 2:
10099                 sprintf(cmailMsg,
10100                         _("Still need to make moves for both games\n"));
10101                 break;
10102                 
10103               default:
10104                 sprintf(cmailMsg,
10105                         _("Still need to make moves for all %d games\n"),
10106                         nCmailGames);
10107                 break;
10108             }
10109         } else {
10110             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10111               case 1:
10112                 sprintf(cmailMsg,
10113                         _("Still need to make a move for game %s\n"),
10114                         string);
10115                 break;
10116                 
10117               case 0:
10118                 if (nCmailResults == nCmailGames) {
10119                     sprintf(cmailMsg, _("No unfinished games\n"));
10120                 } else {
10121                     sprintf(cmailMsg, _("Ready to send mail\n"));
10122                 }
10123                 break;
10124                 
10125               default:
10126                 sprintf(cmailMsg,
10127                         _("Still need to make moves for games %s\n"),
10128                         string);
10129             }
10130         }
10131     }
10132     return cmailMsg;
10133 #endif /* WIN32 */
10134 }
10135
10136 void
10137 ResetGameEvent()
10138 {
10139     if (gameMode == Training)
10140       SetTrainingModeOff();
10141
10142     Reset(TRUE, TRUE);
10143     cmailMsgLoaded = FALSE;
10144     if (appData.icsActive) {
10145       SendToICS(ics_prefix);
10146       SendToICS("refresh\n");
10147     }
10148 }
10149
10150 void
10151 ExitEvent(status)
10152      int status;
10153 {
10154     exiting++;
10155     if (exiting > 2) {
10156       /* Give up on clean exit */
10157       exit(status);
10158     }
10159     if (exiting > 1) {
10160       /* Keep trying for clean exit */
10161       return;
10162     }
10163
10164     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10165
10166     if (telnetISR != NULL) {
10167       RemoveInputSource(telnetISR);
10168     }
10169     if (icsPR != NoProc) {
10170       DestroyChildProcess(icsPR, TRUE);
10171     }
10172
10173     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10174     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10175
10176     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10177     /* make sure this other one finishes before killing it!                  */
10178     if(endingGame) { int count = 0;
10179         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10180         while(endingGame && count++ < 10) DoSleep(1);
10181         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10182     }
10183
10184     /* Kill off chess programs */
10185     if (first.pr != NoProc) {
10186         ExitAnalyzeMode();
10187         
10188         DoSleep( appData.delayBeforeQuit );
10189         SendToProgram("quit\n", &first);
10190         DoSleep( appData.delayAfterQuit );
10191         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10192     }
10193     if (second.pr != NoProc) {
10194         DoSleep( appData.delayBeforeQuit );
10195         SendToProgram("quit\n", &second);
10196         DoSleep( appData.delayAfterQuit );
10197         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10198     }
10199     if (first.isr != NULL) {
10200         RemoveInputSource(first.isr);
10201     }
10202     if (second.isr != NULL) {
10203         RemoveInputSource(second.isr);
10204     }
10205
10206     ShutDownFrontEnd();
10207     exit(status);
10208 }
10209
10210 void
10211 PauseEvent()
10212 {
10213     if (appData.debugMode)
10214         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10215     if (pausing) {
10216         pausing = FALSE;
10217         ModeHighlight();
10218         if (gameMode == MachinePlaysWhite ||
10219             gameMode == MachinePlaysBlack) {
10220             StartClocks();
10221         } else {
10222             DisplayBothClocks();
10223         }
10224         if (gameMode == PlayFromGameFile) {
10225             if (appData.timeDelay >= 0) 
10226                 AutoPlayGameLoop();
10227         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10228             Reset(FALSE, TRUE);
10229             SendToICS(ics_prefix);
10230             SendToICS("refresh\n");
10231         } else if (currentMove < forwardMostMove) {
10232             ForwardInner(forwardMostMove);
10233         }
10234         pauseExamInvalid = FALSE;
10235     } else {
10236         switch (gameMode) {
10237           default:
10238             return;
10239           case IcsExamining:
10240             pauseExamForwardMostMove = forwardMostMove;
10241             pauseExamInvalid = FALSE;
10242             /* fall through */
10243           case IcsObserving:
10244           case IcsPlayingWhite:
10245           case IcsPlayingBlack:
10246             pausing = TRUE;
10247             ModeHighlight();
10248             return;
10249           case PlayFromGameFile:
10250             (void) StopLoadGameTimer();
10251             pausing = TRUE;
10252             ModeHighlight();
10253             break;
10254           case BeginningOfGame:
10255             if (appData.icsActive) return;
10256             /* else fall through */
10257           case MachinePlaysWhite:
10258           case MachinePlaysBlack:
10259           case TwoMachinesPlay:
10260             if (forwardMostMove == 0)
10261               return;           /* don't pause if no one has moved */
10262             if ((gameMode == MachinePlaysWhite &&
10263                  !WhiteOnMove(forwardMostMove)) ||
10264                 (gameMode == MachinePlaysBlack &&
10265                  WhiteOnMove(forwardMostMove))) {
10266                 StopClocks();
10267             }
10268             pausing = TRUE;
10269             ModeHighlight();
10270             break;
10271         }
10272     }
10273 }
10274
10275 void
10276 EditCommentEvent()
10277 {
10278     char title[MSG_SIZ];
10279
10280     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10281         strcpy(title, _("Edit comment"));
10282     } else {
10283         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10284                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10285                 parseList[currentMove - 1]);
10286     }
10287
10288     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10289 }
10290
10291
10292 void
10293 EditTagsEvent()
10294 {
10295     char *tags = PGNTags(&gameInfo);
10296     EditTagsPopUp(tags);
10297     free(tags);
10298 }
10299
10300 void
10301 AnalyzeModeEvent()
10302 {
10303     if (appData.noChessProgram || gameMode == AnalyzeMode)
10304       return;
10305
10306     if (gameMode != AnalyzeFile) {
10307         if (!appData.icsEngineAnalyze) {
10308                EditGameEvent();
10309                if (gameMode != EditGame) return;
10310         }
10311         ResurrectChessProgram();
10312         SendToProgram("analyze\n", &first);
10313         first.analyzing = TRUE;
10314         /*first.maybeThinking = TRUE;*/
10315         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10316         AnalysisPopUp(_("Analysis"),
10317                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10318     }
10319     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10320     pausing = FALSE;
10321     ModeHighlight();
10322     SetGameInfo();
10323
10324     StartAnalysisClock();
10325     GetTimeMark(&lastNodeCountTime);
10326     lastNodeCount = 0;
10327 }
10328
10329 void
10330 AnalyzeFileEvent()
10331 {
10332     if (appData.noChessProgram || gameMode == AnalyzeFile)
10333       return;
10334
10335     if (gameMode != AnalyzeMode) {
10336         EditGameEvent();
10337         if (gameMode != EditGame) return;
10338         ResurrectChessProgram();
10339         SendToProgram("analyze\n", &first);
10340         first.analyzing = TRUE;
10341         /*first.maybeThinking = TRUE;*/
10342         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10343         AnalysisPopUp(_("Analysis"),
10344                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10345     }
10346     gameMode = AnalyzeFile;
10347     pausing = FALSE;
10348     ModeHighlight();
10349     SetGameInfo();
10350
10351     StartAnalysisClock();
10352     GetTimeMark(&lastNodeCountTime);
10353     lastNodeCount = 0;
10354 }
10355
10356 void
10357 MachineWhiteEvent()
10358 {
10359     char buf[MSG_SIZ];
10360     char *bookHit = NULL;
10361
10362     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10363       return;
10364
10365
10366     if (gameMode == PlayFromGameFile || 
10367         gameMode == TwoMachinesPlay  || 
10368         gameMode == Training         || 
10369         gameMode == AnalyzeMode      || 
10370         gameMode == EndOfGame)
10371         EditGameEvent();
10372
10373     if (gameMode == EditPosition) 
10374         EditPositionDone();
10375
10376     if (!WhiteOnMove(currentMove)) {
10377         DisplayError(_("It is not White's turn"), 0);
10378         return;
10379     }
10380   
10381     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10382       ExitAnalyzeMode();
10383
10384     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10385         gameMode == AnalyzeFile)
10386         TruncateGame();
10387
10388     ResurrectChessProgram();    /* in case it isn't running */
10389     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10390         gameMode = MachinePlaysWhite;
10391         ResetClocks();
10392     } else
10393     gameMode = MachinePlaysWhite;
10394     pausing = FALSE;
10395     ModeHighlight();
10396     SetGameInfo();
10397     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10398     DisplayTitle(buf);
10399     if (first.sendName) {
10400       sprintf(buf, "name %s\n", gameInfo.black);
10401       SendToProgram(buf, &first);
10402     }
10403     if (first.sendTime) {
10404       if (first.useColors) {
10405         SendToProgram("black\n", &first); /*gnu kludge*/
10406       }
10407       SendTimeRemaining(&first, TRUE);
10408     }
10409     if (first.useColors) {
10410       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10411     }
10412     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10413     SetMachineThinkingEnables();
10414     first.maybeThinking = TRUE;
10415     StartClocks();
10416     firstMove = FALSE;
10417
10418     if (appData.autoFlipView && !flipView) {
10419       flipView = !flipView;
10420       DrawPosition(FALSE, NULL);
10421       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10422     }
10423
10424     if(bookHit) { // [HGM] book: simulate book reply
10425         static char bookMove[MSG_SIZ]; // a bit generous?
10426
10427         programStats.nodes = programStats.depth = programStats.time = 
10428         programStats.score = programStats.got_only_move = 0;
10429         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10430
10431         strcpy(bookMove, "move ");
10432         strcat(bookMove, bookHit);
10433         HandleMachineMove(bookMove, &first);
10434     }
10435 }
10436
10437 void
10438 MachineBlackEvent()
10439 {
10440     char buf[MSG_SIZ];
10441    char *bookHit = NULL;
10442
10443     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10444         return;
10445
10446
10447     if (gameMode == PlayFromGameFile || 
10448         gameMode == TwoMachinesPlay  || 
10449         gameMode == Training         || 
10450         gameMode == AnalyzeMode      || 
10451         gameMode == EndOfGame)
10452         EditGameEvent();
10453
10454     if (gameMode == EditPosition) 
10455         EditPositionDone();
10456
10457     if (WhiteOnMove(currentMove)) {
10458         DisplayError(_("It is not Black's turn"), 0);
10459         return;
10460     }
10461     
10462     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10463       ExitAnalyzeMode();
10464
10465     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10466         gameMode == AnalyzeFile)
10467         TruncateGame();
10468
10469     ResurrectChessProgram();    /* in case it isn't running */
10470     gameMode = MachinePlaysBlack;
10471     pausing = FALSE;
10472     ModeHighlight();
10473     SetGameInfo();
10474     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10475     DisplayTitle(buf);
10476     if (first.sendName) {
10477       sprintf(buf, "name %s\n", gameInfo.white);
10478       SendToProgram(buf, &first);
10479     }
10480     if (first.sendTime) {
10481       if (first.useColors) {
10482         SendToProgram("white\n", &first); /*gnu kludge*/
10483       }
10484       SendTimeRemaining(&first, FALSE);
10485     }
10486     if (first.useColors) {
10487       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10488     }
10489     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10490     SetMachineThinkingEnables();
10491     first.maybeThinking = TRUE;
10492     StartClocks();
10493
10494     if (appData.autoFlipView && flipView) {
10495       flipView = !flipView;
10496       DrawPosition(FALSE, NULL);
10497       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10498     }
10499     if(bookHit) { // [HGM] book: simulate book reply
10500         static char bookMove[MSG_SIZ]; // a bit generous?
10501
10502         programStats.nodes = programStats.depth = programStats.time = 
10503         programStats.score = programStats.got_only_move = 0;
10504         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10505
10506         strcpy(bookMove, "move ");
10507         strcat(bookMove, bookHit);
10508         HandleMachineMove(bookMove, &first);
10509     }
10510 }
10511
10512
10513 void
10514 DisplayTwoMachinesTitle()
10515 {
10516     char buf[MSG_SIZ];
10517     if (appData.matchGames > 0) {
10518         if (first.twoMachinesColor[0] == 'w') {
10519             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10520                     gameInfo.white, gameInfo.black,
10521                     first.matchWins, second.matchWins,
10522                     matchGame - 1 - (first.matchWins + second.matchWins));
10523         } else {
10524             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10525                     gameInfo.white, gameInfo.black,
10526                     second.matchWins, first.matchWins,
10527                     matchGame - 1 - (first.matchWins + second.matchWins));
10528         }
10529     } else {
10530         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10531     }
10532     DisplayTitle(buf);
10533 }
10534
10535 void
10536 TwoMachinesEvent P((void))
10537 {
10538     int i;
10539     char buf[MSG_SIZ];
10540     ChessProgramState *onmove;
10541     char *bookHit = NULL;
10542     
10543     if (appData.noChessProgram) return;
10544
10545     switch (gameMode) {
10546       case TwoMachinesPlay:
10547         return;
10548       case MachinePlaysWhite:
10549       case MachinePlaysBlack:
10550         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10551             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10552             return;
10553         }
10554         /* fall through */
10555       case BeginningOfGame:
10556       case PlayFromGameFile:
10557       case EndOfGame:
10558         EditGameEvent();
10559         if (gameMode != EditGame) return;
10560         break;
10561       case EditPosition:
10562         EditPositionDone();
10563         break;
10564       case AnalyzeMode:
10565       case AnalyzeFile:
10566         ExitAnalyzeMode();
10567         break;
10568       case EditGame:
10569       default:
10570         break;
10571     }
10572
10573     forwardMostMove = currentMove;
10574     ResurrectChessProgram();    /* in case first program isn't running */
10575
10576     if (second.pr == NULL) {
10577         StartChessProgram(&second);
10578         if (second.protocolVersion == 1) {
10579           TwoMachinesEventIfReady();
10580         } else {
10581           /* kludge: allow timeout for initial "feature" command */
10582           FreezeUI();
10583           DisplayMessage("", _("Starting second chess program"));
10584           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10585         }
10586         return;
10587     }
10588     DisplayMessage("", "");
10589     InitChessProgram(&second, FALSE);
10590     SendToProgram("force\n", &second);
10591     if (startedFromSetupPosition) {
10592         SendBoard(&second, backwardMostMove);
10593     if (appData.debugMode) {
10594         fprintf(debugFP, "Two Machines\n");
10595     }
10596     }
10597     for (i = backwardMostMove; i < forwardMostMove; i++) {
10598         SendMoveToProgram(i, &second);
10599     }
10600
10601     gameMode = TwoMachinesPlay;
10602     pausing = FALSE;
10603     ModeHighlight();
10604     SetGameInfo();
10605     DisplayTwoMachinesTitle();
10606     firstMove = TRUE;
10607     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10608         onmove = &first;
10609     } else {
10610         onmove = &second;
10611     }
10612
10613     SendToProgram(first.computerString, &first);
10614     if (first.sendName) {
10615       sprintf(buf, "name %s\n", second.tidy);
10616       SendToProgram(buf, &first);
10617     }
10618     SendToProgram(second.computerString, &second);
10619     if (second.sendName) {
10620       sprintf(buf, "name %s\n", first.tidy);
10621       SendToProgram(buf, &second);
10622     }
10623
10624     ResetClocks();
10625     if (!first.sendTime || !second.sendTime) {
10626         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10627         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10628     }
10629     if (onmove->sendTime) {
10630       if (onmove->useColors) {
10631         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10632       }
10633       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10634     }
10635     if (onmove->useColors) {
10636       SendToProgram(onmove->twoMachinesColor, onmove);
10637     }
10638     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10639 //    SendToProgram("go\n", onmove);
10640     onmove->maybeThinking = TRUE;
10641     SetMachineThinkingEnables();
10642
10643     StartClocks();
10644
10645     if(bookHit) { // [HGM] book: simulate book reply
10646         static char bookMove[MSG_SIZ]; // a bit generous?
10647
10648         programStats.nodes = programStats.depth = programStats.time = 
10649         programStats.score = programStats.got_only_move = 0;
10650         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10651
10652         strcpy(bookMove, "move ");
10653         strcat(bookMove, bookHit);
10654         HandleMachineMove(bookMove, &first);
10655     }
10656 }
10657
10658 void
10659 TrainingEvent()
10660 {
10661     if (gameMode == Training) {
10662       SetTrainingModeOff();
10663       gameMode = PlayFromGameFile;
10664       DisplayMessage("", _("Training mode off"));
10665     } else {
10666       gameMode = Training;
10667       animateTraining = appData.animate;
10668
10669       /* make sure we are not already at the end of the game */
10670       if (currentMove < forwardMostMove) {
10671         SetTrainingModeOn();
10672         DisplayMessage("", _("Training mode on"));
10673       } else {
10674         gameMode = PlayFromGameFile;
10675         DisplayError(_("Already at end of game"), 0);
10676       }
10677     }
10678     ModeHighlight();
10679 }
10680
10681 void
10682 IcsClientEvent()
10683 {
10684     if (!appData.icsActive) return;
10685     switch (gameMode) {
10686       case IcsPlayingWhite:
10687       case IcsPlayingBlack:
10688       case IcsObserving:
10689       case IcsIdle:
10690       case BeginningOfGame:
10691       case IcsExamining:
10692         return;
10693
10694       case EditGame:
10695         break;
10696
10697       case EditPosition:
10698         EditPositionDone();
10699         break;
10700
10701       case AnalyzeMode:
10702       case AnalyzeFile:
10703         ExitAnalyzeMode();
10704         break;
10705         
10706       default:
10707         EditGameEvent();
10708         break;
10709     }
10710
10711     gameMode = IcsIdle;
10712     ModeHighlight();
10713     return;
10714 }
10715
10716
10717 void
10718 EditGameEvent()
10719 {
10720     int i;
10721
10722     switch (gameMode) {
10723       case Training:
10724         SetTrainingModeOff();
10725         break;
10726       case MachinePlaysWhite:
10727       case MachinePlaysBlack:
10728       case BeginningOfGame:
10729         SendToProgram("force\n", &first);
10730         SetUserThinkingEnables();
10731         break;
10732       case PlayFromGameFile:
10733         (void) StopLoadGameTimer();
10734         if (gameFileFP != NULL) {
10735             gameFileFP = NULL;
10736         }
10737         break;
10738       case EditPosition:
10739         EditPositionDone();
10740         break;
10741       case AnalyzeMode:
10742       case AnalyzeFile:
10743         ExitAnalyzeMode();
10744         SendToProgram("force\n", &first);
10745         break;
10746       case TwoMachinesPlay:
10747         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10748         ResurrectChessProgram();
10749         SetUserThinkingEnables();
10750         break;
10751       case EndOfGame:
10752         ResurrectChessProgram();
10753         break;
10754       case IcsPlayingBlack:
10755       case IcsPlayingWhite:
10756         DisplayError(_("Warning: You are still playing a game"), 0);
10757         break;
10758       case IcsObserving:
10759         DisplayError(_("Warning: You are still observing a game"), 0);
10760         break;
10761       case IcsExamining:
10762         DisplayError(_("Warning: You are still examining a game"), 0);
10763         break;
10764       case IcsIdle:
10765         break;
10766       case EditGame:
10767       default:
10768         return;
10769     }
10770     
10771     pausing = FALSE;
10772     StopClocks();
10773     first.offeredDraw = second.offeredDraw = 0;
10774
10775     if (gameMode == PlayFromGameFile) {
10776         whiteTimeRemaining = timeRemaining[0][currentMove];
10777         blackTimeRemaining = timeRemaining[1][currentMove];
10778         DisplayTitle("");
10779     }
10780
10781     if (gameMode == MachinePlaysWhite ||
10782         gameMode == MachinePlaysBlack ||
10783         gameMode == TwoMachinesPlay ||
10784         gameMode == EndOfGame) {
10785         i = forwardMostMove;
10786         while (i > currentMove) {
10787             SendToProgram("undo\n", &first);
10788             i--;
10789         }
10790         whiteTimeRemaining = timeRemaining[0][currentMove];
10791         blackTimeRemaining = timeRemaining[1][currentMove];
10792         DisplayBothClocks();
10793         if (whiteFlag || blackFlag) {
10794             whiteFlag = blackFlag = 0;
10795         }
10796         DisplayTitle("");
10797     }           
10798     
10799     gameMode = EditGame;
10800     ModeHighlight();
10801     SetGameInfo();
10802 }
10803
10804
10805 void
10806 EditPositionEvent()
10807 {
10808     if (gameMode == EditPosition) {
10809         EditGameEvent();
10810         return;
10811     }
10812     
10813     EditGameEvent();
10814     if (gameMode != EditGame) return;
10815     
10816     gameMode = EditPosition;
10817     ModeHighlight();
10818     SetGameInfo();
10819     if (currentMove > 0)
10820       CopyBoard(boards[0], boards[currentMove]);
10821     
10822     blackPlaysFirst = !WhiteOnMove(currentMove);
10823     ResetClocks();
10824     currentMove = forwardMostMove = backwardMostMove = 0;
10825     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10826     DisplayMove(-1);
10827 }
10828
10829 void
10830 ExitAnalyzeMode()
10831 {
10832     /* [DM] icsEngineAnalyze - possible call from other functions */
10833     if (appData.icsEngineAnalyze) {
10834         appData.icsEngineAnalyze = FALSE;
10835
10836         DisplayMessage("",_("Close ICS engine analyze..."));
10837     }
10838     if (first.analysisSupport && first.analyzing) {
10839       SendToProgram("exit\n", &first);
10840       first.analyzing = FALSE;
10841     }
10842     AnalysisPopDown();
10843     thinkOutput[0] = NULLCHAR;
10844 }
10845
10846 void
10847 EditPositionDone()
10848 {
10849     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10850
10851     startedFromSetupPosition = TRUE;
10852     InitChessProgram(&first, FALSE);
10853     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10854     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10855         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10856         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10857     } else castlingRights[0][2] = -1;
10858     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10859         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10860         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10861     } else castlingRights[0][5] = -1;
10862     SendToProgram("force\n", &first);
10863     if (blackPlaysFirst) {
10864         strcpy(moveList[0], "");
10865         strcpy(parseList[0], "");
10866         currentMove = forwardMostMove = backwardMostMove = 1;
10867         CopyBoard(boards[1], boards[0]);
10868         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10869         { int i;
10870           epStatus[1] = epStatus[0];
10871           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10872         }
10873     } else {
10874         currentMove = forwardMostMove = backwardMostMove = 0;
10875     }
10876     SendBoard(&first, forwardMostMove);
10877     if (appData.debugMode) {
10878         fprintf(debugFP, "EditPosDone\n");
10879     }
10880     DisplayTitle("");
10881     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10882     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10883     gameMode = EditGame;
10884     ModeHighlight();
10885     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10886     ClearHighlights(); /* [AS] */
10887 }
10888
10889 /* Pause for `ms' milliseconds */
10890 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10891 void
10892 TimeDelay(ms)
10893      long ms;
10894 {
10895     TimeMark m1, m2;
10896
10897     GetTimeMark(&m1);
10898     do {
10899         GetTimeMark(&m2);
10900     } while (SubtractTimeMarks(&m2, &m1) < ms);
10901 }
10902
10903 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10904 void
10905 SendMultiLineToICS(buf)
10906      char *buf;
10907 {
10908     char temp[MSG_SIZ+1], *p;
10909     int len;
10910
10911     len = strlen(buf);
10912     if (len > MSG_SIZ)
10913       len = MSG_SIZ;
10914   
10915     strncpy(temp, buf, len);
10916     temp[len] = 0;
10917
10918     p = temp;
10919     while (*p) {
10920         if (*p == '\n' || *p == '\r')
10921           *p = ' ';
10922         ++p;
10923     }
10924
10925     strcat(temp, "\n");
10926     SendToICS(temp);
10927     SendToPlayer(temp, strlen(temp));
10928 }
10929
10930 void
10931 SetWhiteToPlayEvent()
10932 {
10933     if (gameMode == EditPosition) {
10934         blackPlaysFirst = FALSE;
10935         DisplayBothClocks();    /* works because currentMove is 0 */
10936     } else if (gameMode == IcsExamining) {
10937         SendToICS(ics_prefix);
10938         SendToICS("tomove white\n");
10939     }
10940 }
10941
10942 void
10943 SetBlackToPlayEvent()
10944 {
10945     if (gameMode == EditPosition) {
10946         blackPlaysFirst = TRUE;
10947         currentMove = 1;        /* kludge */
10948         DisplayBothClocks();
10949         currentMove = 0;
10950     } else if (gameMode == IcsExamining) {
10951         SendToICS(ics_prefix);
10952         SendToICS("tomove black\n");
10953     }
10954 }
10955
10956 void
10957 EditPositionMenuEvent(selection, x, y)
10958      ChessSquare selection;
10959      int x, y;
10960 {
10961     char buf[MSG_SIZ];
10962     ChessSquare piece = boards[0][y][x];
10963
10964     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10965
10966     switch (selection) {
10967       case ClearBoard:
10968         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10969             SendToICS(ics_prefix);
10970             SendToICS("bsetup clear\n");
10971         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10972             SendToICS(ics_prefix);
10973             SendToICS("clearboard\n");
10974         } else {
10975             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10976                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10977                 for (y = 0; y < BOARD_HEIGHT; y++) {
10978                     if (gameMode == IcsExamining) {
10979                         if (boards[currentMove][y][x] != EmptySquare) {
10980                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
10981                                     AAA + x, ONE + y);
10982                             SendToICS(buf);
10983                         }
10984                     } else {
10985                         boards[0][y][x] = p;
10986                     }
10987                 }
10988             }
10989         }
10990         if (gameMode == EditPosition) {
10991             DrawPosition(FALSE, boards[0]);
10992         }
10993         break;
10994
10995       case WhitePlay:
10996         SetWhiteToPlayEvent();
10997         break;
10998
10999       case BlackPlay:
11000         SetBlackToPlayEvent();
11001         break;
11002
11003       case EmptySquare:
11004         if (gameMode == IcsExamining) {
11005             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11006             SendToICS(buf);
11007         } else {
11008             boards[0][y][x] = EmptySquare;
11009             DrawPosition(FALSE, boards[0]);
11010         }
11011         break;
11012
11013       case PromotePiece:
11014         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11015            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11016             selection = (ChessSquare) (PROMOTED piece);
11017         } else if(piece == EmptySquare) selection = WhiteSilver;
11018         else selection = (ChessSquare)((int)piece - 1);
11019         goto defaultlabel;
11020
11021       case DemotePiece:
11022         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11023            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11024             selection = (ChessSquare) (DEMOTED piece);
11025         } else if(piece == EmptySquare) selection = BlackSilver;
11026         else selection = (ChessSquare)((int)piece + 1);       
11027         goto defaultlabel;
11028
11029       case WhiteQueen:
11030       case BlackQueen:
11031         if(gameInfo.variant == VariantShatranj ||
11032            gameInfo.variant == VariantXiangqi  ||
11033            gameInfo.variant == VariantCourier    )
11034             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11035         goto defaultlabel;
11036
11037       case WhiteKing:
11038       case BlackKing:
11039         if(gameInfo.variant == VariantXiangqi)
11040             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11041         if(gameInfo.variant == VariantKnightmate)
11042             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11043       default:
11044         defaultlabel:
11045         if (gameMode == IcsExamining) {
11046             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11047                     PieceToChar(selection), AAA + x, ONE + y);
11048             SendToICS(buf);
11049         } else {
11050             boards[0][y][x] = selection;
11051             DrawPosition(FALSE, boards[0]);
11052         }
11053         break;
11054     }
11055 }
11056
11057
11058 void
11059 DropMenuEvent(selection, x, y)
11060      ChessSquare selection;
11061      int x, y;
11062 {
11063     ChessMove moveType;
11064
11065     switch (gameMode) {
11066       case IcsPlayingWhite:
11067       case MachinePlaysBlack:
11068         if (!WhiteOnMove(currentMove)) {
11069             DisplayMoveError(_("It is Black's turn"));
11070             return;
11071         }
11072         moveType = WhiteDrop;
11073         break;
11074       case IcsPlayingBlack:
11075       case MachinePlaysWhite:
11076         if (WhiteOnMove(currentMove)) {
11077             DisplayMoveError(_("It is White's turn"));
11078             return;
11079         }
11080         moveType = BlackDrop;
11081         break;
11082       case EditGame:
11083         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11084         break;
11085       default:
11086         return;
11087     }
11088
11089     if (moveType == BlackDrop && selection < BlackPawn) {
11090       selection = (ChessSquare) ((int) selection
11091                                  + (int) BlackPawn - (int) WhitePawn);
11092     }
11093     if (boards[currentMove][y][x] != EmptySquare) {
11094         DisplayMoveError(_("That square is occupied"));
11095         return;
11096     }
11097
11098     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11099 }
11100
11101 void
11102 AcceptEvent()
11103 {
11104     /* Accept a pending offer of any kind from opponent */
11105     
11106     if (appData.icsActive) {
11107         SendToICS(ics_prefix);
11108         SendToICS("accept\n");
11109     } else if (cmailMsgLoaded) {
11110         if (currentMove == cmailOldMove &&
11111             commentList[cmailOldMove] != NULL &&
11112             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11113                    "Black offers a draw" : "White offers a draw")) {
11114             TruncateGame();
11115             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11116             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11117         } else {
11118             DisplayError(_("There is no pending offer on this move"), 0);
11119             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11120         }
11121     } else {
11122         /* Not used for offers from chess program */
11123     }
11124 }
11125
11126 void
11127 DeclineEvent()
11128 {
11129     /* Decline a pending offer of any kind from opponent */
11130     
11131     if (appData.icsActive) {
11132         SendToICS(ics_prefix);
11133         SendToICS("decline\n");
11134     } else if (cmailMsgLoaded) {
11135         if (currentMove == cmailOldMove &&
11136             commentList[cmailOldMove] != NULL &&
11137             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11138                    "Black offers a draw" : "White offers a draw")) {
11139 #ifdef NOTDEF
11140             AppendComment(cmailOldMove, "Draw declined");
11141             DisplayComment(cmailOldMove - 1, "Draw declined");
11142 #endif /*NOTDEF*/
11143         } else {
11144             DisplayError(_("There is no pending offer on this move"), 0);
11145         }
11146     } else {
11147         /* Not used for offers from chess program */
11148     }
11149 }
11150
11151 void
11152 RematchEvent()
11153 {
11154     /* Issue ICS rematch command */
11155     if (appData.icsActive) {
11156         SendToICS(ics_prefix);
11157         SendToICS("rematch\n");
11158     }
11159 }
11160
11161 void
11162 CallFlagEvent()
11163 {
11164     /* Call your opponent's flag (claim a win on time) */
11165     if (appData.icsActive) {
11166         SendToICS(ics_prefix);
11167         SendToICS("flag\n");
11168     } else {
11169         switch (gameMode) {
11170           default:
11171             return;
11172           case MachinePlaysWhite:
11173             if (whiteFlag) {
11174                 if (blackFlag)
11175                   GameEnds(GameIsDrawn, "Both players ran out of time",
11176                            GE_PLAYER);
11177                 else
11178                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11179             } else {
11180                 DisplayError(_("Your opponent is not out of time"), 0);
11181             }
11182             break;
11183           case MachinePlaysBlack:
11184             if (blackFlag) {
11185                 if (whiteFlag)
11186                   GameEnds(GameIsDrawn, "Both players ran out of time",
11187                            GE_PLAYER);
11188                 else
11189                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11190             } else {
11191                 DisplayError(_("Your opponent is not out of time"), 0);
11192             }
11193             break;
11194         }
11195     }
11196 }
11197
11198 void
11199 DrawEvent()
11200 {
11201     /* Offer draw or accept pending draw offer from opponent */
11202     
11203     if (appData.icsActive) {
11204         /* Note: tournament rules require draw offers to be
11205            made after you make your move but before you punch
11206            your clock.  Currently ICS doesn't let you do that;
11207            instead, you immediately punch your clock after making
11208            a move, but you can offer a draw at any time. */
11209         
11210         SendToICS(ics_prefix);
11211         SendToICS("draw\n");
11212     } else if (cmailMsgLoaded) {
11213         if (currentMove == cmailOldMove &&
11214             commentList[cmailOldMove] != NULL &&
11215             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11216                    "Black offers a draw" : "White offers a draw")) {
11217             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11218             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11219         } else if (currentMove == cmailOldMove + 1) {
11220             char *offer = WhiteOnMove(cmailOldMove) ?
11221               "White offers a draw" : "Black offers a draw";
11222             AppendComment(currentMove, offer);
11223             DisplayComment(currentMove - 1, offer);
11224             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11225         } else {
11226             DisplayError(_("You must make your move before offering a draw"), 0);
11227             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11228         }
11229     } else if (first.offeredDraw) {
11230         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11231     } else {
11232         if (first.sendDrawOffers) {
11233             SendToProgram("draw\n", &first);
11234             userOfferedDraw = TRUE;
11235         }
11236     }
11237 }
11238
11239 void
11240 AdjournEvent()
11241 {
11242     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11243     
11244     if (appData.icsActive) {
11245         SendToICS(ics_prefix);
11246         SendToICS("adjourn\n");
11247     } else {
11248         /* Currently GNU Chess doesn't offer or accept Adjourns */
11249     }
11250 }
11251
11252
11253 void
11254 AbortEvent()
11255 {
11256     /* Offer Abort or accept pending Abort offer from opponent */
11257     
11258     if (appData.icsActive) {
11259         SendToICS(ics_prefix);
11260         SendToICS("abort\n");
11261     } else {
11262         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11263     }
11264 }
11265
11266 void
11267 ResignEvent()
11268 {
11269     /* Resign.  You can do this even if it's not your turn. */
11270     
11271     if (appData.icsActive) {
11272         SendToICS(ics_prefix);
11273         SendToICS("resign\n");
11274     } else {
11275         switch (gameMode) {
11276           case MachinePlaysWhite:
11277             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11278             break;
11279           case MachinePlaysBlack:
11280             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11281             break;
11282           case EditGame:
11283             if (cmailMsgLoaded) {
11284                 TruncateGame();
11285                 if (WhiteOnMove(cmailOldMove)) {
11286                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11287                 } else {
11288                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11289                 }
11290                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11291             }
11292             break;
11293           default:
11294             break;
11295         }
11296     }
11297 }
11298
11299
11300 void
11301 StopObservingEvent()
11302 {
11303     /* Stop observing current games */
11304     SendToICS(ics_prefix);
11305     SendToICS("unobserve\n");
11306 }
11307
11308 void
11309 StopExaminingEvent()
11310 {
11311     /* Stop observing current game */
11312     SendToICS(ics_prefix);
11313     SendToICS("unexamine\n");
11314 }
11315
11316 void
11317 ForwardInner(target)
11318      int target;
11319 {
11320     int limit;
11321
11322     if (appData.debugMode)
11323         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11324                 target, currentMove, forwardMostMove);
11325
11326     if (gameMode == EditPosition)
11327       return;
11328
11329     if (gameMode == PlayFromGameFile && !pausing)
11330       PauseEvent();
11331     
11332     if (gameMode == IcsExamining && pausing)
11333       limit = pauseExamForwardMostMove;
11334     else
11335       limit = forwardMostMove;
11336     
11337     if (target > limit) target = limit;
11338
11339     if (target > 0 && moveList[target - 1][0]) {
11340         int fromX, fromY, toX, toY;
11341         toX = moveList[target - 1][2] - AAA;
11342         toY = moveList[target - 1][3] - ONE;
11343         if (moveList[target - 1][1] == '@') {
11344             if (appData.highlightLastMove) {
11345                 SetHighlights(-1, -1, toX, toY);
11346             }
11347         } else {
11348             fromX = moveList[target - 1][0] - AAA;
11349             fromY = moveList[target - 1][1] - ONE;
11350             if (target == currentMove + 1) {
11351                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11352             }
11353             if (appData.highlightLastMove) {
11354                 SetHighlights(fromX, fromY, toX, toY);
11355             }
11356         }
11357     }
11358     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11359         gameMode == Training || gameMode == PlayFromGameFile || 
11360         gameMode == AnalyzeFile) {
11361         while (currentMove < target) {
11362             SendMoveToProgram(currentMove++, &first);
11363         }
11364     } else {
11365         currentMove = target;
11366     }
11367     
11368     if (gameMode == EditGame || gameMode == EndOfGame) {
11369         whiteTimeRemaining = timeRemaining[0][currentMove];
11370         blackTimeRemaining = timeRemaining[1][currentMove];
11371     }
11372     DisplayBothClocks();
11373     DisplayMove(currentMove - 1);
11374     DrawPosition(FALSE, boards[currentMove]);
11375     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11376     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11377         DisplayComment(currentMove - 1, commentList[currentMove]);
11378     }
11379 }
11380
11381
11382 void
11383 ForwardEvent()
11384 {
11385     if (gameMode == IcsExamining && !pausing) {
11386         SendToICS(ics_prefix);
11387         SendToICS("forward\n");
11388     } else {
11389         ForwardInner(currentMove + 1);
11390     }
11391 }
11392
11393 void
11394 ToEndEvent()
11395 {
11396     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11397         /* to optimze, we temporarily turn off analysis mode while we feed
11398          * the remaining moves to the engine. Otherwise we get analysis output
11399          * after each move.
11400          */ 
11401         if (first.analysisSupport) {
11402           SendToProgram("exit\nforce\n", &first);
11403           first.analyzing = FALSE;
11404         }
11405     }
11406         
11407     if (gameMode == IcsExamining && !pausing) {
11408         SendToICS(ics_prefix);
11409         SendToICS("forward 999999\n");
11410     } else {
11411         ForwardInner(forwardMostMove);
11412     }
11413
11414     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11415         /* we have fed all the moves, so reactivate analysis mode */
11416         SendToProgram("analyze\n", &first);
11417         first.analyzing = TRUE;
11418         /*first.maybeThinking = TRUE;*/
11419         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11420     }
11421 }
11422
11423 void
11424 BackwardInner(target)
11425      int target;
11426 {
11427     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11428
11429     if (appData.debugMode)
11430         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11431                 target, currentMove, forwardMostMove);
11432
11433     if (gameMode == EditPosition) return;
11434     if (currentMove <= backwardMostMove) {
11435         ClearHighlights();
11436         DrawPosition(full_redraw, boards[currentMove]);
11437         return;
11438     }
11439     if (gameMode == PlayFromGameFile && !pausing)
11440       PauseEvent();
11441     
11442     if (moveList[target][0]) {
11443         int fromX, fromY, toX, toY;
11444         toX = moveList[target][2] - AAA;
11445         toY = moveList[target][3] - ONE;
11446         if (moveList[target][1] == '@') {
11447             if (appData.highlightLastMove) {
11448                 SetHighlights(-1, -1, toX, toY);
11449             }
11450         } else {
11451             fromX = moveList[target][0] - AAA;
11452             fromY = moveList[target][1] - ONE;
11453             if (target == currentMove - 1) {
11454                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11455             }
11456             if (appData.highlightLastMove) {
11457                 SetHighlights(fromX, fromY, toX, toY);
11458             }
11459         }
11460     }
11461     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11462         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11463         while (currentMove > target) {
11464             SendToProgram("undo\n", &first);
11465             currentMove--;
11466         }
11467     } else {
11468         currentMove = target;
11469     }
11470     
11471     if (gameMode == EditGame || gameMode == EndOfGame) {
11472         whiteTimeRemaining = timeRemaining[0][currentMove];
11473         blackTimeRemaining = timeRemaining[1][currentMove];
11474     }
11475     DisplayBothClocks();
11476     DisplayMove(currentMove - 1);
11477     DrawPosition(full_redraw, boards[currentMove]);
11478     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11479     // [HGM] PV info: routine tests if comment empty
11480     DisplayComment(currentMove - 1, commentList[currentMove]);
11481 }
11482
11483 void
11484 BackwardEvent()
11485 {
11486     if (gameMode == IcsExamining && !pausing) {
11487         SendToICS(ics_prefix);
11488         SendToICS("backward\n");
11489     } else {
11490         BackwardInner(currentMove - 1);
11491     }
11492 }
11493
11494 void
11495 ToStartEvent()
11496 {
11497     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11498         /* to optimze, we temporarily turn off analysis mode while we undo
11499          * all the moves. Otherwise we get analysis output after each undo.
11500          */ 
11501         if (first.analysisSupport) {
11502           SendToProgram("exit\nforce\n", &first);
11503           first.analyzing = FALSE;
11504         }
11505     }
11506
11507     if (gameMode == IcsExamining && !pausing) {
11508         SendToICS(ics_prefix);
11509         SendToICS("backward 999999\n");
11510     } else {
11511         BackwardInner(backwardMostMove);
11512     }
11513
11514     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11515         /* we have fed all the moves, so reactivate analysis mode */
11516         SendToProgram("analyze\n", &first);
11517         first.analyzing = TRUE;
11518         /*first.maybeThinking = TRUE;*/
11519         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11520     }
11521 }
11522
11523 void
11524 ToNrEvent(int to)
11525 {
11526   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11527   if (to >= forwardMostMove) to = forwardMostMove;
11528   if (to <= backwardMostMove) to = backwardMostMove;
11529   if (to < currentMove) {
11530     BackwardInner(to);
11531   } else {
11532     ForwardInner(to);
11533   }
11534 }
11535
11536 void
11537 RevertEvent()
11538 {
11539     if (gameMode != IcsExamining) {
11540         DisplayError(_("You are not examining a game"), 0);
11541         return;
11542     }
11543     if (pausing) {
11544         DisplayError(_("You can't revert while pausing"), 0);
11545         return;
11546     }
11547     SendToICS(ics_prefix);
11548     SendToICS("revert\n");
11549 }
11550
11551 void
11552 RetractMoveEvent()
11553 {
11554     switch (gameMode) {
11555       case MachinePlaysWhite:
11556       case MachinePlaysBlack:
11557         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11558             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11559             return;
11560         }
11561         if (forwardMostMove < 2) return;
11562         currentMove = forwardMostMove = forwardMostMove - 2;
11563         whiteTimeRemaining = timeRemaining[0][currentMove];
11564         blackTimeRemaining = timeRemaining[1][currentMove];
11565         DisplayBothClocks();
11566         DisplayMove(currentMove - 1);
11567         ClearHighlights();/*!! could figure this out*/
11568         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11569         SendToProgram("remove\n", &first);
11570         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11571         break;
11572
11573       case BeginningOfGame:
11574       default:
11575         break;
11576
11577       case IcsPlayingWhite:
11578       case IcsPlayingBlack:
11579         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11580             SendToICS(ics_prefix);
11581             SendToICS("takeback 2\n");
11582         } else {
11583             SendToICS(ics_prefix);
11584             SendToICS("takeback 1\n");
11585         }
11586         break;
11587     }
11588 }
11589
11590 void
11591 MoveNowEvent()
11592 {
11593     ChessProgramState *cps;
11594
11595     switch (gameMode) {
11596       case MachinePlaysWhite:
11597         if (!WhiteOnMove(forwardMostMove)) {
11598             DisplayError(_("It is your turn"), 0);
11599             return;
11600         }
11601         cps = &first;
11602         break;
11603       case MachinePlaysBlack:
11604         if (WhiteOnMove(forwardMostMove)) {
11605             DisplayError(_("It is your turn"), 0);
11606             return;
11607         }
11608         cps = &first;
11609         break;
11610       case TwoMachinesPlay:
11611         if (WhiteOnMove(forwardMostMove) ==
11612             (first.twoMachinesColor[0] == 'w')) {
11613             cps = &first;
11614         } else {
11615             cps = &second;
11616         }
11617         break;
11618       case BeginningOfGame:
11619       default:
11620         return;
11621     }
11622     SendToProgram("?\n", cps);
11623 }
11624
11625 void
11626 TruncateGameEvent()
11627 {
11628     EditGameEvent();
11629     if (gameMode != EditGame) return;
11630     TruncateGame();
11631 }
11632
11633 void
11634 TruncateGame()
11635 {
11636     if (forwardMostMove > currentMove) {
11637         if (gameInfo.resultDetails != NULL) {
11638             free(gameInfo.resultDetails);
11639             gameInfo.resultDetails = NULL;
11640             gameInfo.result = GameUnfinished;
11641         }
11642         forwardMostMove = currentMove;
11643         HistorySet(parseList, backwardMostMove, forwardMostMove,
11644                    currentMove-1);
11645     }
11646 }
11647
11648 void
11649 HintEvent()
11650 {
11651     if (appData.noChessProgram) return;
11652     switch (gameMode) {
11653       case MachinePlaysWhite:
11654         if (WhiteOnMove(forwardMostMove)) {
11655             DisplayError(_("Wait until your turn"), 0);
11656             return;
11657         }
11658         break;
11659       case BeginningOfGame:
11660       case MachinePlaysBlack:
11661         if (!WhiteOnMove(forwardMostMove)) {
11662             DisplayError(_("Wait until your turn"), 0);
11663             return;
11664         }
11665         break;
11666       default:
11667         DisplayError(_("No hint available"), 0);
11668         return;
11669     }
11670     SendToProgram("hint\n", &first);
11671     hintRequested = TRUE;
11672 }
11673
11674 void
11675 BookEvent()
11676 {
11677     if (appData.noChessProgram) return;
11678     switch (gameMode) {
11679       case MachinePlaysWhite:
11680         if (WhiteOnMove(forwardMostMove)) {
11681             DisplayError(_("Wait until your turn"), 0);
11682             return;
11683         }
11684         break;
11685       case BeginningOfGame:
11686       case MachinePlaysBlack:
11687         if (!WhiteOnMove(forwardMostMove)) {
11688             DisplayError(_("Wait until your turn"), 0);
11689             return;
11690         }
11691         break;
11692       case EditPosition:
11693         EditPositionDone();
11694         break;
11695       case TwoMachinesPlay:
11696         return;
11697       default:
11698         break;
11699     }
11700     SendToProgram("bk\n", &first);
11701     bookOutput[0] = NULLCHAR;
11702     bookRequested = TRUE;
11703 }
11704
11705 void
11706 AboutGameEvent()
11707 {
11708     char *tags = PGNTags(&gameInfo);
11709     TagsPopUp(tags, CmailMsg());
11710     free(tags);
11711 }
11712
11713 /* end button procedures */
11714
11715 void
11716 PrintPosition(fp, move)
11717      FILE *fp;
11718      int move;
11719 {
11720     int i, j;
11721     
11722     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11723         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11724             char c = PieceToChar(boards[move][i][j]);
11725             fputc(c == 'x' ? '.' : c, fp);
11726             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11727         }
11728     }
11729     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11730       fprintf(fp, "white to play\n");
11731     else
11732       fprintf(fp, "black to play\n");
11733 }
11734
11735 void
11736 PrintOpponents(fp)
11737      FILE *fp;
11738 {
11739     if (gameInfo.white != NULL) {
11740         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11741     } else {
11742         fprintf(fp, "\n");
11743     }
11744 }
11745
11746 /* Find last component of program's own name, using some heuristics */
11747 void
11748 TidyProgramName(prog, host, buf)
11749      char *prog, *host, buf[MSG_SIZ];
11750 {
11751     char *p, *q;
11752     int local = (strcmp(host, "localhost") == 0);
11753     while (!local && (p = strchr(prog, ';')) != NULL) {
11754         p++;
11755         while (*p == ' ') p++;
11756         prog = p;
11757     }
11758     if (*prog == '"' || *prog == '\'') {
11759         q = strchr(prog + 1, *prog);
11760     } else {
11761         q = strchr(prog, ' ');
11762     }
11763     if (q == NULL) q = prog + strlen(prog);
11764     p = q;
11765     while (p >= prog && *p != '/' && *p != '\\') p--;
11766     p++;
11767     if(p == prog && *p == '"') p++;
11768     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11769     memcpy(buf, p, q - p);
11770     buf[q - p] = NULLCHAR;
11771     if (!local) {
11772         strcat(buf, "@");
11773         strcat(buf, host);
11774     }
11775 }
11776
11777 char *
11778 TimeControlTagValue()
11779 {
11780     char buf[MSG_SIZ];
11781     if (!appData.clockMode) {
11782         strcpy(buf, "-");
11783     } else if (movesPerSession > 0) {
11784         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11785     } else if (timeIncrement == 0) {
11786         sprintf(buf, "%ld", timeControl/1000);
11787     } else {
11788         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11789     }
11790     return StrSave(buf);
11791 }
11792
11793 void
11794 SetGameInfo()
11795 {
11796     /* This routine is used only for certain modes */
11797     VariantClass v = gameInfo.variant;
11798     ClearGameInfo(&gameInfo);
11799     gameInfo.variant = v;
11800
11801     switch (gameMode) {
11802       case MachinePlaysWhite:
11803         gameInfo.event = StrSave( appData.pgnEventHeader );
11804         gameInfo.site = StrSave(HostName());
11805         gameInfo.date = PGNDate();
11806         gameInfo.round = StrSave("-");
11807         gameInfo.white = StrSave(first.tidy);
11808         gameInfo.black = StrSave(UserName());
11809         gameInfo.timeControl = TimeControlTagValue();
11810         break;
11811
11812       case MachinePlaysBlack:
11813         gameInfo.event = StrSave( appData.pgnEventHeader );
11814         gameInfo.site = StrSave(HostName());
11815         gameInfo.date = PGNDate();
11816         gameInfo.round = StrSave("-");
11817         gameInfo.white = StrSave(UserName());
11818         gameInfo.black = StrSave(first.tidy);
11819         gameInfo.timeControl = TimeControlTagValue();
11820         break;
11821
11822       case TwoMachinesPlay:
11823         gameInfo.event = StrSave( appData.pgnEventHeader );
11824         gameInfo.site = StrSave(HostName());
11825         gameInfo.date = PGNDate();
11826         if (matchGame > 0) {
11827             char buf[MSG_SIZ];
11828             sprintf(buf, "%d", matchGame);
11829             gameInfo.round = StrSave(buf);
11830         } else {
11831             gameInfo.round = StrSave("-");
11832         }
11833         if (first.twoMachinesColor[0] == 'w') {
11834             gameInfo.white = StrSave(first.tidy);
11835             gameInfo.black = StrSave(second.tidy);
11836         } else {
11837             gameInfo.white = StrSave(second.tidy);
11838             gameInfo.black = StrSave(first.tidy);
11839         }
11840         gameInfo.timeControl = TimeControlTagValue();
11841         break;
11842
11843       case EditGame:
11844         gameInfo.event = StrSave("Edited game");
11845         gameInfo.site = StrSave(HostName());
11846         gameInfo.date = PGNDate();
11847         gameInfo.round = StrSave("-");
11848         gameInfo.white = StrSave("-");
11849         gameInfo.black = StrSave("-");
11850         break;
11851
11852       case EditPosition:
11853         gameInfo.event = StrSave("Edited position");
11854         gameInfo.site = StrSave(HostName());
11855         gameInfo.date = PGNDate();
11856         gameInfo.round = StrSave("-");
11857         gameInfo.white = StrSave("-");
11858         gameInfo.black = StrSave("-");
11859         break;
11860
11861       case IcsPlayingWhite:
11862       case IcsPlayingBlack:
11863       case IcsObserving:
11864       case IcsExamining:
11865         break;
11866
11867       case PlayFromGameFile:
11868         gameInfo.event = StrSave("Game from non-PGN file");
11869         gameInfo.site = StrSave(HostName());
11870         gameInfo.date = PGNDate();
11871         gameInfo.round = StrSave("-");
11872         gameInfo.white = StrSave("?");
11873         gameInfo.black = StrSave("?");
11874         break;
11875
11876       default:
11877         break;
11878     }
11879 }
11880
11881 void
11882 ReplaceComment(index, text)
11883      int index;
11884      char *text;
11885 {
11886     int len;
11887
11888     while (*text == '\n') text++;
11889     len = strlen(text);
11890     while (len > 0 && text[len - 1] == '\n') len--;
11891
11892     if (commentList[index] != NULL)
11893       free(commentList[index]);
11894
11895     if (len == 0) {
11896         commentList[index] = NULL;
11897         return;
11898     }
11899     commentList[index] = (char *) malloc(len + 2);
11900     strncpy(commentList[index], text, len);
11901     commentList[index][len] = '\n';
11902     commentList[index][len + 1] = NULLCHAR;
11903 }
11904
11905 void
11906 CrushCRs(text)
11907      char *text;
11908 {
11909   char *p = text;
11910   char *q = text;
11911   char ch;
11912
11913   do {
11914     ch = *p++;
11915     if (ch == '\r') continue;
11916     *q++ = ch;
11917   } while (ch != '\0');
11918 }
11919
11920 void
11921 AppendComment(index, text)
11922      int index;
11923      char *text;
11924 {
11925     int oldlen, len;
11926     char *old;
11927
11928     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11929
11930     CrushCRs(text);
11931     while (*text == '\n') text++;
11932     len = strlen(text);
11933     while (len > 0 && text[len - 1] == '\n') len--;
11934
11935     if (len == 0) return;
11936
11937     if (commentList[index] != NULL) {
11938         old = commentList[index];
11939         oldlen = strlen(old);
11940         commentList[index] = (char *) malloc(oldlen + len + 2);
11941         strcpy(commentList[index], old);
11942         free(old);
11943         strncpy(&commentList[index][oldlen], text, len);
11944         commentList[index][oldlen + len] = '\n';
11945         commentList[index][oldlen + len + 1] = NULLCHAR;
11946     } else {
11947         commentList[index] = (char *) malloc(len + 2);
11948         strncpy(commentList[index], text, len);
11949         commentList[index][len] = '\n';
11950         commentList[index][len + 1] = NULLCHAR;
11951     }
11952 }
11953
11954 static char * FindStr( char * text, char * sub_text )
11955 {
11956     char * result = strstr( text, sub_text );
11957
11958     if( result != NULL ) {
11959         result += strlen( sub_text );
11960     }
11961
11962     return result;
11963 }
11964
11965 /* [AS] Try to extract PV info from PGN comment */
11966 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11967 char *GetInfoFromComment( int index, char * text )
11968 {
11969     char * sep = text;
11970
11971     if( text != NULL && index > 0 ) {
11972         int score = 0;
11973         int depth = 0;
11974         int time = -1, sec = 0, deci;
11975         char * s_eval = FindStr( text, "[%eval " );
11976         char * s_emt = FindStr( text, "[%emt " );
11977
11978         if( s_eval != NULL || s_emt != NULL ) {
11979             /* New style */
11980             char delim;
11981
11982             if( s_eval != NULL ) {
11983                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11984                     return text;
11985                 }
11986
11987                 if( delim != ']' ) {
11988                     return text;
11989                 }
11990             }
11991
11992             if( s_emt != NULL ) {
11993             }
11994         }
11995         else {
11996             /* We expect something like: [+|-]nnn.nn/dd */
11997             int score_lo = 0;
11998
11999             sep = strchr( text, '/' );
12000             if( sep == NULL || sep < (text+4) ) {
12001                 return text;
12002             }
12003
12004             time = -1; sec = -1; deci = -1;
12005             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12006                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12007                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12008                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12009                 return text;
12010             }
12011
12012             if( score_lo < 0 || score_lo >= 100 ) {
12013                 return text;
12014             }
12015
12016             if(sec >= 0) time = 600*time + 10*sec; else
12017             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12018
12019             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12020
12021             /* [HGM] PV time: now locate end of PV info */
12022             while( *++sep >= '0' && *sep <= '9'); // strip depth
12023             if(time >= 0)
12024             while( *++sep >= '0' && *sep <= '9'); // strip time
12025             if(sec >= 0)
12026             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12027             if(deci >= 0)
12028             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12029             while(*sep == ' ') sep++;
12030         }
12031
12032         if( depth <= 0 ) {
12033             return text;
12034         }
12035
12036         if( time < 0 ) {
12037             time = -1;
12038         }
12039
12040         pvInfoList[index-1].depth = depth;
12041         pvInfoList[index-1].score = score;
12042         pvInfoList[index-1].time  = 10*time; // centi-sec
12043     }
12044     return sep;
12045 }
12046
12047 void
12048 SendToProgram(message, cps)
12049      char *message;
12050      ChessProgramState *cps;
12051 {
12052     int count, outCount, error;
12053     char buf[MSG_SIZ];
12054
12055     if (cps->pr == NULL) return;
12056     Attention(cps);
12057     
12058     if (appData.debugMode) {
12059         TimeMark now;
12060         GetTimeMark(&now);
12061         fprintf(debugFP, "%ld >%-6s: %s", 
12062                 SubtractTimeMarks(&now, &programStartTime),
12063                 cps->which, message);
12064     }
12065     
12066     count = strlen(message);
12067     outCount = OutputToProcess(cps->pr, message, count, &error);
12068     if (outCount < count && !exiting 
12069                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12070         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12071         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12072             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12073                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12074                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12075             } else {
12076                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12077             }
12078             gameInfo.resultDetails = buf;
12079         }
12080         DisplayFatalError(buf, error, 1);
12081     }
12082 }
12083
12084 void
12085 ReceiveFromProgram(isr, closure, message, count, error)
12086      InputSourceRef isr;
12087      VOIDSTAR closure;
12088      char *message;
12089      int count;
12090      int error;
12091 {
12092     char *end_str;
12093     char buf[MSG_SIZ];
12094     ChessProgramState *cps = (ChessProgramState *)closure;
12095
12096     if (isr != cps->isr) return; /* Killed intentionally */
12097     if (count <= 0) {
12098         if (count == 0) {
12099             sprintf(buf,
12100                     _("Error: %s chess program (%s) exited unexpectedly"),
12101                     cps->which, cps->program);
12102         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12103                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12104                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12105                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12106                 } else {
12107                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12108                 }
12109                 gameInfo.resultDetails = buf;
12110             }
12111             RemoveInputSource(cps->isr);
12112             DisplayFatalError(buf, 0, 1);
12113         } else {
12114             sprintf(buf,
12115                     _("Error reading from %s chess program (%s)"),
12116                     cps->which, cps->program);
12117             RemoveInputSource(cps->isr);
12118
12119             /* [AS] Program is misbehaving badly... kill it */
12120             if( count == -2 ) {
12121                 DestroyChildProcess( cps->pr, 9 );
12122                 cps->pr = NoProc;
12123             }
12124
12125             DisplayFatalError(buf, error, 1);
12126         }
12127         return;
12128     }
12129     
12130     if ((end_str = strchr(message, '\r')) != NULL)
12131       *end_str = NULLCHAR;
12132     if ((end_str = strchr(message, '\n')) != NULL)
12133       *end_str = NULLCHAR;
12134     
12135     if (appData.debugMode) {
12136         TimeMark now; int print = 1;
12137         char *quote = ""; char c; int i;
12138
12139         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12140                 char start = message[0];
12141                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12142                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12143                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12144                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12145                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12146                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12147                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12148                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12149                         { quote = "# "; print = (appData.engineComments == 2); }
12150                 message[0] = start; // restore original message
12151         }
12152         if(print) {
12153                 GetTimeMark(&now);
12154                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12155                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12156                         quote,
12157                         message);
12158         }
12159     }
12160
12161     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12162     if (appData.icsEngineAnalyze) {
12163         if (strstr(message, "whisper") != NULL ||
12164              strstr(message, "kibitz") != NULL || 
12165             strstr(message, "tellics") != NULL) return;
12166     }
12167
12168     HandleMachineMove(message, cps);
12169 }
12170
12171
12172 void
12173 SendTimeControl(cps, mps, tc, inc, sd, st)
12174      ChessProgramState *cps;
12175      int mps, inc, sd, st;
12176      long tc;
12177 {
12178     char buf[MSG_SIZ];
12179     int seconds;
12180
12181     if( timeControl_2 > 0 ) {
12182         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12183             tc = timeControl_2;
12184         }
12185     }
12186     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12187     inc /= cps->timeOdds;
12188     st  /= cps->timeOdds;
12189
12190     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12191
12192     if (st > 0) {
12193       /* Set exact time per move, normally using st command */
12194       if (cps->stKludge) {
12195         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12196         seconds = st % 60;
12197         if (seconds == 0) {
12198           sprintf(buf, "level 1 %d\n", st/60);
12199         } else {
12200           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12201         }
12202       } else {
12203         sprintf(buf, "st %d\n", st);
12204       }
12205     } else {
12206       /* Set conventional or incremental time control, using level command */
12207       if (seconds == 0) {
12208         /* Note old gnuchess bug -- minutes:seconds used to not work.
12209            Fixed in later versions, but still avoid :seconds
12210            when seconds is 0. */
12211         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12212       } else {
12213         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12214                 seconds, inc/1000);
12215       }
12216     }
12217     SendToProgram(buf, cps);
12218
12219     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12220     /* Orthogonally, limit search to given depth */
12221     if (sd > 0) {
12222       if (cps->sdKludge) {
12223         sprintf(buf, "depth\n%d\n", sd);
12224       } else {
12225         sprintf(buf, "sd %d\n", sd);
12226       }
12227       SendToProgram(buf, cps);
12228     }
12229
12230     if(cps->nps > 0) { /* [HGM] nps */
12231         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12232         else {
12233                 sprintf(buf, "nps %d\n", cps->nps);
12234               SendToProgram(buf, cps);
12235         }
12236     }
12237 }
12238
12239 ChessProgramState *WhitePlayer()
12240 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12241 {
12242     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12243        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12244         return &second;
12245     return &first;
12246 }
12247
12248 void
12249 SendTimeRemaining(cps, machineWhite)
12250      ChessProgramState *cps;
12251      int /*boolean*/ machineWhite;
12252 {
12253     char message[MSG_SIZ];
12254     long time, otime;
12255
12256     /* Note: this routine must be called when the clocks are stopped
12257        or when they have *just* been set or switched; otherwise
12258        it will be off by the time since the current tick started.
12259     */
12260     if (machineWhite) {
12261         time = whiteTimeRemaining / 10;
12262         otime = blackTimeRemaining / 10;
12263     } else {
12264         time = blackTimeRemaining / 10;
12265         otime = whiteTimeRemaining / 10;
12266     }
12267     /* [HGM] translate opponent's time by time-odds factor */
12268     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12269     if (appData.debugMode) {
12270         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12271     }
12272
12273     if (time <= 0) time = 1;
12274     if (otime <= 0) otime = 1;
12275     
12276     sprintf(message, "time %ld\n", time);
12277     SendToProgram(message, cps);
12278
12279     sprintf(message, "otim %ld\n", otime);
12280     SendToProgram(message, cps);
12281 }
12282
12283 int
12284 BoolFeature(p, name, loc, cps)
12285      char **p;
12286      char *name;
12287      int *loc;
12288      ChessProgramState *cps;
12289 {
12290   char buf[MSG_SIZ];
12291   int len = strlen(name);
12292   int val;
12293   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12294     (*p) += len + 1;
12295     sscanf(*p, "%d", &val);
12296     *loc = (val != 0);
12297     while (**p && **p != ' ') (*p)++;
12298     sprintf(buf, "accepted %s\n", name);
12299     SendToProgram(buf, cps);
12300     return TRUE;
12301   }
12302   return FALSE;
12303 }
12304
12305 int
12306 IntFeature(p, name, loc, cps)
12307      char **p;
12308      char *name;
12309      int *loc;
12310      ChessProgramState *cps;
12311 {
12312   char buf[MSG_SIZ];
12313   int len = strlen(name);
12314   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12315     (*p) += len + 1;
12316     sscanf(*p, "%d", loc);
12317     while (**p && **p != ' ') (*p)++;
12318     sprintf(buf, "accepted %s\n", name);
12319     SendToProgram(buf, cps);
12320     return TRUE;
12321   }
12322   return FALSE;
12323 }
12324
12325 int
12326 StringFeature(p, name, loc, cps)
12327      char **p;
12328      char *name;
12329      char loc[];
12330      ChessProgramState *cps;
12331 {
12332   char buf[MSG_SIZ];
12333   int len = strlen(name);
12334   if (strncmp((*p), name, len) == 0
12335       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12336     (*p) += len + 2;
12337     sscanf(*p, "%[^\"]", loc);
12338     while (**p && **p != '\"') (*p)++;
12339     if (**p == '\"') (*p)++;
12340     sprintf(buf, "accepted %s\n", name);
12341     SendToProgram(buf, cps);
12342     return TRUE;
12343   }
12344   return FALSE;
12345 }
12346
12347 int 
12348 ParseOption(Option *opt, ChessProgramState *cps)
12349 // [HGM] options: process the string that defines an engine option, and determine
12350 // name, type, default value, and allowed value range
12351 {
12352         char *p, *q, buf[MSG_SIZ];
12353         int n, min = (-1)<<31, max = 1<<31, def;
12354
12355         if(p = strstr(opt->name, " -spin ")) {
12356             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12357             if(max < min) max = min; // enforce consistency
12358             if(def < min) def = min;
12359             if(def > max) def = max;
12360             opt->value = def;
12361             opt->min = min;
12362             opt->max = max;
12363             opt->type = Spin;
12364         } else if((p = strstr(opt->name, " -slider "))) {
12365             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12366             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12367             if(max < min) max = min; // enforce consistency
12368             if(def < min) def = min;
12369             if(def > max) def = max;
12370             opt->value = def;
12371             opt->min = min;
12372             opt->max = max;
12373             opt->type = Spin; // Slider;
12374         } else if((p = strstr(opt->name, " -string "))) {
12375             opt->textValue = p+9;
12376             opt->type = TextBox;
12377         } else if((p = strstr(opt->name, " -file "))) {
12378             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12379             opt->textValue = p+7;
12380             opt->type = TextBox; // FileName;
12381         } else if((p = strstr(opt->name, " -path "))) {
12382             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12383             opt->textValue = p+7;
12384             opt->type = TextBox; // PathName;
12385         } else if(p = strstr(opt->name, " -check ")) {
12386             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12387             opt->value = (def != 0);
12388             opt->type = CheckBox;
12389         } else if(p = strstr(opt->name, " -combo ")) {
12390             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12391             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12392             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12393             opt->value = n = 0;
12394             while(q = StrStr(q, " /// ")) {
12395                 n++; *q = 0;    // count choices, and null-terminate each of them
12396                 q += 5;
12397                 if(*q == '*') { // remember default, which is marked with * prefix
12398                     q++;
12399                     opt->value = n;
12400                 }
12401                 cps->comboList[cps->comboCnt++] = q;
12402             }
12403             cps->comboList[cps->comboCnt++] = NULL;
12404             opt->max = n + 1;
12405             opt->type = ComboBox;
12406         } else if(p = strstr(opt->name, " -button")) {
12407             opt->type = Button;
12408         } else if(p = strstr(opt->name, " -save")) {
12409             opt->type = SaveButton;
12410         } else return FALSE;
12411         *p = 0; // terminate option name
12412         // now look if the command-line options define a setting for this engine option.
12413         if(cps->optionSettings && cps->optionSettings[0])
12414             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12415         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12416                 sprintf(buf, "option %s", p);
12417                 if(p = strstr(buf, ",")) *p = 0;
12418                 strcat(buf, "\n");
12419                 SendToProgram(buf, cps);
12420         }
12421         return TRUE;
12422 }
12423
12424 void
12425 FeatureDone(cps, val)
12426      ChessProgramState* cps;
12427      int val;
12428 {
12429   DelayedEventCallback cb = GetDelayedEvent();
12430   if ((cb == InitBackEnd3 && cps == &first) ||
12431       (cb == TwoMachinesEventIfReady && cps == &second)) {
12432     CancelDelayedEvent();
12433     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12434   }
12435   cps->initDone = val;
12436 }
12437
12438 /* Parse feature command from engine */
12439 void
12440 ParseFeatures(args, cps)
12441      char* args;
12442      ChessProgramState *cps;  
12443 {
12444   char *p = args;
12445   char *q;
12446   int val;
12447   char buf[MSG_SIZ];
12448
12449   for (;;) {
12450     while (*p == ' ') p++;
12451     if (*p == NULLCHAR) return;
12452
12453     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12454     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12455     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12456     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12457     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12458     if (BoolFeature(&p, "reuse", &val, cps)) {
12459       /* Engine can disable reuse, but can't enable it if user said no */
12460       if (!val) cps->reuse = FALSE;
12461       continue;
12462     }
12463     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12464     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12465       if (gameMode == TwoMachinesPlay) {
12466         DisplayTwoMachinesTitle();
12467       } else {
12468         DisplayTitle("");
12469       }
12470       continue;
12471     }
12472     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12473     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12474     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12475     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12476     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12477     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12478     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12479     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12480     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12481     if (IntFeature(&p, "done", &val, cps)) {
12482       FeatureDone(cps, val);
12483       continue;
12484     }
12485     /* Added by Tord: */
12486     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12487     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12488     /* End of additions by Tord */
12489
12490     /* [HGM] added features: */
12491     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12492     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12493     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12494     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12495     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12496     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12497     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12498         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12499             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12500             SendToProgram(buf, cps);
12501             continue;
12502         }
12503         if(cps->nrOptions >= MAX_OPTIONS) {
12504             cps->nrOptions--;
12505             sprintf(buf, "%s engine has too many options\n", cps->which);
12506             DisplayError(buf, 0);
12507         }
12508         continue;
12509     }
12510     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12511     /* End of additions by HGM */
12512
12513     /* unknown feature: complain and skip */
12514     q = p;
12515     while (*q && *q != '=') q++;
12516     sprintf(buf, "rejected %.*s\n", q-p, p);
12517     SendToProgram(buf, cps);
12518     p = q;
12519     if (*p == '=') {
12520       p++;
12521       if (*p == '\"') {
12522         p++;
12523         while (*p && *p != '\"') p++;
12524         if (*p == '\"') p++;
12525       } else {
12526         while (*p && *p != ' ') p++;
12527       }
12528     }
12529   }
12530
12531 }
12532
12533 void
12534 PeriodicUpdatesEvent(newState)
12535      int newState;
12536 {
12537     if (newState == appData.periodicUpdates)
12538       return;
12539
12540     appData.periodicUpdates=newState;
12541
12542     /* Display type changes, so update it now */
12543     DisplayAnalysis();
12544
12545     /* Get the ball rolling again... */
12546     if (newState) {
12547         AnalysisPeriodicEvent(1);
12548         StartAnalysisClock();
12549     }
12550 }
12551
12552 void
12553 PonderNextMoveEvent(newState)
12554      int newState;
12555 {
12556     if (newState == appData.ponderNextMove) return;
12557     if (gameMode == EditPosition) EditPositionDone();
12558     if (newState) {
12559         SendToProgram("hard\n", &first);
12560         if (gameMode == TwoMachinesPlay) {
12561             SendToProgram("hard\n", &second);
12562         }
12563     } else {
12564         SendToProgram("easy\n", &first);
12565         thinkOutput[0] = NULLCHAR;
12566         if (gameMode == TwoMachinesPlay) {
12567             SendToProgram("easy\n", &second);
12568         }
12569     }
12570     appData.ponderNextMove = newState;
12571 }
12572
12573 void
12574 NewSettingEvent(option, command, value)
12575      char *command;
12576      int option, value;
12577 {
12578     char buf[MSG_SIZ];
12579
12580     if (gameMode == EditPosition) EditPositionDone();
12581     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12582     SendToProgram(buf, &first);
12583     if (gameMode == TwoMachinesPlay) {
12584         SendToProgram(buf, &second);
12585     }
12586 }
12587
12588 void
12589 ShowThinkingEvent()
12590 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12591 {
12592     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12593     int newState = appData.showThinking
12594         // [HGM] thinking: other features now need thinking output as well
12595         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12596     
12597     if (oldState == newState) return;
12598     oldState = newState;
12599     if (gameMode == EditPosition) EditPositionDone();
12600     if (oldState) {
12601         SendToProgram("post\n", &first);
12602         if (gameMode == TwoMachinesPlay) {
12603             SendToProgram("post\n", &second);
12604         }
12605     } else {
12606         SendToProgram("nopost\n", &first);
12607         thinkOutput[0] = NULLCHAR;
12608         if (gameMode == TwoMachinesPlay) {
12609             SendToProgram("nopost\n", &second);
12610         }
12611     }
12612 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12613 }
12614
12615 void
12616 AskQuestionEvent(title, question, replyPrefix, which)
12617      char *title; char *question; char *replyPrefix; char *which;
12618 {
12619   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12620   if (pr == NoProc) return;
12621   AskQuestion(title, question, replyPrefix, pr);
12622 }
12623
12624 void
12625 DisplayMove(moveNumber)
12626      int moveNumber;
12627 {
12628     char message[MSG_SIZ];
12629     char res[MSG_SIZ];
12630     char cpThinkOutput[MSG_SIZ];
12631
12632     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12633     
12634     if (moveNumber == forwardMostMove - 1 || 
12635         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12636
12637         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12638
12639         if (strchr(cpThinkOutput, '\n')) {
12640             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12641         }
12642     } else {
12643         *cpThinkOutput = NULLCHAR;
12644     }
12645
12646     /* [AS] Hide thinking from human user */
12647     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12648         *cpThinkOutput = NULLCHAR;
12649         if( thinkOutput[0] != NULLCHAR ) {
12650             int i;
12651
12652             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12653                 cpThinkOutput[i] = '.';
12654             }
12655             cpThinkOutput[i] = NULLCHAR;
12656             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12657         }
12658     }
12659
12660     if (moveNumber == forwardMostMove - 1 &&
12661         gameInfo.resultDetails != NULL) {
12662         if (gameInfo.resultDetails[0] == NULLCHAR) {
12663             sprintf(res, " %s", PGNResult(gameInfo.result));
12664         } else {
12665             sprintf(res, " {%s} %s",
12666                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12667         }
12668     } else {
12669         res[0] = NULLCHAR;
12670     }
12671
12672     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12673         DisplayMessage(res, cpThinkOutput);
12674     } else {
12675         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12676                 WhiteOnMove(moveNumber) ? " " : ".. ",
12677                 parseList[moveNumber], res);
12678         DisplayMessage(message, cpThinkOutput);
12679     }
12680 }
12681
12682 void
12683 DisplayAnalysisText(text)
12684      char *text;
12685 {
12686     char buf[MSG_SIZ];
12687
12688     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12689                || appData.icsEngineAnalyze) {
12690         sprintf(buf, "Analysis (%s)", first.tidy);
12691         AnalysisPopUp(buf, text);
12692     }
12693 }
12694
12695 static int
12696 only_one_move(str)
12697      char *str;
12698 {
12699     while (*str && isspace(*str)) ++str;
12700     while (*str && !isspace(*str)) ++str;
12701     if (!*str) return 1;
12702     while (*str && isspace(*str)) ++str;
12703     if (!*str) return 1;
12704     return 0;
12705 }
12706
12707 void
12708 DisplayAnalysis()
12709 {
12710     char buf[MSG_SIZ];
12711     char lst[MSG_SIZ / 2];
12712     double nps;
12713     static char *xtra[] = { "", " (--)", " (++)" };
12714     int h, m, s, cs;
12715   
12716     if (programStats.time == 0) {
12717         programStats.time = 1;
12718     }
12719   
12720     if (programStats.got_only_move) {
12721         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12722     } else {
12723         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12724
12725         nps = (u64ToDouble(programStats.nodes) /
12726              ((double)programStats.time /100.0));
12727
12728         cs = programStats.time % 100;
12729         s = programStats.time / 100;
12730         h = (s / (60*60));
12731         s = s - h*60*60;
12732         m = (s/60);
12733         s = s - m*60;
12734
12735         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12736           if (programStats.move_name[0] != NULLCHAR) {
12737             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12738                     programStats.depth,
12739                     programStats.nr_moves-programStats.moves_left,
12740                     programStats.nr_moves, programStats.move_name,
12741                     ((float)programStats.score)/100.0, lst,
12742                     only_one_move(lst)?
12743                     xtra[programStats.got_fail] : "",
12744                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12745           } else {
12746             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12747                     programStats.depth,
12748                     programStats.nr_moves-programStats.moves_left,
12749                     programStats.nr_moves, ((float)programStats.score)/100.0,
12750                     lst,
12751                     only_one_move(lst)?
12752                     xtra[programStats.got_fail] : "",
12753                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12754           }
12755         } else {
12756             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12757                     programStats.depth,
12758                     ((float)programStats.score)/100.0,
12759                     lst,
12760                     only_one_move(lst)?
12761                     xtra[programStats.got_fail] : "",
12762                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12763         }
12764     }
12765     DisplayAnalysisText(buf);
12766 }
12767
12768 void
12769 DisplayComment(moveNumber, text)
12770      int moveNumber;
12771      char *text;
12772 {
12773     char title[MSG_SIZ];
12774     char buf[8000]; // comment can be long!
12775     int score, depth;
12776
12777     if( appData.autoDisplayComment ) {
12778         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12779             strcpy(title, "Comment");
12780         } else {
12781             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12782                     WhiteOnMove(moveNumber) ? " " : ".. ",
12783                     parseList[moveNumber]);
12784         }
12785         // [HGM] PV info: display PV info together with (or as) comment
12786         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12787             if(text == NULL) text = "";                                           
12788             score = pvInfoList[moveNumber].score;
12789             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12790                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12791             text = buf;
12792         }
12793     } else title[0] = 0;
12794
12795     if (text != NULL)
12796         CommentPopUp(title, text);
12797 }
12798
12799 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12800  * might be busy thinking or pondering.  It can be omitted if your
12801  * gnuchess is configured to stop thinking immediately on any user
12802  * input.  However, that gnuchess feature depends on the FIONREAD
12803  * ioctl, which does not work properly on some flavors of Unix.
12804  */
12805 void
12806 Attention(cps)
12807      ChessProgramState *cps;
12808 {
12809 #if ATTENTION
12810     if (!cps->useSigint) return;
12811     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12812     switch (gameMode) {
12813       case MachinePlaysWhite:
12814       case MachinePlaysBlack:
12815       case TwoMachinesPlay:
12816       case IcsPlayingWhite:
12817       case IcsPlayingBlack:
12818       case AnalyzeMode:
12819       case AnalyzeFile:
12820         /* Skip if we know it isn't thinking */
12821         if (!cps->maybeThinking) return;
12822         if (appData.debugMode)
12823           fprintf(debugFP, "Interrupting %s\n", cps->which);
12824         InterruptChildProcess(cps->pr);
12825         cps->maybeThinking = FALSE;
12826         break;
12827       default:
12828         break;
12829     }
12830 #endif /*ATTENTION*/
12831 }
12832
12833 int
12834 CheckFlags()
12835 {
12836     if (whiteTimeRemaining <= 0) {
12837         if (!whiteFlag) {
12838             whiteFlag = TRUE;
12839             if (appData.icsActive) {
12840                 if (appData.autoCallFlag &&
12841                     gameMode == IcsPlayingBlack && !blackFlag) {
12842                   SendToICS(ics_prefix);
12843                   SendToICS("flag\n");
12844                 }
12845             } else {
12846                 if (blackFlag) {
12847                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12848                 } else {
12849                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12850                     if (appData.autoCallFlag) {
12851                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12852                         return TRUE;
12853                     }
12854                 }
12855             }
12856         }
12857     }
12858     if (blackTimeRemaining <= 0) {
12859         if (!blackFlag) {
12860             blackFlag = TRUE;
12861             if (appData.icsActive) {
12862                 if (appData.autoCallFlag &&
12863                     gameMode == IcsPlayingWhite && !whiteFlag) {
12864                   SendToICS(ics_prefix);
12865                   SendToICS("flag\n");
12866                 }
12867             } else {
12868                 if (whiteFlag) {
12869                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12870                 } else {
12871                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12872                     if (appData.autoCallFlag) {
12873                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12874                         return TRUE;
12875                     }
12876                 }
12877             }
12878         }
12879     }
12880     return FALSE;
12881 }
12882
12883 void
12884 CheckTimeControl()
12885 {
12886     if (!appData.clockMode || appData.icsActive ||
12887         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12888
12889     /*
12890      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12891      */
12892     if ( !WhiteOnMove(forwardMostMove) )
12893         /* White made time control */
12894         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12895         /* [HGM] time odds: correct new time quota for time odds! */
12896                                             / WhitePlayer()->timeOdds;
12897       else
12898         /* Black made time control */
12899         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12900                                             / WhitePlayer()->other->timeOdds;
12901 }
12902
12903 void
12904 DisplayBothClocks()
12905 {
12906     int wom = gameMode == EditPosition ?
12907       !blackPlaysFirst : WhiteOnMove(currentMove);
12908     DisplayWhiteClock(whiteTimeRemaining, wom);
12909     DisplayBlackClock(blackTimeRemaining, !wom);
12910 }
12911
12912
12913 /* Timekeeping seems to be a portability nightmare.  I think everyone
12914    has ftime(), but I'm really not sure, so I'm including some ifdefs
12915    to use other calls if you don't.  Clocks will be less accurate if
12916    you have neither ftime nor gettimeofday.
12917 */
12918
12919 /* VS 2008 requires the #include outside of the function */
12920 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12921 #include <sys/timeb.h>
12922 #endif
12923
12924 /* Get the current time as a TimeMark */
12925 void
12926 GetTimeMark(tm)
12927      TimeMark *tm;
12928 {
12929 #if HAVE_GETTIMEOFDAY
12930
12931     struct timeval timeVal;
12932     struct timezone timeZone;
12933
12934     gettimeofday(&timeVal, &timeZone);
12935     tm->sec = (long) timeVal.tv_sec; 
12936     tm->ms = (int) (timeVal.tv_usec / 1000L);
12937
12938 #else /*!HAVE_GETTIMEOFDAY*/
12939 #if HAVE_FTIME
12940
12941 // include <sys/timeb.h> / moved to just above start of function
12942     struct timeb timeB;
12943
12944     ftime(&timeB);
12945     tm->sec = (long) timeB.time;
12946     tm->ms = (int) timeB.millitm;
12947
12948 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12949     tm->sec = (long) time(NULL);
12950     tm->ms = 0;
12951 #endif
12952 #endif
12953 }
12954
12955 /* Return the difference in milliseconds between two
12956    time marks.  We assume the difference will fit in a long!
12957 */
12958 long
12959 SubtractTimeMarks(tm2, tm1)
12960      TimeMark *tm2, *tm1;
12961 {
12962     return 1000L*(tm2->sec - tm1->sec) +
12963            (long) (tm2->ms - tm1->ms);
12964 }
12965
12966
12967 /*
12968  * Code to manage the game clocks.
12969  *
12970  * In tournament play, black starts the clock and then white makes a move.
12971  * We give the human user a slight advantage if he is playing white---the
12972  * clocks don't run until he makes his first move, so it takes zero time.
12973  * Also, we don't account for network lag, so we could get out of sync
12974  * with GNU Chess's clock -- but then, referees are always right.  
12975  */
12976
12977 static TimeMark tickStartTM;
12978 static long intendedTickLength;
12979
12980 long
12981 NextTickLength(timeRemaining)
12982      long timeRemaining;
12983 {
12984     long nominalTickLength, nextTickLength;
12985
12986     if (timeRemaining > 0L && timeRemaining <= 10000L)
12987       nominalTickLength = 100L;
12988     else
12989       nominalTickLength = 1000L;
12990     nextTickLength = timeRemaining % nominalTickLength;
12991     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12992
12993     return nextTickLength;
12994 }
12995
12996 /* Adjust clock one minute up or down */
12997 void
12998 AdjustClock(Boolean which, int dir)
12999 {
13000     if(which) blackTimeRemaining += 60000*dir;
13001     else      whiteTimeRemaining += 60000*dir;
13002     DisplayBothClocks();
13003 }
13004
13005 /* Stop clocks and reset to a fresh time control */
13006 void
13007 ResetClocks() 
13008 {
13009     (void) StopClockTimer();
13010     if (appData.icsActive) {
13011         whiteTimeRemaining = blackTimeRemaining = 0;
13012     } else { /* [HGM] correct new time quote for time odds */
13013         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13014         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13015     }
13016     if (whiteFlag || blackFlag) {
13017         DisplayTitle("");
13018         whiteFlag = blackFlag = FALSE;
13019     }
13020     DisplayBothClocks();
13021 }
13022
13023 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13024
13025 /* Decrement running clock by amount of time that has passed */
13026 void
13027 DecrementClocks()
13028 {
13029     long timeRemaining;
13030     long lastTickLength, fudge;
13031     TimeMark now;
13032
13033     if (!appData.clockMode) return;
13034     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13035         
13036     GetTimeMark(&now);
13037
13038     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13039
13040     /* Fudge if we woke up a little too soon */
13041     fudge = intendedTickLength - lastTickLength;
13042     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13043
13044     if (WhiteOnMove(forwardMostMove)) {
13045         if(whiteNPS >= 0) lastTickLength = 0;
13046         timeRemaining = whiteTimeRemaining -= lastTickLength;
13047         DisplayWhiteClock(whiteTimeRemaining - fudge,
13048                           WhiteOnMove(currentMove));
13049     } else {
13050         if(blackNPS >= 0) lastTickLength = 0;
13051         timeRemaining = blackTimeRemaining -= lastTickLength;
13052         DisplayBlackClock(blackTimeRemaining - fudge,
13053                           !WhiteOnMove(currentMove));
13054     }
13055
13056     if (CheckFlags()) return;
13057         
13058     tickStartTM = now;
13059     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13060     StartClockTimer(intendedTickLength);
13061
13062     /* if the time remaining has fallen below the alarm threshold, sound the
13063      * alarm. if the alarm has sounded and (due to a takeback or time control
13064      * with increment) the time remaining has increased to a level above the
13065      * threshold, reset the alarm so it can sound again. 
13066      */
13067     
13068     if (appData.icsActive && appData.icsAlarm) {
13069
13070         /* make sure we are dealing with the user's clock */
13071         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13072                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13073            )) return;
13074
13075         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13076             alarmSounded = FALSE;
13077         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13078             PlayAlarmSound();
13079             alarmSounded = TRUE;
13080         }
13081     }
13082 }
13083
13084
13085 /* A player has just moved, so stop the previously running
13086    clock and (if in clock mode) start the other one.
13087    We redisplay both clocks in case we're in ICS mode, because
13088    ICS gives us an update to both clocks after every move.
13089    Note that this routine is called *after* forwardMostMove
13090    is updated, so the last fractional tick must be subtracted
13091    from the color that is *not* on move now.
13092 */
13093 void
13094 SwitchClocks()
13095 {
13096     long lastTickLength;
13097     TimeMark now;
13098     int flagged = FALSE;
13099
13100     GetTimeMark(&now);
13101
13102     if (StopClockTimer() && appData.clockMode) {
13103         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13104         if (WhiteOnMove(forwardMostMove)) {
13105             if(blackNPS >= 0) lastTickLength = 0;
13106             blackTimeRemaining -= lastTickLength;
13107            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13108 //         if(pvInfoList[forwardMostMove-1].time == -1)
13109                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13110                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13111         } else {
13112            if(whiteNPS >= 0) lastTickLength = 0;
13113            whiteTimeRemaining -= lastTickLength;
13114            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13115 //         if(pvInfoList[forwardMostMove-1].time == -1)
13116                  pvInfoList[forwardMostMove-1].time = 
13117                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13118         }
13119         flagged = CheckFlags();
13120     }
13121     CheckTimeControl();
13122
13123     if (flagged || !appData.clockMode) return;
13124
13125     switch (gameMode) {
13126       case MachinePlaysBlack:
13127       case MachinePlaysWhite:
13128       case BeginningOfGame:
13129         if (pausing) return;
13130         break;
13131
13132       case EditGame:
13133       case PlayFromGameFile:
13134       case IcsExamining:
13135         return;
13136
13137       default:
13138         break;
13139     }
13140
13141     tickStartTM = now;
13142     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13143       whiteTimeRemaining : blackTimeRemaining);
13144     StartClockTimer(intendedTickLength);
13145 }
13146         
13147
13148 /* Stop both clocks */
13149 void
13150 StopClocks()
13151 {       
13152     long lastTickLength;
13153     TimeMark now;
13154
13155     if (!StopClockTimer()) return;
13156     if (!appData.clockMode) return;
13157
13158     GetTimeMark(&now);
13159
13160     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13161     if (WhiteOnMove(forwardMostMove)) {
13162         if(whiteNPS >= 0) lastTickLength = 0;
13163         whiteTimeRemaining -= lastTickLength;
13164         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13165     } else {
13166         if(blackNPS >= 0) lastTickLength = 0;
13167         blackTimeRemaining -= lastTickLength;
13168         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13169     }
13170     CheckFlags();
13171 }
13172         
13173 /* Start clock of player on move.  Time may have been reset, so
13174    if clock is already running, stop and restart it. */
13175 void
13176 StartClocks()
13177 {
13178     (void) StopClockTimer(); /* in case it was running already */
13179     DisplayBothClocks();
13180     if (CheckFlags()) return;
13181
13182     if (!appData.clockMode) return;
13183     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13184
13185     GetTimeMark(&tickStartTM);
13186     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13187       whiteTimeRemaining : blackTimeRemaining);
13188
13189    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13190     whiteNPS = blackNPS = -1; 
13191     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13192        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13193         whiteNPS = first.nps;
13194     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13195        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13196         blackNPS = first.nps;
13197     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13198         whiteNPS = second.nps;
13199     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13200         blackNPS = second.nps;
13201     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13202
13203     StartClockTimer(intendedTickLength);
13204 }
13205
13206 char *
13207 TimeString(ms)
13208      long ms;
13209 {
13210     long second, minute, hour, day;
13211     char *sign = "";
13212     static char buf[32];
13213     
13214     if (ms > 0 && ms <= 9900) {
13215       /* convert milliseconds to tenths, rounding up */
13216       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13217
13218       sprintf(buf, " %03.1f ", tenths/10.0);
13219       return buf;
13220     }
13221
13222     /* convert milliseconds to seconds, rounding up */
13223     /* use floating point to avoid strangeness of integer division
13224        with negative dividends on many machines */
13225     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13226
13227     if (second < 0) {
13228         sign = "-";
13229         second = -second;
13230     }
13231     
13232     day = second / (60 * 60 * 24);
13233     second = second % (60 * 60 * 24);
13234     hour = second / (60 * 60);
13235     second = second % (60 * 60);
13236     minute = second / 60;
13237     second = second % 60;
13238     
13239     if (day > 0)
13240       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13241               sign, day, hour, minute, second);
13242     else if (hour > 0)
13243       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13244     else
13245       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13246     
13247     return buf;
13248 }
13249
13250
13251 /*
13252  * This is necessary because some C libraries aren't ANSI C compliant yet.
13253  */
13254 char *
13255 StrStr(string, match)
13256      char *string, *match;
13257 {
13258     int i, length;
13259     
13260     length = strlen(match);
13261     
13262     for (i = strlen(string) - length; i >= 0; i--, string++)
13263       if (!strncmp(match, string, length))
13264         return string;
13265     
13266     return NULL;
13267 }
13268
13269 char *
13270 StrCaseStr(string, match)
13271      char *string, *match;
13272 {
13273     int i, j, length;
13274     
13275     length = strlen(match);
13276     
13277     for (i = strlen(string) - length; i >= 0; i--, string++) {
13278         for (j = 0; j < length; j++) {
13279             if (ToLower(match[j]) != ToLower(string[j]))
13280               break;
13281         }
13282         if (j == length) return string;
13283     }
13284
13285     return NULL;
13286 }
13287
13288 #ifndef _amigados
13289 int
13290 StrCaseCmp(s1, s2)
13291      char *s1, *s2;
13292 {
13293     char c1, c2;
13294     
13295     for (;;) {
13296         c1 = ToLower(*s1++);
13297         c2 = ToLower(*s2++);
13298         if (c1 > c2) return 1;
13299         if (c1 < c2) return -1;
13300         if (c1 == NULLCHAR) return 0;
13301     }
13302 }
13303
13304
13305 int
13306 ToLower(c)
13307      int c;
13308 {
13309     return isupper(c) ? tolower(c) : c;
13310 }
13311
13312
13313 int
13314 ToUpper(c)
13315      int c;
13316 {
13317     return islower(c) ? toupper(c) : c;
13318 }
13319 #endif /* !_amigados    */
13320
13321 char *
13322 StrSave(s)
13323      char *s;
13324 {
13325     char *ret;
13326
13327     if ((ret = (char *) malloc(strlen(s) + 1))) {
13328         strcpy(ret, s);
13329     }
13330     return ret;
13331 }
13332
13333 char *
13334 StrSavePtr(s, savePtr)
13335      char *s, **savePtr;
13336 {
13337     if (*savePtr) {
13338         free(*savePtr);
13339     }
13340     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13341         strcpy(*savePtr, s);
13342     }
13343     return(*savePtr);
13344 }
13345
13346 char *
13347 PGNDate()
13348 {
13349     time_t clock;
13350     struct tm *tm;
13351     char buf[MSG_SIZ];
13352
13353     clock = time((time_t *)NULL);
13354     tm = localtime(&clock);
13355     sprintf(buf, "%04d.%02d.%02d",
13356             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13357     return StrSave(buf);
13358 }
13359
13360
13361 char *
13362 PositionToFEN(move, overrideCastling)
13363      int move;
13364      char *overrideCastling;
13365 {
13366     int i, j, fromX, fromY, toX, toY;
13367     int whiteToPlay;
13368     char buf[128];
13369     char *p, *q;
13370     int emptycount;
13371     ChessSquare piece;
13372
13373     whiteToPlay = (gameMode == EditPosition) ?
13374       !blackPlaysFirst : (move % 2 == 0);
13375     p = buf;
13376
13377     /* Piece placement data */
13378     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13379         emptycount = 0;
13380         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13381             if (boards[move][i][j] == EmptySquare) {
13382                 emptycount++;
13383             } else { ChessSquare piece = boards[move][i][j];
13384                 if (emptycount > 0) {
13385                     if(emptycount<10) /* [HGM] can be >= 10 */
13386                         *p++ = '0' + emptycount;
13387                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13388                     emptycount = 0;
13389                 }
13390                 if(PieceToChar(piece) == '+') {
13391                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13392                     *p++ = '+';
13393                     piece = (ChessSquare)(DEMOTED piece);
13394                 } 
13395                 *p++ = PieceToChar(piece);
13396                 if(p[-1] == '~') {
13397                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13398                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13399                     *p++ = '~';
13400                 }
13401             }
13402         }
13403         if (emptycount > 0) {
13404             if(emptycount<10) /* [HGM] can be >= 10 */
13405                 *p++ = '0' + emptycount;
13406             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13407             emptycount = 0;
13408         }
13409         *p++ = '/';
13410     }
13411     *(p - 1) = ' ';
13412
13413     /* [HGM] print Crazyhouse or Shogi holdings */
13414     if( gameInfo.holdingsWidth ) {
13415         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13416         q = p;
13417         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13418             piece = boards[move][i][BOARD_WIDTH-1];
13419             if( piece != EmptySquare )
13420               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13421                   *p++ = PieceToChar(piece);
13422         }
13423         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13424             piece = boards[move][BOARD_HEIGHT-i-1][0];
13425             if( piece != EmptySquare )
13426               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13427                   *p++ = PieceToChar(piece);
13428         }
13429
13430         if( q == p ) *p++ = '-';
13431         *p++ = ']';
13432         *p++ = ' ';
13433     }
13434
13435     /* Active color */
13436     *p++ = whiteToPlay ? 'w' : 'b';
13437     *p++ = ' ';
13438
13439   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13440     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13441   } else {
13442   if(nrCastlingRights) {
13443      q = p;
13444      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13445        /* [HGM] write directly from rights */
13446            if(castlingRights[move][2] >= 0 &&
13447               castlingRights[move][0] >= 0   )
13448                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13449            if(castlingRights[move][2] >= 0 &&
13450               castlingRights[move][1] >= 0   )
13451                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13452            if(castlingRights[move][5] >= 0 &&
13453               castlingRights[move][3] >= 0   )
13454                 *p++ = castlingRights[move][3] + AAA;
13455            if(castlingRights[move][5] >= 0 &&
13456               castlingRights[move][4] >= 0   )
13457                 *p++ = castlingRights[move][4] + AAA;
13458      } else {
13459
13460         /* [HGM] write true castling rights */
13461         if( nrCastlingRights == 6 ) {
13462             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13463                castlingRights[move][2] >= 0  ) *p++ = 'K';
13464             if(castlingRights[move][1] == BOARD_LEFT &&
13465                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13466             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13467                castlingRights[move][5] >= 0  ) *p++ = 'k';
13468             if(castlingRights[move][4] == BOARD_LEFT &&
13469                castlingRights[move][5] >= 0  ) *p++ = 'q';
13470         }
13471      }
13472      if (q == p) *p++ = '-'; /* No castling rights */
13473      *p++ = ' ';
13474   }
13475
13476   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13477      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13478     /* En passant target square */
13479     if (move > backwardMostMove) {
13480         fromX = moveList[move - 1][0] - AAA;
13481         fromY = moveList[move - 1][1] - ONE;
13482         toX = moveList[move - 1][2] - AAA;
13483         toY = moveList[move - 1][3] - ONE;
13484         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13485             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13486             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13487             fromX == toX) {
13488             /* 2-square pawn move just happened */
13489             *p++ = toX + AAA;
13490             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13491         } else {
13492             *p++ = '-';
13493         }
13494     } else {
13495         *p++ = '-';
13496     }
13497     *p++ = ' ';
13498   }
13499   }
13500
13501     /* [HGM] find reversible plies */
13502     {   int i = 0, j=move;
13503
13504         if (appData.debugMode) { int k;
13505             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13506             for(k=backwardMostMove; k<=forwardMostMove; k++)
13507                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13508
13509         }
13510
13511         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13512         if( j == backwardMostMove ) i += initialRulePlies;
13513         sprintf(p, "%d ", i);
13514         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13515     }
13516     /* Fullmove number */
13517     sprintf(p, "%d", (move / 2) + 1);
13518     
13519     return StrSave(buf);
13520 }
13521
13522 Boolean
13523 ParseFEN(board, blackPlaysFirst, fen)
13524     Board board;
13525      int *blackPlaysFirst;
13526      char *fen;
13527 {
13528     int i, j;
13529     char *p;
13530     int emptycount;
13531     ChessSquare piece;
13532
13533     p = fen;
13534
13535     /* [HGM] by default clear Crazyhouse holdings, if present */
13536     if(gameInfo.holdingsWidth) {
13537        for(i=0; i<BOARD_HEIGHT; i++) {
13538            board[i][0]             = EmptySquare; /* black holdings */
13539            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13540            board[i][1]             = (ChessSquare) 0; /* black counts */
13541            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13542        }
13543     }
13544
13545     /* Piece placement data */
13546     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13547         j = 0;
13548         for (;;) {
13549             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13550                 if (*p == '/') p++;
13551                 emptycount = gameInfo.boardWidth - j;
13552                 while (emptycount--)
13553                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13554                 break;
13555 #if(BOARD_SIZE >= 10)
13556             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13557                 p++; emptycount=10;
13558                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13559                 while (emptycount--)
13560                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13561 #endif
13562             } else if (isdigit(*p)) {
13563                 emptycount = *p++ - '0';
13564                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13565                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13566                 while (emptycount--)
13567                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13568             } else if (*p == '+' || isalpha(*p)) {
13569                 if (j >= gameInfo.boardWidth) return FALSE;
13570                 if(*p=='+') {
13571                     piece = CharToPiece(*++p);
13572                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13573                     piece = (ChessSquare) (PROMOTED piece ); p++;
13574                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13575                 } else piece = CharToPiece(*p++);
13576
13577                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13578                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13579                     piece = (ChessSquare) (PROMOTED piece);
13580                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13581                     p++;
13582                 }
13583                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13584             } else {
13585                 return FALSE;
13586             }
13587         }
13588     }
13589     while (*p == '/' || *p == ' ') p++;
13590
13591     /* [HGM] look for Crazyhouse holdings here */
13592     while(*p==' ') p++;
13593     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13594         if(*p == '[') p++;
13595         if(*p == '-' ) *p++; /* empty holdings */ else {
13596             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13597             /* if we would allow FEN reading to set board size, we would   */
13598             /* have to add holdings and shift the board read so far here   */
13599             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13600                 *p++;
13601                 if((int) piece >= (int) BlackPawn ) {
13602                     i = (int)piece - (int)BlackPawn;
13603                     i = PieceToNumber((ChessSquare)i);
13604                     if( i >= gameInfo.holdingsSize ) return FALSE;
13605                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13606                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13607                 } else {
13608                     i = (int)piece - (int)WhitePawn;
13609                     i = PieceToNumber((ChessSquare)i);
13610                     if( i >= gameInfo.holdingsSize ) return FALSE;
13611                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13612                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13613                 }
13614             }
13615         }
13616         if(*p == ']') *p++;
13617     }
13618
13619     while(*p == ' ') p++;
13620
13621     /* Active color */
13622     switch (*p++) {
13623       case 'w':
13624         *blackPlaysFirst = FALSE;
13625         break;
13626       case 'b': 
13627         *blackPlaysFirst = TRUE;
13628         break;
13629       default:
13630         return FALSE;
13631     }
13632
13633     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13634     /* return the extra info in global variiables             */
13635
13636     /* set defaults in case FEN is incomplete */
13637     FENepStatus = EP_UNKNOWN;
13638     for(i=0; i<nrCastlingRights; i++ ) {
13639         FENcastlingRights[i] =
13640             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13641     }   /* assume possible unless obviously impossible */
13642     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13643     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13644     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13645     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13646     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13647     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13648     FENrulePlies = 0;
13649
13650     while(*p==' ') p++;
13651     if(nrCastlingRights) {
13652       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13653           /* castling indicator present, so default becomes no castlings */
13654           for(i=0; i<nrCastlingRights; i++ ) {
13655                  FENcastlingRights[i] = -1;
13656           }
13657       }
13658       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13659              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13660              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13661              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13662         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13663
13664         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13665             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13666             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13667         }
13668         switch(c) {
13669           case'K':
13670               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13671               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13672               FENcastlingRights[2] = whiteKingFile;
13673               break;
13674           case'Q':
13675               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13676               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13677               FENcastlingRights[2] = whiteKingFile;
13678               break;
13679           case'k':
13680               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13681               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13682               FENcastlingRights[5] = blackKingFile;
13683               break;
13684           case'q':
13685               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13686               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13687               FENcastlingRights[5] = blackKingFile;
13688           case '-':
13689               break;
13690           default: /* FRC castlings */
13691               if(c >= 'a') { /* black rights */
13692                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13693                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13694                   if(i == BOARD_RGHT) break;
13695                   FENcastlingRights[5] = i;
13696                   c -= AAA;
13697                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13698                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13699                   if(c > i)
13700                       FENcastlingRights[3] = c;
13701                   else
13702                       FENcastlingRights[4] = c;
13703               } else { /* white rights */
13704                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13705                     if(board[0][i] == WhiteKing) break;
13706                   if(i == BOARD_RGHT) break;
13707                   FENcastlingRights[2] = i;
13708                   c -= AAA - 'a' + 'A';
13709                   if(board[0][c] >= WhiteKing) break;
13710                   if(c > i)
13711                       FENcastlingRights[0] = c;
13712                   else
13713                       FENcastlingRights[1] = c;
13714               }
13715         }
13716       }
13717     if (appData.debugMode) {
13718         fprintf(debugFP, "FEN castling rights:");
13719         for(i=0; i<nrCastlingRights; i++)
13720         fprintf(debugFP, " %d", FENcastlingRights[i]);
13721         fprintf(debugFP, "\n");
13722     }
13723
13724       while(*p==' ') p++;
13725     }
13726
13727     /* read e.p. field in games that know e.p. capture */
13728     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13729        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13730       if(*p=='-') {
13731         p++; FENepStatus = EP_NONE;
13732       } else {
13733          char c = *p++ - AAA;
13734
13735          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13736          if(*p >= '0' && *p <='9') *p++;
13737          FENepStatus = c;
13738       }
13739     }
13740
13741
13742     if(sscanf(p, "%d", &i) == 1) {
13743         FENrulePlies = i; /* 50-move ply counter */
13744         /* (The move number is still ignored)    */
13745     }
13746
13747     return TRUE;
13748 }
13749       
13750 void
13751 EditPositionPasteFEN(char *fen)
13752 {
13753   if (fen != NULL) {
13754     Board initial_position;
13755
13756     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13757       DisplayError(_("Bad FEN position in clipboard"), 0);
13758       return ;
13759     } else {
13760       int savedBlackPlaysFirst = blackPlaysFirst;
13761       EditPositionEvent();
13762       blackPlaysFirst = savedBlackPlaysFirst;
13763       CopyBoard(boards[0], initial_position);
13764           /* [HGM] copy FEN attributes as well */
13765           {   int i;
13766               initialRulePlies = FENrulePlies;
13767               epStatus[0] = FENepStatus;
13768               for( i=0; i<nrCastlingRights; i++ )
13769                   castlingRights[0][i] = FENcastlingRights[i];
13770           }
13771       EditPositionDone();
13772       DisplayBothClocks();
13773       DrawPosition(FALSE, boards[currentMove]);
13774     }
13775   }
13776 }