cleanup: removed "#if 1" statements
[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 SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
153                       int toX, int toY));
154 void HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
161                   Board board, char *castle, char *ep));
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165                    /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176                            char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178                         int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
186
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219
220 #ifdef WIN32
221        extern void ConsoleCreate();
222 #endif
223
224 ChessProgramState *WhitePlayer();
225 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
226 int VerifyDisplayMode P(());
227
228 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
229 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
230 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
231 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
232 extern char installDir[MSG_SIZ];
233
234 extern int tinyLayout, smallLayout;
235 ChessProgramStats programStats;
236 static int exiting = 0; /* [HGM] moved to top */
237 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
238 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
239 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
240 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
241 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
242 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
243 int opponentKibitzes;
244 int lastSavedGame; /* [HGM] save: ID of game */
245 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
246 extern int chatCount;
247 int chattingPartner;
248
249 /* States for ics_getting_history */
250 #define H_FALSE 0
251 #define H_REQUESTED 1
252 #define H_GOT_REQ_HEADER 2
253 #define H_GOT_UNREQ_HEADER 3
254 #define H_GETTING_MOVES 4
255 #define H_GOT_UNWANTED_HEADER 5
256
257 /* whosays values for GameEnds */
258 #define GE_ICS 0
259 #define GE_ENGINE 1
260 #define GE_PLAYER 2
261 #define GE_FILE 3
262 #define GE_XBOARD 4
263 #define GE_ENGINE1 5
264 #define GE_ENGINE2 6
265
266 /* Maximum number of games in a cmail message */
267 #define CMAIL_MAX_GAMES 20
268
269 /* Different types of move when calling RegisterMove */
270 #define CMAIL_MOVE   0
271 #define CMAIL_RESIGN 1
272 #define CMAIL_DRAW   2
273 #define CMAIL_ACCEPT 3
274
275 /* Different types of result to remember for each game */
276 #define CMAIL_NOT_RESULT 0
277 #define CMAIL_OLD_RESULT 1
278 #define CMAIL_NEW_RESULT 2
279
280 /* Telnet protocol constants */
281 #define TN_WILL 0373
282 #define TN_WONT 0374
283 #define TN_DO   0375
284 #define TN_DONT 0376
285 #define TN_IAC  0377
286 #define TN_ECHO 0001
287 #define TN_SGA  0003
288 #define TN_PORT 23
289
290 /* [AS] */
291 static char * safeStrCpy( char * dst, const char * src, size_t count )
292 {
293     assert( dst != NULL );
294     assert( src != NULL );
295     assert( count > 0 );
296
297     strncpy( dst, src, count );
298     dst[ count-1 ] = '\0';
299     return dst;
300 }
301
302 /* Some compiler can't cast u64 to double
303  * This function do the job for us:
304
305  * We use the highest bit for cast, this only
306  * works if the highest bit is not
307  * in use (This should not happen)
308  *
309  * We used this for all compiler
310  */
311 double
312 u64ToDouble(u64 value)
313 {
314   double r;
315   u64 tmp = value & u64Const(0x7fffffffffffffff);
316   r = (double)(s64)tmp;
317   if (value & u64Const(0x8000000000000000))
318        r +=  9.2233720368547758080e18; /* 2^63 */
319  return r;
320 }
321
322 /* Fake up flags for now, as we aren't keeping track of castling
323    availability yet. [HGM] Change of logic: the flag now only
324    indicates the type of castlings allowed by the rule of the game.
325    The actual rights themselves are maintained in the array
326    castlingRights, as part of the game history, and are not probed
327    by this function.
328  */
329 int
330 PosFlags(index)
331 {
332   int flags = F_ALL_CASTLE_OK;
333   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
334   switch (gameInfo.variant) {
335   case VariantSuicide:
336     flags &= ~F_ALL_CASTLE_OK;
337   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
338     flags |= F_IGNORE_CHECK;
339   case VariantLosers:
340     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
341     break;
342   case VariantAtomic:
343     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
344     break;
345   case VariantKriegspiel:
346     flags |= F_KRIEGSPIEL_CAPTURE;
347     break;
348   case VariantCapaRandom: 
349   case VariantFischeRandom:
350     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
351   case VariantNoCastle:
352   case VariantShatranj:
353   case VariantCourier:
354     flags &= ~F_ALL_CASTLE_OK;
355     break;
356   default:
357     break;
358   }
359   return flags;
360 }
361
362 FILE *gameFileFP, *debugFP;
363
364 /* 
365     [AS] Note: sometimes, the sscanf() function is used to parse the input
366     into a fixed-size buffer. Because of this, we must be prepared to
367     receive strings as long as the size of the input buffer, which is currently
368     set to 4K for Windows and 8K for the rest.
369     So, we must either allocate sufficiently large buffers here, or
370     reduce the size of the input buffer in the input reading part.
371 */
372
373 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
374 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
375 char thinkOutput1[MSG_SIZ*10];
376
377 ChessProgramState first, second;
378
379 /* premove variables */
380 int premoveToX = 0;
381 int premoveToY = 0;
382 int premoveFromX = 0;
383 int premoveFromY = 0;
384 int premovePromoChar = 0;
385 int gotPremove = 0;
386 Boolean alarmSounded;
387 /* end premove variables */
388
389 char *ics_prefix = "$";
390 int ics_type = ICS_GENERIC;
391
392 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
393 int pauseExamForwardMostMove = 0;
394 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
395 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
396 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
397 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
398 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
399 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
400 int whiteFlag = FALSE, blackFlag = FALSE;
401 int userOfferedDraw = FALSE;
402 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
403 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
404 int cmailMoveType[CMAIL_MAX_GAMES];
405 long ics_clock_paused = 0;
406 ProcRef icsPR = NoProc, cmailPR = NoProc;
407 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
408 GameMode gameMode = BeginningOfGame;
409 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
410 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
411 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
412 int hiddenThinkOutputState = 0; /* [AS] */
413 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
414 int adjudicateLossPlies = 6;
415 char white_holding[64], black_holding[64];
416 TimeMark lastNodeCountTime;
417 long lastNodeCount=0;
418 int have_sent_ICS_logon = 0;
419 int movesPerSession;
420 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
421 long timeControl_2; /* [AS] Allow separate time controls */
422 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
423 long timeRemaining[2][MAX_MOVES];
424 int matchGame = 0;
425 TimeMark programStartTime;
426 char ics_handle[MSG_SIZ];
427 int have_set_title = 0;
428
429 /* animateTraining preserves the state of appData.animate
430  * when Training mode is activated. This allows the
431  * response to be animated when appData.animate == TRUE and
432  * appData.animateDragging == TRUE.
433  */
434 Boolean animateTraining;
435
436 GameInfo gameInfo;
437
438 AppData appData;
439
440 Board boards[MAX_MOVES];
441 /* [HGM] Following 7 needed for accurate legality tests: */
442 signed char  epStatus[MAX_MOVES];
443 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
444 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
445 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
446 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int   initialRulePlies, FENrulePlies;
448 char  FENepStatus;
449 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
450 int loadFlag = 0; 
451 int shuffleOpenings;
452 int mute; // mute all sounds
453
454 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
455     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
456         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
457     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
458         BlackKing, BlackBishop, BlackKnight, BlackRook }
459 };
460
461 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
462     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
463         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
464     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
465         BlackKing, BlackKing, BlackKnight, BlackRook }
466 };
467
468 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
469     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
470         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
471     { BlackRook, BlackMan, BlackBishop, BlackQueen,
472         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
473 };
474
475 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
476     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
477         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
478     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
479         BlackKing, BlackBishop, BlackKnight, BlackRook }
480 };
481
482 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
483     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
484         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
485     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
486         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
487 };
488
489
490 #if (BOARD_SIZE>=10)
491 ChessSquare ShogiArray[2][BOARD_SIZE] = {
492     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
493         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
494     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
495         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
496 };
497
498 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
499     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
500         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
502         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 };
504
505 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
506     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
507         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
509         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
510 };
511
512 ChessSquare GreatArray[2][BOARD_SIZE] = {
513     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
514         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
515     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
516         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
517 };
518
519 ChessSquare JanusArray[2][BOARD_SIZE] = {
520     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
521         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
522     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
523         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
524 };
525
526 #ifdef GOTHIC
527 ChessSquare GothicArray[2][BOARD_SIZE] = {
528     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
529         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
530     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
531         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
532 };
533 #else // !GOTHIC
534 #define GothicArray CapablancaArray
535 #endif // !GOTHIC
536
537 #ifdef FALCON
538 ChessSquare FalconArray[2][BOARD_SIZE] = {
539     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
540         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
541     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
542         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
543 };
544 #else // !FALCON
545 #define FalconArray CapablancaArray
546 #endif // !FALCON
547
548 #else // !(BOARD_SIZE>=10)
549 #define XiangqiPosition FIDEArray
550 #define CapablancaArray FIDEArray
551 #define GothicArray FIDEArray
552 #define GreatArray FIDEArray
553 #endif // !(BOARD_SIZE>=10)
554
555 #if (BOARD_SIZE>=12)
556 ChessSquare CourierArray[2][BOARD_SIZE] = {
557     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
558         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
560         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
561 };
562 #else // !(BOARD_SIZE>=12)
563 #define CourierArray CapablancaArray
564 #endif // !(BOARD_SIZE>=12)
565
566
567 Board initialPosition;
568
569
570 /* Convert str to a rating. Checks for special cases of "----",
571
572    "++++", etc. Also strips ()'s */
573 int
574 string_to_rating(str)
575   char *str;
576 {
577   while(*str && !isdigit(*str)) ++str;
578   if (!*str)
579     return 0;   /* One of the special "no rating" cases */
580   else
581     return atoi(str);
582 }
583
584 void
585 ClearProgramStats()
586 {
587     /* Init programStats */
588     programStats.movelist[0] = 0;
589     programStats.depth = 0;
590     programStats.nr_moves = 0;
591     programStats.moves_left = 0;
592     programStats.nodes = 0;
593     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
594     programStats.score = 0;
595     programStats.got_only_move = 0;
596     programStats.got_fail = 0;
597     programStats.line_is_book = 0;
598 }
599
600 void
601 InitBackEnd1()
602 {
603     int matched, min, sec;
604
605     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
606
607     GetTimeMark(&programStartTime);
608     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
609
610     ClearProgramStats();
611     programStats.ok_to_send = 1;
612     programStats.seen_stat = 0;
613
614     /*
615      * Initialize game list
616      */
617     ListNew(&gameList);
618
619
620     /*
621      * Internet chess server status
622      */
623     if (appData.icsActive) {
624         appData.matchMode = FALSE;
625         appData.matchGames = 0;
626 #if ZIPPY       
627         appData.noChessProgram = !appData.zippyPlay;
628 #else
629         appData.zippyPlay = FALSE;
630         appData.zippyTalk = FALSE;
631         appData.noChessProgram = TRUE;
632 #endif
633         if (*appData.icsHelper != NULLCHAR) {
634             appData.useTelnet = TRUE;
635             appData.telnetProgram = appData.icsHelper;
636         }
637     } else {
638         appData.zippyTalk = appData.zippyPlay = FALSE;
639     }
640
641     /* [AS] Initialize pv info list [HGM] and game state */
642     {
643         int i, j;
644
645         for( i=0; i<MAX_MOVES; i++ ) {
646             pvInfoList[i].depth = -1;
647             epStatus[i]=EP_NONE;
648             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
649         }
650     }
651
652     /*
653      * Parse timeControl resource
654      */
655     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
656                           appData.movesPerSession)) {
657         char buf[MSG_SIZ];
658         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
659         DisplayFatalError(buf, 0, 2);
660     }
661
662     /*
663      * Parse searchTime resource
664      */
665     if (*appData.searchTime != NULLCHAR) {
666         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
667         if (matched == 1) {
668             searchTime = min * 60;
669         } else if (matched == 2) {
670             searchTime = min * 60 + sec;
671         } else {
672             char buf[MSG_SIZ];
673             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
674             DisplayFatalError(buf, 0, 2);
675         }
676     }
677
678     /* [AS] Adjudication threshold */
679     adjudicateLossThreshold = appData.adjudicateLossThreshold;
680     
681     first.which = "first";
682     second.which = "second";
683     first.maybeThinking = second.maybeThinking = FALSE;
684     first.pr = second.pr = NoProc;
685     first.isr = second.isr = NULL;
686     first.sendTime = second.sendTime = 2;
687     first.sendDrawOffers = 1;
688     if (appData.firstPlaysBlack) {
689         first.twoMachinesColor = "black\n";
690         second.twoMachinesColor = "white\n";
691     } else {
692         first.twoMachinesColor = "white\n";
693         second.twoMachinesColor = "black\n";
694     }
695     first.program = appData.firstChessProgram;
696     second.program = appData.secondChessProgram;
697     first.host = appData.firstHost;
698     second.host = appData.secondHost;
699     first.dir = appData.firstDirectory;
700     second.dir = appData.secondDirectory;
701     first.other = &second;
702     second.other = &first;
703     first.initString = appData.initString;
704     second.initString = appData.secondInitString;
705     first.computerString = appData.firstComputerString;
706     second.computerString = appData.secondComputerString;
707     first.useSigint = second.useSigint = TRUE;
708     first.useSigterm = second.useSigterm = TRUE;
709     first.reuse = appData.reuseFirst;
710     second.reuse = appData.reuseSecond;
711     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
712     second.nps = appData.secondNPS;
713     first.useSetboard = second.useSetboard = FALSE;
714     first.useSAN = second.useSAN = FALSE;
715     first.usePing = second.usePing = FALSE;
716     first.lastPing = second.lastPing = 0;
717     first.lastPong = second.lastPong = 0;
718     first.usePlayother = second.usePlayother = FALSE;
719     first.useColors = second.useColors = TRUE;
720     first.useUsermove = second.useUsermove = FALSE;
721     first.sendICS = second.sendICS = FALSE;
722     first.sendName = second.sendName = appData.icsActive;
723     first.sdKludge = second.sdKludge = FALSE;
724     first.stKludge = second.stKludge = FALSE;
725     TidyProgramName(first.program, first.host, first.tidy);
726     TidyProgramName(second.program, second.host, second.tidy);
727     first.matchWins = second.matchWins = 0;
728     strcpy(first.variants, appData.variant);
729     strcpy(second.variants, appData.variant);
730     first.analysisSupport = second.analysisSupport = 2; /* detect */
731     first.analyzing = second.analyzing = FALSE;
732     first.initDone = second.initDone = FALSE;
733
734     /* New features added by Tord: */
735     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
736     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
737     /* End of new features added by Tord. */
738     first.fenOverride  = appData.fenOverride1;
739     second.fenOverride = appData.fenOverride2;
740
741     /* [HGM] time odds: set factor for each machine */
742     first.timeOdds  = appData.firstTimeOdds;
743     second.timeOdds = appData.secondTimeOdds;
744     { int norm = 1;
745         if(appData.timeOddsMode) {
746             norm = first.timeOdds;
747             if(norm > second.timeOdds) norm = second.timeOdds;
748         }
749         first.timeOdds /= norm;
750         second.timeOdds /= norm;
751     }
752
753     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
754     first.accumulateTC = appData.firstAccumulateTC;
755     second.accumulateTC = appData.secondAccumulateTC;
756     first.maxNrOfSessions = second.maxNrOfSessions = 1;
757
758     /* [HGM] debug */
759     first.debug = second.debug = FALSE;
760     first.supportsNPS = second.supportsNPS = UNKNOWN;
761
762     /* [HGM] options */
763     first.optionSettings  = appData.firstOptions;
764     second.optionSettings = appData.secondOptions;
765
766     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
767     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
768     first.isUCI = appData.firstIsUCI; /* [AS] */
769     second.isUCI = appData.secondIsUCI; /* [AS] */
770     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
771     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
772
773     if (appData.firstProtocolVersion > PROTOVER ||
774         appData.firstProtocolVersion < 1) {
775       char buf[MSG_SIZ];
776       sprintf(buf, _("protocol version %d not supported"),
777               appData.firstProtocolVersion);
778       DisplayFatalError(buf, 0, 2);
779     } else {
780       first.protocolVersion = appData.firstProtocolVersion;
781     }
782
783     if (appData.secondProtocolVersion > PROTOVER ||
784         appData.secondProtocolVersion < 1) {
785       char buf[MSG_SIZ];
786       sprintf(buf, _("protocol version %d not supported"),
787               appData.secondProtocolVersion);
788       DisplayFatalError(buf, 0, 2);
789     } else {
790       second.protocolVersion = appData.secondProtocolVersion;
791     }
792
793     if (appData.icsActive) {
794         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
795     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
796         appData.clockMode = FALSE;
797         first.sendTime = second.sendTime = 0;
798     }
799     
800 #if ZIPPY
801     /* Override some settings from environment variables, for backward
802        compatibility.  Unfortunately it's not feasible to have the env
803        vars just set defaults, at least in xboard.  Ugh.
804     */
805     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
806       ZippyInit();
807     }
808 #endif
809     
810     if (appData.noChessProgram) {
811         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
812         sprintf(programVersion, "%s", PACKAGE_STRING);
813     } else {
814       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
815       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
816       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
817     }
818
819     if (!appData.icsActive) {
820       char buf[MSG_SIZ];
821       /* Check for variants that are supported only in ICS mode,
822          or not at all.  Some that are accepted here nevertheless
823          have bugs; see comments below.
824       */
825       VariantClass variant = StringToVariant(appData.variant);
826       switch (variant) {
827       case VariantBughouse:     /* need four players and two boards */
828       case VariantKriegspiel:   /* need to hide pieces and move details */
829       /* case VariantFischeRandom: (Fabien: moved below) */
830         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
831         DisplayFatalError(buf, 0, 2);
832         return;
833
834       case VariantUnknown:
835       case VariantLoadable:
836       case Variant29:
837       case Variant30:
838       case Variant31:
839       case Variant32:
840       case Variant33:
841       case Variant34:
842       case Variant35:
843       case Variant36:
844       default:
845         sprintf(buf, _("Unknown variant name %s"), appData.variant);
846         DisplayFatalError(buf, 0, 2);
847         return;
848
849       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
850       case VariantFairy:      /* [HGM] TestLegality definitely off! */
851       case VariantGothic:     /* [HGM] should work */
852       case VariantCapablanca: /* [HGM] should work */
853       case VariantCourier:    /* [HGM] initial forced moves not implemented */
854       case VariantShogi:      /* [HGM] drops not tested for legality */
855       case VariantKnightmate: /* [HGM] should work */
856       case VariantCylinder:   /* [HGM] untested */
857       case VariantFalcon:     /* [HGM] untested */
858       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
859                                  offboard interposition not understood */
860       case VariantNormal:     /* definitely works! */
861       case VariantWildCastle: /* pieces not automatically shuffled */
862       case VariantNoCastle:   /* pieces not automatically shuffled */
863       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
864       case VariantLosers:     /* should work except for win condition,
865                                  and doesn't know captures are mandatory */
866       case VariantSuicide:    /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantGiveaway:   /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantTwoKings:   /* should work */
871       case VariantAtomic:     /* should work except for win condition */
872       case Variant3Check:     /* should work except for win condition */
873       case VariantShatranj:   /* should work except for all win conditions */
874       case VariantBerolina:   /* might work if TestLegality is off */
875       case VariantCapaRandom: /* should work */
876       case VariantJanus:      /* should work */
877       case VariantSuper:      /* experimental */
878       case VariantGreat:      /* experimental, requires legality testing to be off */
879         break;
880       }
881     }
882
883     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
884     InitEngineUCI( installDir, &second );
885 }
886
887 int NextIntegerFromString( char ** str, long * value )
888 {
889     int result = -1;
890     char * s = *str;
891
892     while( *s == ' ' || *s == '\t' ) {
893         s++;
894     }
895
896     *value = 0;
897
898     if( *s >= '0' && *s <= '9' ) {
899         while( *s >= '0' && *s <= '9' ) {
900             *value = *value * 10 + (*s - '0');
901             s++;
902         }
903
904         result = 0;
905     }
906
907     *str = s;
908
909     return result;
910 }
911
912 int NextTimeControlFromString( char ** str, long * value )
913 {
914     long temp;
915     int result = NextIntegerFromString( str, &temp );
916
917     if( result == 0 ) {
918         *value = temp * 60; /* Minutes */
919         if( **str == ':' ) {
920             (*str)++;
921             result = NextIntegerFromString( str, &temp );
922             *value += temp; /* Seconds */
923         }
924     }
925
926     return result;
927 }
928
929 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
930 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
931     int result = -1; long temp, temp2;
932
933     if(**str != '+') return -1; // old params remain in force!
934     (*str)++;
935     if( NextTimeControlFromString( str, &temp ) ) return -1;
936
937     if(**str != '/') {
938         /* time only: incremental or sudden-death time control */
939         if(**str == '+') { /* increment follows; read it */
940             (*str)++;
941             if(result = NextIntegerFromString( str, &temp2)) return -1;
942             *inc = temp2 * 1000;
943         } else *inc = 0;
944         *moves = 0; *tc = temp * 1000; 
945         return 0;
946     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
947
948     (*str)++; /* classical time control */
949     result = NextTimeControlFromString( str, &temp2);
950     if(result == 0) {
951         *moves = temp/60;
952         *tc    = temp2 * 1000;
953         *inc   = 0;
954     }
955     return result;
956 }
957
958 int GetTimeQuota(int movenr)
959 {   /* [HGM] get time to add from the multi-session time-control string */
960     int moves=1; /* kludge to force reading of first session */
961     long time, increment;
962     char *s = fullTimeControlString;
963
964     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
965     do {
966         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
967         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
968         if(movenr == -1) return time;    /* last move before new session     */
969         if(!moves) return increment;     /* current session is incremental   */
970         if(movenr >= 0) movenr -= moves; /* we already finished this session */
971     } while(movenr >= -1);               /* try again for next session       */
972
973     return 0; // no new time quota on this move
974 }
975
976 int
977 ParseTimeControl(tc, ti, mps)
978      char *tc;
979      int ti;
980      int mps;
981 {
982   long tc1;
983   long tc2;
984   char buf[MSG_SIZ];
985   
986   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
987   if(ti > 0) {
988     if(mps)
989       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
990     else sprintf(buf, "+%s+%d", tc, ti);
991   } else {
992     if(mps)
993              sprintf(buf, "+%d/%s", mps, tc);
994     else sprintf(buf, "+%s", tc);
995   }
996   fullTimeControlString = StrSave(buf);
997   
998   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
999     return FALSE;
1000   }
1001   
1002   if( *tc == '/' ) {
1003     /* Parse second time control */
1004     tc++;
1005     
1006     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1007       return FALSE;
1008     }
1009     
1010     if( tc2 == 0 ) {
1011       return FALSE;
1012     }
1013     
1014     timeControl_2 = tc2 * 1000;
1015   }
1016   else {
1017     timeControl_2 = 0;
1018   }
1019   
1020   if( tc1 == 0 ) {
1021     return FALSE;
1022   }
1023   
1024   timeControl = tc1 * 1000;
1025   
1026   if (ti >= 0) {
1027     timeIncrement = ti * 1000;  /* convert to ms */
1028     movesPerSession = 0;
1029   } else {
1030     timeIncrement = 0;
1031     movesPerSession = mps;
1032   }
1033   return TRUE;
1034 }
1035
1036 void
1037 InitBackEnd2()
1038 {
1039     if (appData.debugMode) {
1040         fprintf(debugFP, "%s\n", programVersion);
1041     }
1042
1043     if (appData.matchGames > 0) {
1044         appData.matchMode = TRUE;
1045     } else if (appData.matchMode) {
1046         appData.matchGames = 1;
1047     }
1048     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1049         appData.matchGames = appData.sameColorGames;
1050     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1051         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1052         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1053     }
1054     Reset(TRUE, FALSE);
1055     if (appData.noChessProgram || first.protocolVersion == 1) {
1056       InitBackEnd3();
1057     } else {
1058       /* kludge: allow timeout for initial "feature" commands */
1059       FreezeUI();
1060       DisplayMessage("", _("Starting chess program"));
1061       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1062     }
1063 }
1064
1065 void
1066 InitBackEnd3 P((void))
1067 {
1068     GameMode initialMode;
1069     char buf[MSG_SIZ];
1070     int err;
1071
1072     InitChessProgram(&first, startedFromSetupPosition);
1073
1074
1075     if (appData.icsActive) {
1076 #ifdef WIN32
1077         /* [DM] Make a console window if needed [HGM] merged ifs */
1078         ConsoleCreate(); 
1079 #endif
1080         err = establish();
1081         if (err != 0) {
1082             if (*appData.icsCommPort != NULLCHAR) {
1083                 sprintf(buf, _("Could not open comm port %s"),  
1084                         appData.icsCommPort);
1085             } else {
1086                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1087                         appData.icsHost, appData.icsPort);
1088             }
1089             DisplayFatalError(buf, err, 1);
1090             return;
1091         }
1092         SetICSMode();
1093         telnetISR =
1094           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1095         fromUserISR =
1096           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1097     } else if (appData.noChessProgram) {
1098         SetNCPMode();
1099     } else {
1100         SetGNUMode();
1101     }
1102
1103     if (*appData.cmailGameName != NULLCHAR) {
1104         SetCmailMode();
1105         OpenLoopback(&cmailPR);
1106         cmailISR =
1107           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1108     }
1109     
1110     ThawUI();
1111     DisplayMessage("", "");
1112     if (StrCaseCmp(appData.initialMode, "") == 0) {
1113       initialMode = BeginningOfGame;
1114     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1115       initialMode = TwoMachinesPlay;
1116     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1117       initialMode = AnalyzeFile; 
1118     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1119       initialMode = AnalyzeMode;
1120     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1121       initialMode = MachinePlaysWhite;
1122     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1123       initialMode = MachinePlaysBlack;
1124     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1125       initialMode = EditGame;
1126     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1127       initialMode = EditPosition;
1128     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1129       initialMode = Training;
1130     } else {
1131       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1132       DisplayFatalError(buf, 0, 2);
1133       return;
1134     }
1135
1136     if (appData.matchMode) {
1137         /* Set up machine vs. machine match */
1138         if (appData.noChessProgram) {
1139             DisplayFatalError(_("Can't have a match with no chess programs"),
1140                               0, 2);
1141             return;
1142         }
1143         matchMode = TRUE;
1144         matchGame = 1;
1145         if (*appData.loadGameFile != NULLCHAR) {
1146             int index = appData.loadGameIndex; // [HGM] autoinc
1147             if(index<0) lastIndex = index = 1;
1148             if (!LoadGameFromFile(appData.loadGameFile,
1149                                   index,
1150                                   appData.loadGameFile, FALSE)) {
1151                 DisplayFatalError(_("Bad game file"), 0, 1);
1152                 return;
1153             }
1154         } else if (*appData.loadPositionFile != NULLCHAR) {
1155             int index = appData.loadPositionIndex; // [HGM] autoinc
1156             if(index<0) lastIndex = index = 1;
1157             if (!LoadPositionFromFile(appData.loadPositionFile,
1158                                       index,
1159                                       appData.loadPositionFile)) {
1160                 DisplayFatalError(_("Bad position file"), 0, 1);
1161                 return;
1162             }
1163         }
1164         TwoMachinesEvent();
1165     } else if (*appData.cmailGameName != NULLCHAR) {
1166         /* Set up cmail mode */
1167         ReloadCmailMsgEvent(TRUE);
1168     } else {
1169         /* Set up other modes */
1170         if (initialMode == AnalyzeFile) {
1171           if (*appData.loadGameFile == NULLCHAR) {
1172             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1173             return;
1174           }
1175         }
1176         if (*appData.loadGameFile != NULLCHAR) {
1177             (void) LoadGameFromFile(appData.loadGameFile,
1178                                     appData.loadGameIndex,
1179                                     appData.loadGameFile, TRUE);
1180         } else if (*appData.loadPositionFile != NULLCHAR) {
1181             (void) LoadPositionFromFile(appData.loadPositionFile,
1182                                         appData.loadPositionIndex,
1183                                         appData.loadPositionFile);
1184             /* [HGM] try to make self-starting even after FEN load */
1185             /* to allow automatic setup of fairy variants with wtm */
1186             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1187                 gameMode = BeginningOfGame;
1188                 setboardSpoiledMachineBlack = 1;
1189             }
1190             /* [HGM] loadPos: make that every new game uses the setup */
1191             /* from file as long as we do not switch variant          */
1192             if(!blackPlaysFirst) { int i;
1193                 startedFromPositionFile = TRUE;
1194                 CopyBoard(filePosition, boards[0]);
1195                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1196             }
1197         }
1198         if (initialMode == AnalyzeMode) {
1199           if (appData.noChessProgram) {
1200             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1201             return;
1202           }
1203           if (appData.icsActive) {
1204             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1205             return;
1206           }
1207           AnalyzeModeEvent();
1208         } else if (initialMode == AnalyzeFile) {
1209           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1210           ShowThinkingEvent();
1211           AnalyzeFileEvent();
1212           AnalysisPeriodicEvent(1);
1213         } else if (initialMode == MachinePlaysWhite) {
1214           if (appData.noChessProgram) {
1215             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1216                               0, 2);
1217             return;
1218           }
1219           if (appData.icsActive) {
1220             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1221                               0, 2);
1222             return;
1223           }
1224           MachineWhiteEvent();
1225         } else if (initialMode == MachinePlaysBlack) {
1226           if (appData.noChessProgram) {
1227             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1228                               0, 2);
1229             return;
1230           }
1231           if (appData.icsActive) {
1232             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1233                               0, 2);
1234             return;
1235           }
1236           MachineBlackEvent();
1237         } else if (initialMode == TwoMachinesPlay) {
1238           if (appData.noChessProgram) {
1239             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1240                               0, 2);
1241             return;
1242           }
1243           if (appData.icsActive) {
1244             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1245                               0, 2);
1246             return;
1247           }
1248           TwoMachinesEvent();
1249         } else if (initialMode == EditGame) {
1250           EditGameEvent();
1251         } else if (initialMode == EditPosition) {
1252           EditPositionEvent();
1253         } else if (initialMode == Training) {
1254           if (*appData.loadGameFile == NULLCHAR) {
1255             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1256             return;
1257           }
1258           TrainingEvent();
1259         }
1260     }
1261 }
1262
1263 /*
1264  * Establish will establish a contact to a remote host.port.
1265  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1266  *  used to talk to the host.
1267  * Returns 0 if okay, error code if not.
1268  */
1269 int
1270 establish()
1271 {
1272     char buf[MSG_SIZ];
1273
1274     if (*appData.icsCommPort != NULLCHAR) {
1275         /* Talk to the host through a serial comm port */
1276         return OpenCommPort(appData.icsCommPort, &icsPR);
1277
1278     } else if (*appData.gateway != NULLCHAR) {
1279         if (*appData.remoteShell == NULLCHAR) {
1280             /* Use the rcmd protocol to run telnet program on a gateway host */
1281             snprintf(buf, sizeof(buf), "%s %s %s",
1282                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1283             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1284
1285         } else {
1286             /* Use the rsh program to run telnet program on a gateway host */
1287             if (*appData.remoteUser == NULLCHAR) {
1288                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1289                         appData.gateway, appData.telnetProgram,
1290                         appData.icsHost, appData.icsPort);
1291             } else {
1292                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1293                         appData.remoteShell, appData.gateway, 
1294                         appData.remoteUser, appData.telnetProgram,
1295                         appData.icsHost, appData.icsPort);
1296             }
1297             return StartChildProcess(buf, "", &icsPR);
1298
1299         }
1300     } else if (appData.useTelnet) {
1301         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1302
1303     } else {
1304         /* TCP socket interface differs somewhat between
1305            Unix and NT; handle details in the front end.
1306            */
1307         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1308     }
1309 }
1310
1311 void
1312 show_bytes(fp, buf, count)
1313      FILE *fp;
1314      char *buf;
1315      int count;
1316 {
1317     while (count--) {
1318         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1319             fprintf(fp, "\\%03o", *buf & 0xff);
1320         } else {
1321             putc(*buf, fp);
1322         }
1323         buf++;
1324     }
1325     fflush(fp);
1326 }
1327
1328 /* Returns an errno value */
1329 int
1330 OutputMaybeTelnet(pr, message, count, outError)
1331      ProcRef pr;
1332      char *message;
1333      int count;
1334      int *outError;
1335 {
1336     char buf[8192], *p, *q, *buflim;
1337     int left, newcount, outcount;
1338
1339     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1340         *appData.gateway != NULLCHAR) {
1341         if (appData.debugMode) {
1342             fprintf(debugFP, ">ICS: ");
1343             show_bytes(debugFP, message, count);
1344             fprintf(debugFP, "\n");
1345         }
1346         return OutputToProcess(pr, message, count, outError);
1347     }
1348
1349     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1350     p = message;
1351     q = buf;
1352     left = count;
1353     newcount = 0;
1354     while (left) {
1355         if (q >= buflim) {
1356             if (appData.debugMode) {
1357                 fprintf(debugFP, ">ICS: ");
1358                 show_bytes(debugFP, buf, newcount);
1359                 fprintf(debugFP, "\n");
1360             }
1361             outcount = OutputToProcess(pr, buf, newcount, outError);
1362             if (outcount < newcount) return -1; /* to be sure */
1363             q = buf;
1364             newcount = 0;
1365         }
1366         if (*p == '\n') {
1367             *q++ = '\r';
1368             newcount++;
1369         } else if (((unsigned char) *p) == TN_IAC) {
1370             *q++ = (char) TN_IAC;
1371             newcount ++;
1372         }
1373         *q++ = *p++;
1374         newcount++;
1375         left--;
1376     }
1377     if (appData.debugMode) {
1378         fprintf(debugFP, ">ICS: ");
1379         show_bytes(debugFP, buf, newcount);
1380         fprintf(debugFP, "\n");
1381     }
1382     outcount = OutputToProcess(pr, buf, newcount, outError);
1383     if (outcount < newcount) return -1; /* to be sure */
1384     return count;
1385 }
1386
1387 void
1388 read_from_player(isr, closure, message, count, error)
1389      InputSourceRef isr;
1390      VOIDSTAR closure;
1391      char *message;
1392      int count;
1393      int error;
1394 {
1395     int outError, outCount;
1396     static int gotEof = 0;
1397
1398     /* Pass data read from player on to ICS */
1399     if (count > 0) {
1400         gotEof = 0;
1401         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1402         if (outCount < count) {
1403             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1404         }
1405     } else if (count < 0) {
1406         RemoveInputSource(isr);
1407         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1408     } else if (gotEof++ > 0) {
1409         RemoveInputSource(isr);
1410         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1411     }
1412 }
1413
1414 void
1415 KeepAlive()
1416 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1417     SendToICS("date\n");
1418     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1419 }
1420
1421 void
1422 SendToICS(s)
1423      char *s;
1424 {
1425     int count, outCount, outError;
1426
1427     if (icsPR == NULL) return;
1428
1429     count = strlen(s);
1430     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1431     if (outCount < count) {
1432         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1433     }
1434 }
1435
1436 /* This is used for sending logon scripts to the ICS. Sending
1437    without a delay causes problems when using timestamp on ICC
1438    (at least on my machine). */
1439 void
1440 SendToICSDelayed(s,msdelay)
1441      char *s;
1442      long msdelay;
1443 {
1444     int count, outCount, outError;
1445
1446     if (icsPR == NULL) return;
1447
1448     count = strlen(s);
1449     if (appData.debugMode) {
1450         fprintf(debugFP, ">ICS: ");
1451         show_bytes(debugFP, s, count);
1452         fprintf(debugFP, "\n");
1453     }
1454     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1455                                       msdelay);
1456     if (outCount < count) {
1457         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1458     }
1459 }
1460
1461
1462 /* Remove all highlighting escape sequences in s
1463    Also deletes any suffix starting with '(' 
1464    */
1465 char *
1466 StripHighlightAndTitle(s)
1467      char *s;
1468 {
1469     static char retbuf[MSG_SIZ];
1470     char *p = retbuf;
1471
1472     while (*s != NULLCHAR) {
1473         while (*s == '\033') {
1474             while (*s != NULLCHAR && !isalpha(*s)) s++;
1475             if (*s != NULLCHAR) s++;
1476         }
1477         while (*s != NULLCHAR && *s != '\033') {
1478             if (*s == '(' || *s == '[') {
1479                 *p = NULLCHAR;
1480                 return retbuf;
1481             }
1482             *p++ = *s++;
1483         }
1484     }
1485     *p = NULLCHAR;
1486     return retbuf;
1487 }
1488
1489 /* Remove all highlighting escape sequences in s */
1490 char *
1491 StripHighlight(s)
1492      char *s;
1493 {
1494     static char retbuf[MSG_SIZ];
1495     char *p = retbuf;
1496
1497     while (*s != NULLCHAR) {
1498         while (*s == '\033') {
1499             while (*s != NULLCHAR && !isalpha(*s)) s++;
1500             if (*s != NULLCHAR) s++;
1501         }
1502         while (*s != NULLCHAR && *s != '\033') {
1503             *p++ = *s++;
1504         }
1505     }
1506     *p = NULLCHAR;
1507     return retbuf;
1508 }
1509
1510 char *variantNames[] = VARIANT_NAMES;
1511 char *
1512 VariantName(v)
1513      VariantClass v;
1514 {
1515     return variantNames[v];
1516 }
1517
1518
1519 /* Identify a variant from the strings the chess servers use or the
1520    PGN Variant tag names we use. */
1521 VariantClass
1522 StringToVariant(e)
1523      char *e;
1524 {
1525     char *p;
1526     int wnum = -1;
1527     VariantClass v = VariantNormal;
1528     int i, found = FALSE;
1529     char buf[MSG_SIZ];
1530
1531     if (!e) return v;
1532
1533     /* [HGM] skip over optional board-size prefixes */
1534     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1535         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1536         while( *e++ != '_');
1537     }
1538
1539     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1540       if (StrCaseStr(e, variantNames[i])) {
1541         v = (VariantClass) i;
1542         found = TRUE;
1543         break;
1544       }
1545     }
1546
1547     if (!found) {
1548       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1549           || StrCaseStr(e, "wild/fr") 
1550           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1551         v = VariantFischeRandom;
1552       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1553                  (i = 1, p = StrCaseStr(e, "w"))) {
1554         p += i;
1555         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1556         if (isdigit(*p)) {
1557           wnum = atoi(p);
1558         } else {
1559           wnum = -1;
1560         }
1561         switch (wnum) {
1562         case 0: /* FICS only, actually */
1563         case 1:
1564           /* Castling legal even if K starts on d-file */
1565           v = VariantWildCastle;
1566           break;
1567         case 2:
1568         case 3:
1569         case 4:
1570           /* Castling illegal even if K & R happen to start in
1571              normal positions. */
1572           v = VariantNoCastle;
1573           break;
1574         case 5:
1575         case 7:
1576         case 8:
1577         case 10:
1578         case 11:
1579         case 12:
1580         case 13:
1581         case 14:
1582         case 15:
1583         case 18:
1584         case 19:
1585           /* Castling legal iff K & R start in normal positions */
1586           v = VariantNormal;
1587           break;
1588         case 6:
1589         case 20:
1590         case 21:
1591           /* Special wilds for position setup; unclear what to do here */
1592           v = VariantLoadable;
1593           break;
1594         case 9:
1595           /* Bizarre ICC game */
1596           v = VariantTwoKings;
1597           break;
1598         case 16:
1599           v = VariantKriegspiel;
1600           break;
1601         case 17:
1602           v = VariantLosers;
1603           break;
1604         case 22:
1605           v = VariantFischeRandom;
1606           break;
1607         case 23:
1608           v = VariantCrazyhouse;
1609           break;
1610         case 24:
1611           v = VariantBughouse;
1612           break;
1613         case 25:
1614           v = Variant3Check;
1615           break;
1616         case 26:
1617           /* Not quite the same as FICS suicide! */
1618           v = VariantGiveaway;
1619           break;
1620         case 27:
1621           v = VariantAtomic;
1622           break;
1623         case 28:
1624           v = VariantShatranj;
1625           break;
1626
1627         /* Temporary names for future ICC types.  The name *will* change in 
1628            the next xboard/WinBoard release after ICC defines it. */
1629         case 29:
1630           v = Variant29;
1631           break;
1632         case 30:
1633           v = Variant30;
1634           break;
1635         case 31:
1636           v = Variant31;
1637           break;
1638         case 32:
1639           v = Variant32;
1640           break;
1641         case 33:
1642           v = Variant33;
1643           break;
1644         case 34:
1645           v = Variant34;
1646           break;
1647         case 35:
1648           v = Variant35;
1649           break;
1650         case 36:
1651           v = Variant36;
1652           break;
1653         case 37:
1654           v = VariantShogi;
1655           break;
1656         case 38:
1657           v = VariantXiangqi;
1658           break;
1659         case 39:
1660           v = VariantCourier;
1661           break;
1662         case 40:
1663           v = VariantGothic;
1664           break;
1665         case 41:
1666           v = VariantCapablanca;
1667           break;
1668         case 42:
1669           v = VariantKnightmate;
1670           break;
1671         case 43:
1672           v = VariantFairy;
1673           break;
1674         case 44:
1675           v = VariantCylinder;
1676           break;
1677         case 45:
1678           v = VariantFalcon;
1679           break;
1680         case 46:
1681           v = VariantCapaRandom;
1682           break;
1683         case 47:
1684           v = VariantBerolina;
1685           break;
1686         case 48:
1687           v = VariantJanus;
1688           break;
1689         case 49:
1690           v = VariantSuper;
1691           break;
1692         case 50:
1693           v = VariantGreat;
1694           break;
1695         case -1:
1696           /* Found "wild" or "w" in the string but no number;
1697              must assume it's normal chess. */
1698           v = VariantNormal;
1699           break;
1700         default:
1701           sprintf(buf, _("Unknown wild type %d"), wnum);
1702           DisplayError(buf, 0);
1703           v = VariantUnknown;
1704           break;
1705         }
1706       }
1707     }
1708     if (appData.debugMode) {
1709       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1710               e, wnum, VariantName(v));
1711     }
1712     return v;
1713 }
1714
1715 static int leftover_start = 0, leftover_len = 0;
1716 char star_match[STAR_MATCH_N][MSG_SIZ];
1717
1718 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1719    advance *index beyond it, and set leftover_start to the new value of
1720    *index; else return FALSE.  If pattern contains the character '*', it
1721    matches any sequence of characters not containing '\r', '\n', or the
1722    character following the '*' (if any), and the matched sequence(s) are
1723    copied into star_match.
1724    */
1725 int
1726 looking_at(buf, index, pattern)
1727      char *buf;
1728      int *index;
1729      char *pattern;
1730 {
1731     char *bufp = &buf[*index], *patternp = pattern;
1732     int star_count = 0;
1733     char *matchp = star_match[0];
1734     
1735     for (;;) {
1736         if (*patternp == NULLCHAR) {
1737             *index = leftover_start = bufp - buf;
1738             *matchp = NULLCHAR;
1739             return TRUE;
1740         }
1741         if (*bufp == NULLCHAR) return FALSE;
1742         if (*patternp == '*') {
1743             if (*bufp == *(patternp + 1)) {
1744                 *matchp = NULLCHAR;
1745                 matchp = star_match[++star_count];
1746                 patternp += 2;
1747                 bufp++;
1748                 continue;
1749             } else if (*bufp == '\n' || *bufp == '\r') {
1750                 patternp++;
1751                 if (*patternp == NULLCHAR)
1752                   continue;
1753                 else
1754                   return FALSE;
1755             } else {
1756                 *matchp++ = *bufp++;
1757                 continue;
1758             }
1759         }
1760         if (*patternp != *bufp) return FALSE;
1761         patternp++;
1762         bufp++;
1763     }
1764 }
1765
1766 void
1767 SendToPlayer(data, length)
1768      char *data;
1769      int length;
1770 {
1771     int error, outCount;
1772     outCount = OutputToProcess(NoProc, data, length, &error);
1773     if (outCount < length) {
1774         DisplayFatalError(_("Error writing to display"), error, 1);
1775     }
1776 }
1777
1778 void
1779 PackHolding(packed, holding)
1780      char packed[];
1781      char *holding;
1782 {
1783     char *p = holding;
1784     char *q = packed;
1785     int runlength = 0;
1786     int curr = 9999;
1787     do {
1788         if (*p == curr) {
1789             runlength++;
1790         } else {
1791             switch (runlength) {
1792               case 0:
1793                 break;
1794               case 1:
1795                 *q++ = curr;
1796                 break;
1797               case 2:
1798                 *q++ = curr;
1799                 *q++ = curr;
1800                 break;
1801               default:
1802                 sprintf(q, "%d", runlength);
1803                 while (*q) q++;
1804                 *q++ = curr;
1805                 break;
1806             }
1807             runlength = 1;
1808             curr = *p;
1809         }
1810     } while (*p++);
1811     *q = NULLCHAR;
1812 }
1813
1814 /* Telnet protocol requests from the front end */
1815 void
1816 TelnetRequest(ddww, option)
1817      unsigned char ddww, option;
1818 {
1819     unsigned char msg[3];
1820     int outCount, outError;
1821
1822     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1823
1824     if (appData.debugMode) {
1825         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1826         switch (ddww) {
1827           case TN_DO:
1828             ddwwStr = "DO";
1829             break;
1830           case TN_DONT:
1831             ddwwStr = "DONT";
1832             break;
1833           case TN_WILL:
1834             ddwwStr = "WILL";
1835             break;
1836           case TN_WONT:
1837             ddwwStr = "WONT";
1838             break;
1839           default:
1840             ddwwStr = buf1;
1841             sprintf(buf1, "%d", ddww);
1842             break;
1843         }
1844         switch (option) {
1845           case TN_ECHO:
1846             optionStr = "ECHO";
1847             break;
1848           default:
1849             optionStr = buf2;
1850             sprintf(buf2, "%d", option);
1851             break;
1852         }
1853         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1854     }
1855     msg[0] = TN_IAC;
1856     msg[1] = ddww;
1857     msg[2] = option;
1858     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1859     if (outCount < 3) {
1860         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1861     }
1862 }
1863
1864 void
1865 DoEcho()
1866 {
1867     if (!appData.icsActive) return;
1868     TelnetRequest(TN_DO, TN_ECHO);
1869 }
1870
1871 void
1872 DontEcho()
1873 {
1874     if (!appData.icsActive) return;
1875     TelnetRequest(TN_DONT, TN_ECHO);
1876 }
1877
1878 void
1879 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1880 {
1881     /* put the holdings sent to us by the server on the board holdings area */
1882     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1883     char p;
1884     ChessSquare piece;
1885
1886     if(gameInfo.holdingsWidth < 2)  return;
1887
1888     if( (int)lowestPiece >= BlackPawn ) {
1889         holdingsColumn = 0;
1890         countsColumn = 1;
1891         holdingsStartRow = BOARD_HEIGHT-1;
1892         direction = -1;
1893     } else {
1894         holdingsColumn = BOARD_WIDTH-1;
1895         countsColumn = BOARD_WIDTH-2;
1896         holdingsStartRow = 0;
1897         direction = 1;
1898     }
1899
1900     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1901         board[i][holdingsColumn] = EmptySquare;
1902         board[i][countsColumn]   = (ChessSquare) 0;
1903     }
1904     while( (p=*holdings++) != NULLCHAR ) {
1905         piece = CharToPiece( ToUpper(p) );
1906         if(piece == EmptySquare) continue;
1907         /*j = (int) piece - (int) WhitePawn;*/
1908         j = PieceToNumber(piece);
1909         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1910         if(j < 0) continue;               /* should not happen */
1911         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1912         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1913         board[holdingsStartRow+j*direction][countsColumn]++;
1914     }
1915
1916 }
1917
1918
1919 void
1920 VariantSwitch(Board board, VariantClass newVariant)
1921 {
1922    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1923    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1924 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1925
1926    startedFromPositionFile = FALSE;
1927    if(gameInfo.variant == newVariant) return;
1928
1929    /* [HGM] This routine is called each time an assignment is made to
1930     * gameInfo.variant during a game, to make sure the board sizes
1931     * are set to match the new variant. If that means adding or deleting
1932     * holdings, we shift the playing board accordingly
1933     * This kludge is needed because in ICS observe mode, we get boards
1934     * of an ongoing game without knowing the variant, and learn about the
1935     * latter only later. This can be because of the move list we requested,
1936     * in which case the game history is refilled from the beginning anyway,
1937     * but also when receiving holdings of a crazyhouse game. In the latter
1938     * case we want to add those holdings to the already received position.
1939     */
1940
1941
1942   if (appData.debugMode) {
1943     fprintf(debugFP, "Switch board from %s to %s\n",
1944                VariantName(gameInfo.variant), VariantName(newVariant));
1945     setbuf(debugFP, NULL);
1946   }
1947     shuffleOpenings = 0;       /* [HGM] shuffle */
1948     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1949     switch(newVariant) {
1950             case VariantShogi:
1951               newWidth = 9;  newHeight = 9;
1952               gameInfo.holdingsSize = 7;
1953             case VariantBughouse:
1954             case VariantCrazyhouse:
1955               newHoldingsWidth = 2; break;
1956             default:
1957               newHoldingsWidth = gameInfo.holdingsSize = 0;
1958     }
1959
1960     if(newWidth  != gameInfo.boardWidth  ||
1961        newHeight != gameInfo.boardHeight ||
1962        newHoldingsWidth != gameInfo.holdingsWidth ) {
1963
1964         /* shift position to new playing area, if needed */
1965         if(newHoldingsWidth > gameInfo.holdingsWidth) {
1966            for(i=0; i<BOARD_HEIGHT; i++) 
1967                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1968                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1969                                                      board[i][j];
1970            for(i=0; i<newHeight; i++) {
1971                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1972                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1973            }
1974         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1975            for(i=0; i<BOARD_HEIGHT; i++)
1976                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1977                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1978                                                  board[i][j];
1979         }
1980
1981         gameInfo.boardWidth  = newWidth;
1982         gameInfo.boardHeight = newHeight;
1983         gameInfo.holdingsWidth = newHoldingsWidth;
1984         gameInfo.variant = newVariant;
1985         InitDrawingSizes(-2, 0);
1986
1987         /* [HGM] The following should definitely be solved in a better way */
1988         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
1989     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
1990
1991     forwardMostMove = oldForwardMostMove;
1992     backwardMostMove = oldBackwardMostMove;
1993     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
1994 }
1995
1996 static int loggedOn = FALSE;
1997
1998 /*-- Game start info cache: --*/
1999 int gs_gamenum;
2000 char gs_kind[MSG_SIZ];
2001 static char player1Name[128] = "";
2002 static char player2Name[128] = "";
2003 static int player1Rating = -1;
2004 static int player2Rating = -1;
2005 /*----------------------------*/
2006
2007 ColorClass curColor = ColorNormal;
2008 int suppressKibitz = 0;
2009
2010 void
2011 read_from_ics(isr, closure, data, count, error)
2012      InputSourceRef isr;
2013      VOIDSTAR closure;
2014      char *data;
2015      int count;
2016      int error;
2017 {
2018 #define BUF_SIZE 8192
2019 #define STARTED_NONE 0
2020 #define STARTED_MOVES 1
2021 #define STARTED_BOARD 2
2022 #define STARTED_OBSERVE 3
2023 #define STARTED_HOLDINGS 4
2024 #define STARTED_CHATTER 5
2025 #define STARTED_COMMENT 6
2026 #define STARTED_MOVES_NOHIDE 7
2027     
2028     static int started = STARTED_NONE;
2029     static char parse[20000];
2030     static int parse_pos = 0;
2031     static char buf[BUF_SIZE + 1];
2032     static int firstTime = TRUE, intfSet = FALSE;
2033     static ColorClass prevColor = ColorNormal;
2034     static int savingComment = FALSE;
2035     char str[500];
2036     int i, oldi;
2037     int buf_len;
2038     int next_out;
2039     int tkind;
2040     int backup;    /* [DM] For zippy color lines */
2041     char *p;
2042     char talker[MSG_SIZ]; // [HGM] chat
2043     int channel;
2044
2045     if (appData.debugMode) {
2046       if (!error) {
2047         fprintf(debugFP, "<ICS: ");
2048         show_bytes(debugFP, data, count);
2049         fprintf(debugFP, "\n");
2050       }
2051     }
2052
2053     if (appData.debugMode) { int f = forwardMostMove;
2054         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2055                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2056     }
2057     if (count > 0) {
2058         /* If last read ended with a partial line that we couldn't parse,
2059            prepend it to the new read and try again. */
2060         if (leftover_len > 0) {
2061             for (i=0; i<leftover_len; i++)
2062               buf[i] = buf[leftover_start + i];
2063         }
2064
2065         /* Copy in new characters, removing nulls and \r's */
2066         buf_len = leftover_len;
2067         for (i = 0; i < count; i++) {
2068             if (data[i] != NULLCHAR && data[i] != '\r')
2069               buf[buf_len++] = data[i];
2070             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2071                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2072                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2073                 if(buf_len == 0 || buf[buf_len-1] != ' ')
2074                    buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2075             }
2076         }
2077
2078         buf[buf_len] = NULLCHAR;
2079         next_out = leftover_len;
2080         leftover_start = 0;
2081         
2082         i = 0;
2083         while (i < buf_len) {
2084             /* Deal with part of the TELNET option negotiation
2085                protocol.  We refuse to do anything beyond the
2086                defaults, except that we allow the WILL ECHO option,
2087                which ICS uses to turn off password echoing when we are
2088                directly connected to it.  We reject this option
2089                if localLineEditing mode is on (always on in xboard)
2090                and we are talking to port 23, which might be a real
2091                telnet server that will try to keep WILL ECHO on permanently.
2092              */
2093             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2094                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2095                 unsigned char option;
2096                 oldi = i;
2097                 switch ((unsigned char) buf[++i]) {
2098                   case TN_WILL:
2099                     if (appData.debugMode)
2100                       fprintf(debugFP, "\n<WILL ");
2101                     switch (option = (unsigned char) buf[++i]) {
2102                       case TN_ECHO:
2103                         if (appData.debugMode)
2104                           fprintf(debugFP, "ECHO ");
2105                         /* Reply only if this is a change, according
2106                            to the protocol rules. */
2107                         if (remoteEchoOption) break;
2108                         if (appData.localLineEditing &&
2109                             atoi(appData.icsPort) == TN_PORT) {
2110                             TelnetRequest(TN_DONT, TN_ECHO);
2111                         } else {
2112                             EchoOff();
2113                             TelnetRequest(TN_DO, TN_ECHO);
2114                             remoteEchoOption = TRUE;
2115                         }
2116                         break;
2117                       default:
2118                         if (appData.debugMode)
2119                           fprintf(debugFP, "%d ", option);
2120                         /* Whatever this is, we don't want it. */
2121                         TelnetRequest(TN_DONT, option);
2122                         break;
2123                     }
2124                     break;
2125                   case TN_WONT:
2126                     if (appData.debugMode)
2127                       fprintf(debugFP, "\n<WONT ");
2128                     switch (option = (unsigned char) buf[++i]) {
2129                       case TN_ECHO:
2130                         if (appData.debugMode)
2131                           fprintf(debugFP, "ECHO ");
2132                         /* Reply only if this is a change, according
2133                            to the protocol rules. */
2134                         if (!remoteEchoOption) break;
2135                         EchoOn();
2136                         TelnetRequest(TN_DONT, TN_ECHO);
2137                         remoteEchoOption = FALSE;
2138                         break;
2139                       default:
2140                         if (appData.debugMode)
2141                           fprintf(debugFP, "%d ", (unsigned char) option);
2142                         /* Whatever this is, it must already be turned
2143                            off, because we never agree to turn on
2144                            anything non-default, so according to the
2145                            protocol rules, we don't reply. */
2146                         break;
2147                     }
2148                     break;
2149                   case TN_DO:
2150                     if (appData.debugMode)
2151                       fprintf(debugFP, "\n<DO ");
2152                     switch (option = (unsigned char) buf[++i]) {
2153                       default:
2154                         /* Whatever this is, we refuse to do it. */
2155                         if (appData.debugMode)
2156                           fprintf(debugFP, "%d ", option);
2157                         TelnetRequest(TN_WONT, option);
2158                         break;
2159                     }
2160                     break;
2161                   case TN_DONT:
2162                     if (appData.debugMode)
2163                       fprintf(debugFP, "\n<DONT ");
2164                     switch (option = (unsigned char) buf[++i]) {
2165                       default:
2166                         if (appData.debugMode)
2167                           fprintf(debugFP, "%d ", option);
2168                         /* Whatever this is, we are already not doing
2169                            it, because we never agree to do anything
2170                            non-default, so according to the protocol
2171                            rules, we don't reply. */
2172                         break;
2173                     }
2174                     break;
2175                   case TN_IAC:
2176                     if (appData.debugMode)
2177                       fprintf(debugFP, "\n<IAC ");
2178                     /* Doubled IAC; pass it through */
2179                     i--;
2180                     break;
2181                   default:
2182                     if (appData.debugMode)
2183                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2184                     /* Drop all other telnet commands on the floor */
2185                     break;
2186                 }
2187                 if (oldi > next_out)
2188                   SendToPlayer(&buf[next_out], oldi - next_out);
2189                 if (++i > next_out)
2190                   next_out = i;
2191                 continue;
2192             }
2193                 
2194             /* OK, this at least will *usually* work */
2195             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2196                 loggedOn = TRUE;
2197             }
2198             
2199             if (loggedOn && !intfSet) {
2200                 if (ics_type == ICS_ICC) {
2201                   sprintf(str,
2202                           "/set-quietly interface %s\n/set-quietly style 12\n",
2203                           programVersion);
2204
2205                 } else if (ics_type == ICS_CHESSNET) {
2206                   sprintf(str, "/style 12\n");
2207                 } else {
2208                   strcpy(str, "alias $ @\n$set interface ");
2209                   strcat(str, programVersion);
2210                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2211 #ifdef WIN32
2212                   strcat(str, "$iset nohighlight 1\n");
2213 #endif
2214                   strcat(str, "$iset lock 1\n$style 12\n");
2215                 }
2216                 SendToICS(str);
2217                 intfSet = TRUE;
2218             }
2219
2220             if (started == STARTED_COMMENT) {
2221                 /* Accumulate characters in comment */
2222                 parse[parse_pos++] = buf[i];
2223                 if (buf[i] == '\n') {
2224                     parse[parse_pos] = NULLCHAR;
2225                     if(chattingPartner>=0) {
2226                         char mess[MSG_SIZ];
2227                         sprintf(mess, "%s%s", talker, parse);
2228                         OutputChatMessage(chattingPartner, mess);
2229                         chattingPartner = -1;
2230                     } else
2231                     if(!suppressKibitz) // [HGM] kibitz
2232                         AppendComment(forwardMostMove, StripHighlight(parse));
2233                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2234                         int nrDigit = 0, nrAlph = 0, i;
2235                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2236                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2237                         parse[parse_pos] = NULLCHAR;
2238                         // try to be smart: if it does not look like search info, it should go to
2239                         // ICS interaction window after all, not to engine-output window.
2240                         for(i=0; i<parse_pos; i++) { // count letters and digits
2241                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2242                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2243                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2244                         }
2245                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2246                             int depth=0; float score;
2247                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2248                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2249                                 pvInfoList[forwardMostMove-1].depth = depth;
2250                                 pvInfoList[forwardMostMove-1].score = 100*score;
2251                             }
2252                             OutputKibitz(suppressKibitz, parse);
2253                         } else {
2254                             char tmp[MSG_SIZ];
2255                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2256                             SendToPlayer(tmp, strlen(tmp));
2257                         }
2258                     }
2259                     started = STARTED_NONE;
2260                 } else {
2261                     /* Don't match patterns against characters in chatter */
2262                     i++;
2263                     continue;
2264                 }
2265             }
2266             if (started == STARTED_CHATTER) {
2267                 if (buf[i] != '\n') {
2268                     /* Don't match patterns against characters in chatter */
2269                     i++;
2270                     continue;
2271                 }
2272                 started = STARTED_NONE;
2273             }
2274
2275             /* Kludge to deal with rcmd protocol */
2276             if (firstTime && looking_at(buf, &i, "\001*")) {
2277                 DisplayFatalError(&buf[1], 0, 1);
2278                 continue;
2279             } else {
2280                 firstTime = FALSE;
2281             }
2282
2283             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2284                 ics_type = ICS_ICC;
2285                 ics_prefix = "/";
2286                 if (appData.debugMode)
2287                   fprintf(debugFP, "ics_type %d\n", ics_type);
2288                 continue;
2289             }
2290             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2291                 ics_type = ICS_FICS;
2292                 ics_prefix = "$";
2293                 if (appData.debugMode)
2294                   fprintf(debugFP, "ics_type %d\n", ics_type);
2295                 continue;
2296             }
2297             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2298                 ics_type = ICS_CHESSNET;
2299                 ics_prefix = "/";
2300                 if (appData.debugMode)
2301                   fprintf(debugFP, "ics_type %d\n", ics_type);
2302                 continue;
2303             }
2304
2305             if (!loggedOn &&
2306                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2307                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2308                  looking_at(buf, &i, "will be \"*\""))) {
2309               strcpy(ics_handle, star_match[0]);
2310               continue;
2311             }
2312
2313             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2314               char buf[MSG_SIZ];
2315               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2316               DisplayIcsInteractionTitle(buf);
2317               have_set_title = TRUE;
2318             }
2319
2320             /* skip finger notes */
2321             if (started == STARTED_NONE &&
2322                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2323                  (buf[i] == '1' && buf[i+1] == '0')) &&
2324                 buf[i+2] == ':' && buf[i+3] == ' ') {
2325               started = STARTED_CHATTER;
2326               i += 3;
2327               continue;
2328             }
2329
2330             /* skip formula vars */
2331             if (started == STARTED_NONE &&
2332                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2333               started = STARTED_CHATTER;
2334               i += 3;
2335               continue;
2336             }
2337
2338             oldi = i;
2339             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2340             if (appData.autoKibitz && started == STARTED_NONE && 
2341                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2342                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2343                 if(looking_at(buf, &i, "* kibitzes: ") &&
2344                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2345                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2346                         suppressKibitz = TRUE;
2347                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2348                                 && (gameMode == IcsPlayingWhite)) ||
2349                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2350                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2351                             started = STARTED_CHATTER; // own kibitz we simply discard
2352                         else {
2353                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2354                             parse_pos = 0; parse[0] = NULLCHAR;
2355                             savingComment = TRUE;
2356                             suppressKibitz = gameMode != IcsObserving ? 2 :
2357                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2358                         } 
2359                         continue;
2360                 } else
2361                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2362                     started = STARTED_CHATTER;
2363                     suppressKibitz = TRUE;
2364                 }
2365             } // [HGM] kibitz: end of patch
2366
2367 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2368
2369             // [HGM] chat: intercept tells by users for which we have an open chat window
2370             channel = -1;
2371             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2372                                            looking_at(buf, &i, "* whispers:") ||
2373                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2374                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2375                 int p;
2376                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2377                 chattingPartner = -1;
2378
2379                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2380                 for(p=0; p<MAX_CHAT; p++) {
2381                     if(channel == atoi(chatPartner[p])) {
2382                     talker[0] = '['; strcat(talker, "]");
2383                     chattingPartner = p; break;
2384                     }
2385                 } else
2386                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2387                 for(p=0; p<MAX_CHAT; p++) {
2388                     if(!strcmp("WHISPER", chatPartner[p])) {
2389                         talker[0] = '['; strcat(talker, "]");
2390                         chattingPartner = p; break;
2391                     }
2392                 }
2393                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2394                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2395                     talker[0] = 0;
2396                     chattingPartner = p; break;
2397                 }
2398                 if(chattingPartner<0) i = oldi; else {
2399                     started = STARTED_COMMENT;
2400                     parse_pos = 0; parse[0] = NULLCHAR;
2401                     savingComment = TRUE;
2402                     suppressKibitz = TRUE;
2403                 }
2404             } // [HGM] chat: end of patch
2405
2406             if (appData.zippyTalk || appData.zippyPlay) {
2407                 /* [DM] Backup address for color zippy lines */
2408                 backup = i;
2409 #if ZIPPY
2410        #ifdef WIN32
2411                if (loggedOn == TRUE)
2412                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2413                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2414        #else
2415                 if (ZippyControl(buf, &i) ||
2416                     ZippyConverse(buf, &i) ||
2417                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2418                       loggedOn = TRUE;
2419                       if (!appData.colorize) continue;
2420                 }
2421        #endif
2422 #endif
2423             } // [DM] 'else { ' deleted
2424                 if (
2425                     /* Regular tells and says */
2426                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2427                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2428                     looking_at(buf, &i, "* says: ") ||
2429                     /* Don't color "message" or "messages" output */
2430                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2431                     looking_at(buf, &i, "*. * at *:*: ") ||
2432                     looking_at(buf, &i, "--* (*:*): ") ||
2433                     /* Message notifications (same color as tells) */
2434                     looking_at(buf, &i, "* has left a message ") ||
2435                     looking_at(buf, &i, "* just sent you a message:\n") ||
2436                     /* Whispers and kibitzes */
2437                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2438                     looking_at(buf, &i, "* kibitzes: ") ||
2439                     /* Channel tells */
2440                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2441
2442                   if (tkind == 1 && strchr(star_match[0], ':')) {
2443                       /* Avoid "tells you:" spoofs in channels */
2444                      tkind = 3;
2445                   }
2446                   if (star_match[0][0] == NULLCHAR ||
2447                       strchr(star_match[0], ' ') ||
2448                       (tkind == 3 && strchr(star_match[1], ' '))) {
2449                     /* Reject bogus matches */
2450                     i = oldi;
2451                   } else {
2452                     if (appData.colorize) {
2453                       if (oldi > next_out) {
2454                         SendToPlayer(&buf[next_out], oldi - next_out);
2455                         next_out = oldi;
2456                       }
2457                       switch (tkind) {
2458                       case 1:
2459                         Colorize(ColorTell, FALSE);
2460                         curColor = ColorTell;
2461                         break;
2462                       case 2:
2463                         Colorize(ColorKibitz, FALSE);
2464                         curColor = ColorKibitz;
2465                         break;
2466                       case 3:
2467                         p = strrchr(star_match[1], '(');
2468                         if (p == NULL) {
2469                           p = star_match[1];
2470                         } else {
2471                           p++;
2472                         }
2473                         if (atoi(p) == 1) {
2474                           Colorize(ColorChannel1, FALSE);
2475                           curColor = ColorChannel1;
2476                         } else {
2477                           Colorize(ColorChannel, FALSE);
2478                           curColor = ColorChannel;
2479                         }
2480                         break;
2481                       case 5:
2482                         curColor = ColorNormal;
2483                         break;
2484                       }
2485                     }
2486                     if (started == STARTED_NONE && appData.autoComment &&
2487                         (gameMode == IcsObserving ||
2488                          gameMode == IcsPlayingWhite ||
2489                          gameMode == IcsPlayingBlack)) {
2490                       parse_pos = i - oldi;
2491                       memcpy(parse, &buf[oldi], parse_pos);
2492                       parse[parse_pos] = NULLCHAR;
2493                       started = STARTED_COMMENT;
2494                       savingComment = TRUE;
2495                     } else {
2496                       started = STARTED_CHATTER;
2497                       savingComment = FALSE;
2498                     }
2499                     loggedOn = TRUE;
2500                     continue;
2501                   }
2502                 }
2503
2504                 if (looking_at(buf, &i, "* s-shouts: ") ||
2505                     looking_at(buf, &i, "* c-shouts: ")) {
2506                     if (appData.colorize) {
2507                         if (oldi > next_out) {
2508                             SendToPlayer(&buf[next_out], oldi - next_out);
2509                             next_out = oldi;
2510                         }
2511                         Colorize(ColorSShout, FALSE);
2512                         curColor = ColorSShout;
2513                     }
2514                     loggedOn = TRUE;
2515                     started = STARTED_CHATTER;
2516                     continue;
2517                 }
2518
2519                 if (looking_at(buf, &i, "--->")) {
2520                     loggedOn = TRUE;
2521                     continue;
2522                 }
2523
2524                 if (looking_at(buf, &i, "* shouts: ") ||
2525                     looking_at(buf, &i, "--> ")) {
2526                     if (appData.colorize) {
2527                         if (oldi > next_out) {
2528                             SendToPlayer(&buf[next_out], oldi - next_out);
2529                             next_out = oldi;
2530                         }
2531                         Colorize(ColorShout, FALSE);
2532                         curColor = ColorShout;
2533                     }
2534                     loggedOn = TRUE;
2535                     started = STARTED_CHATTER;
2536                     continue;
2537                 }
2538
2539                 if (looking_at( buf, &i, "Challenge:")) {
2540                     if (appData.colorize) {
2541                         if (oldi > next_out) {
2542                             SendToPlayer(&buf[next_out], oldi - next_out);
2543                             next_out = oldi;
2544                         }
2545                         Colorize(ColorChallenge, FALSE);
2546                         curColor = ColorChallenge;
2547                     }
2548                     loggedOn = TRUE;
2549                     continue;
2550                 }
2551
2552                 if (looking_at(buf, &i, "* offers you") ||
2553                     looking_at(buf, &i, "* offers to be") ||
2554                     looking_at(buf, &i, "* would like to") ||
2555                     looking_at(buf, &i, "* requests to") ||
2556                     looking_at(buf, &i, "Your opponent offers") ||
2557                     looking_at(buf, &i, "Your opponent requests")) {
2558
2559                     if (appData.colorize) {
2560                         if (oldi > next_out) {
2561                             SendToPlayer(&buf[next_out], oldi - next_out);
2562                             next_out = oldi;
2563                         }
2564                         Colorize(ColorRequest, FALSE);
2565                         curColor = ColorRequest;
2566                     }
2567                     continue;
2568                 }
2569
2570                 if (looking_at(buf, &i, "* (*) seeking")) {
2571                     if (appData.colorize) {
2572                         if (oldi > next_out) {
2573                             SendToPlayer(&buf[next_out], oldi - next_out);
2574                             next_out = oldi;
2575                         }
2576                         Colorize(ColorSeek, FALSE);
2577                         curColor = ColorSeek;
2578                     }
2579                     continue;
2580             }
2581
2582             if (looking_at(buf, &i, "\\   ")) {
2583                 if (prevColor != ColorNormal) {
2584                     if (oldi > next_out) {
2585                         SendToPlayer(&buf[next_out], oldi - next_out);
2586                         next_out = oldi;
2587                     }
2588                     Colorize(prevColor, TRUE);
2589                     curColor = prevColor;
2590                 }
2591                 if (savingComment) {
2592                     parse_pos = i - oldi;
2593                     memcpy(parse, &buf[oldi], parse_pos);
2594                     parse[parse_pos] = NULLCHAR;
2595                     started = STARTED_COMMENT;
2596                 } else {
2597                     started = STARTED_CHATTER;
2598                 }
2599                 continue;
2600             }
2601
2602             if (looking_at(buf, &i, "Black Strength :") ||
2603                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2604                 looking_at(buf, &i, "<10>") ||
2605                 looking_at(buf, &i, "#@#")) {
2606                 /* Wrong board style */
2607                 loggedOn = TRUE;
2608                 SendToICS(ics_prefix);
2609                 SendToICS("set style 12\n");
2610                 SendToICS(ics_prefix);
2611                 SendToICS("refresh\n");
2612                 continue;
2613             }
2614             
2615             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2616                 ICSInitScript();
2617                 have_sent_ICS_logon = 1;
2618                 continue;
2619             }
2620               
2621             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2622                 (looking_at(buf, &i, "\n<12> ") ||
2623                  looking_at(buf, &i, "<12> "))) {
2624                 loggedOn = TRUE;
2625                 if (oldi > next_out) {
2626                     SendToPlayer(&buf[next_out], oldi - next_out);
2627                 }
2628                 next_out = i;
2629                 started = STARTED_BOARD;
2630                 parse_pos = 0;
2631                 continue;
2632             }
2633
2634             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2635                 looking_at(buf, &i, "<b1> ")) {
2636                 if (oldi > next_out) {
2637                     SendToPlayer(&buf[next_out], oldi - next_out);
2638                 }
2639                 next_out = i;
2640                 started = STARTED_HOLDINGS;
2641                 parse_pos = 0;
2642                 continue;
2643             }
2644
2645             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2646                 loggedOn = TRUE;
2647                 /* Header for a move list -- first line */
2648
2649                 switch (ics_getting_history) {
2650                   case H_FALSE:
2651                     switch (gameMode) {
2652                       case IcsIdle:
2653                       case BeginningOfGame:
2654                         /* User typed "moves" or "oldmoves" while we
2655                            were idle.  Pretend we asked for these
2656                            moves and soak them up so user can step
2657                            through them and/or save them.
2658                            */
2659                         Reset(FALSE, TRUE);
2660                         gameMode = IcsObserving;
2661                         ModeHighlight();
2662                         ics_gamenum = -1;
2663                         ics_getting_history = H_GOT_UNREQ_HEADER;
2664                         break;
2665                       case EditGame: /*?*/
2666                       case EditPosition: /*?*/
2667                         /* Should above feature work in these modes too? */
2668                         /* For now it doesn't */
2669                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2670                         break;
2671                       default:
2672                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2673                         break;
2674                     }
2675                     break;
2676                   case H_REQUESTED:
2677                     /* Is this the right one? */
2678                     if (gameInfo.white && gameInfo.black &&
2679                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2680                         strcmp(gameInfo.black, star_match[2]) == 0) {
2681                         /* All is well */
2682                         ics_getting_history = H_GOT_REQ_HEADER;
2683                     }
2684                     break;
2685                   case H_GOT_REQ_HEADER:
2686                   case H_GOT_UNREQ_HEADER:
2687                   case H_GOT_UNWANTED_HEADER:
2688                   case H_GETTING_MOVES:
2689                     /* Should not happen */
2690                     DisplayError(_("Error gathering move list: two headers"), 0);
2691                     ics_getting_history = H_FALSE;
2692                     break;
2693                 }
2694
2695                 /* Save player ratings into gameInfo if needed */
2696                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2697                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2698                     (gameInfo.whiteRating == -1 ||
2699                      gameInfo.blackRating == -1)) {
2700
2701                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2702                     gameInfo.blackRating = string_to_rating(star_match[3]);
2703                     if (appData.debugMode)
2704                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2705                               gameInfo.whiteRating, gameInfo.blackRating);
2706                 }
2707                 continue;
2708             }
2709
2710             if (looking_at(buf, &i,
2711               "* * match, initial time: * minute*, increment: * second")) {
2712                 /* Header for a move list -- second line */
2713                 /* Initial board will follow if this is a wild game */
2714                 if (gameInfo.event != NULL) free(gameInfo.event);
2715                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2716                 gameInfo.event = StrSave(str);
2717                 /* [HGM] we switched variant. Translate boards if needed. */
2718                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2719                 continue;
2720             }
2721
2722             if (looking_at(buf, &i, "Move  ")) {
2723                 /* Beginning of a move list */
2724                 switch (ics_getting_history) {
2725                   case H_FALSE:
2726                     /* Normally should not happen */
2727                     /* Maybe user hit reset while we were parsing */
2728                     break;
2729                   case H_REQUESTED:
2730                     /* Happens if we are ignoring a move list that is not
2731                      * the one we just requested.  Common if the user
2732                      * tries to observe two games without turning off
2733                      * getMoveList */
2734                     break;
2735                   case H_GETTING_MOVES:
2736                     /* Should not happen */
2737                     DisplayError(_("Error gathering move list: nested"), 0);
2738                     ics_getting_history = H_FALSE;
2739                     break;
2740                   case H_GOT_REQ_HEADER:
2741                     ics_getting_history = H_GETTING_MOVES;
2742                     started = STARTED_MOVES;
2743                     parse_pos = 0;
2744                     if (oldi > next_out) {
2745                         SendToPlayer(&buf[next_out], oldi - next_out);
2746                     }
2747                     break;
2748                   case H_GOT_UNREQ_HEADER:
2749                     ics_getting_history = H_GETTING_MOVES;
2750                     started = STARTED_MOVES_NOHIDE;
2751                     parse_pos = 0;
2752                     break;
2753                   case H_GOT_UNWANTED_HEADER:
2754                     ics_getting_history = H_FALSE;
2755                     break;
2756                 }
2757                 continue;
2758             }                           
2759             
2760             if (looking_at(buf, &i, "% ") ||
2761                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2762                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2763                 savingComment = FALSE;
2764                 switch (started) {
2765                   case STARTED_MOVES:
2766                   case STARTED_MOVES_NOHIDE:
2767                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2768                     parse[parse_pos + i - oldi] = NULLCHAR;
2769                     ParseGameHistory(parse);
2770 #if ZIPPY
2771                     if (appData.zippyPlay && first.initDone) {
2772                         FeedMovesToProgram(&first, forwardMostMove);
2773                         if (gameMode == IcsPlayingWhite) {
2774                             if (WhiteOnMove(forwardMostMove)) {
2775                                 if (first.sendTime) {
2776                                   if (first.useColors) {
2777                                     SendToProgram("black\n", &first); 
2778                                   }
2779                                   SendTimeRemaining(&first, TRUE);
2780                                 }
2781                                 if (first.useColors) {
2782                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2783                                 }
2784                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2785                                 first.maybeThinking = TRUE;
2786                             } else {
2787                                 if (first.usePlayother) {
2788                                   if (first.sendTime) {
2789                                     SendTimeRemaining(&first, TRUE);
2790                                   }
2791                                   SendToProgram("playother\n", &first);
2792                                   firstMove = FALSE;
2793                                 } else {
2794                                   firstMove = TRUE;
2795                                 }
2796                             }
2797                         } else if (gameMode == IcsPlayingBlack) {
2798                             if (!WhiteOnMove(forwardMostMove)) {
2799                                 if (first.sendTime) {
2800                                   if (first.useColors) {
2801                                     SendToProgram("white\n", &first);
2802                                   }
2803                                   SendTimeRemaining(&first, FALSE);
2804                                 }
2805                                 if (first.useColors) {
2806                                   SendToProgram("black\n", &first);
2807                                 }
2808                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2809                                 first.maybeThinking = TRUE;
2810                             } else {
2811                                 if (first.usePlayother) {
2812                                   if (first.sendTime) {
2813                                     SendTimeRemaining(&first, FALSE);
2814                                   }
2815                                   SendToProgram("playother\n", &first);
2816                                   firstMove = FALSE;
2817                                 } else {
2818                                   firstMove = TRUE;
2819                                 }
2820                             }
2821                         }                       
2822                     }
2823 #endif
2824                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2825                         /* Moves came from oldmoves or moves command
2826                            while we weren't doing anything else.
2827                            */
2828                         currentMove = forwardMostMove;
2829                         ClearHighlights();/*!!could figure this out*/
2830                         flipView = appData.flipView;
2831                         DrawPosition(FALSE, boards[currentMove]);
2832                         DisplayBothClocks();
2833                         sprintf(str, "%s vs. %s",
2834                                 gameInfo.white, gameInfo.black);
2835                         DisplayTitle(str);
2836                         gameMode = IcsIdle;
2837                     } else {
2838                         /* Moves were history of an active game */
2839                         if (gameInfo.resultDetails != NULL) {
2840                             free(gameInfo.resultDetails);
2841                             gameInfo.resultDetails = NULL;
2842                         }
2843                     }
2844                     HistorySet(parseList, backwardMostMove,
2845                                forwardMostMove, currentMove-1);
2846                     DisplayMove(currentMove - 1);
2847                     if (started == STARTED_MOVES) next_out = i;
2848                     started = STARTED_NONE;
2849                     ics_getting_history = H_FALSE;
2850                     break;
2851
2852                   case STARTED_OBSERVE:
2853                     started = STARTED_NONE;
2854                     SendToICS(ics_prefix);
2855                     SendToICS("refresh\n");
2856                     break;
2857
2858                   default:
2859                     break;
2860                 }
2861                 if(bookHit) { // [HGM] book: simulate book reply
2862                     static char bookMove[MSG_SIZ]; // a bit generous?
2863
2864                     programStats.nodes = programStats.depth = programStats.time = 
2865                     programStats.score = programStats.got_only_move = 0;
2866                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2867
2868                     strcpy(bookMove, "move ");
2869                     strcat(bookMove, bookHit);
2870                     HandleMachineMove(bookMove, &first);
2871                 }
2872                 continue;
2873             }
2874             
2875             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2876                  started == STARTED_HOLDINGS ||
2877                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2878                 /* Accumulate characters in move list or board */
2879                 parse[parse_pos++] = buf[i];
2880             }
2881             
2882             /* Start of game messages.  Mostly we detect start of game
2883                when the first board image arrives.  On some versions
2884                of the ICS, though, we need to do a "refresh" after starting
2885                to observe in order to get the current board right away. */
2886             if (looking_at(buf, &i, "Adding game * to observation list")) {
2887                 started = STARTED_OBSERVE;
2888                 continue;
2889             }
2890
2891             /* Handle auto-observe */
2892             if (appData.autoObserve &&
2893                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2894                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2895                 char *player;
2896                 /* Choose the player that was highlighted, if any. */
2897                 if (star_match[0][0] == '\033' ||
2898                     star_match[1][0] != '\033') {
2899                     player = star_match[0];
2900                 } else {
2901                     player = star_match[2];
2902                 }
2903                 sprintf(str, "%sobserve %s\n",
2904                         ics_prefix, StripHighlightAndTitle(player));
2905                 SendToICS(str);
2906
2907                 /* Save ratings from notify string */
2908                 strcpy(player1Name, star_match[0]);
2909                 player1Rating = string_to_rating(star_match[1]);
2910                 strcpy(player2Name, star_match[2]);
2911                 player2Rating = string_to_rating(star_match[3]);
2912
2913                 if (appData.debugMode)
2914                   fprintf(debugFP, 
2915                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2916                           player1Name, player1Rating,
2917                           player2Name, player2Rating);
2918
2919                 continue;
2920             }
2921
2922             /* Deal with automatic examine mode after a game,
2923                and with IcsObserving -> IcsExamining transition */
2924             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2925                 looking_at(buf, &i, "has made you an examiner of game *")) {
2926
2927                 int gamenum = atoi(star_match[0]);
2928                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2929                     gamenum == ics_gamenum) {
2930                     /* We were already playing or observing this game;
2931                        no need to refetch history */
2932                     gameMode = IcsExamining;
2933                     if (pausing) {
2934                         pauseExamForwardMostMove = forwardMostMove;
2935                     } else if (currentMove < forwardMostMove) {
2936                         ForwardInner(forwardMostMove);
2937                     }
2938                 } else {
2939                     /* I don't think this case really can happen */
2940                     SendToICS(ics_prefix);
2941                     SendToICS("refresh\n");
2942                 }
2943                 continue;
2944             }    
2945             
2946             /* Error messages */
2947 //          if (ics_user_moved) {
2948             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2949                 if (looking_at(buf, &i, "Illegal move") ||
2950                     looking_at(buf, &i, "Not a legal move") ||
2951                     looking_at(buf, &i, "Your king is in check") ||
2952                     looking_at(buf, &i, "It isn't your turn") ||
2953                     looking_at(buf, &i, "It is not your move")) {
2954                     /* Illegal move */
2955                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2956                         currentMove = --forwardMostMove;
2957                         DisplayMove(currentMove - 1); /* before DMError */
2958                         DrawPosition(FALSE, boards[currentMove]);
2959                         SwitchClocks();
2960                         DisplayBothClocks();
2961                     }
2962                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2963                     ics_user_moved = 0;
2964                     continue;
2965                 }
2966             }
2967
2968             if (looking_at(buf, &i, "still have time") ||
2969                 looking_at(buf, &i, "not out of time") ||
2970                 looking_at(buf, &i, "either player is out of time") ||
2971                 looking_at(buf, &i, "has timeseal; checking")) {
2972                 /* We must have called his flag a little too soon */
2973                 whiteFlag = blackFlag = FALSE;
2974                 continue;
2975             }
2976
2977             if (looking_at(buf, &i, "added * seconds to") ||
2978                 looking_at(buf, &i, "seconds were added to")) {
2979                 /* Update the clocks */
2980                 SendToICS(ics_prefix);
2981                 SendToICS("refresh\n");
2982                 continue;
2983             }
2984
2985             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2986                 ics_clock_paused = TRUE;
2987                 StopClocks();
2988                 continue;
2989             }
2990
2991             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2992                 ics_clock_paused = FALSE;
2993                 StartClocks();
2994                 continue;
2995             }
2996
2997             /* Grab player ratings from the Creating: message.
2998                Note we have to check for the special case when
2999                the ICS inserts things like [white] or [black]. */
3000             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3001                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3002                 /* star_matches:
3003                    0    player 1 name (not necessarily white)
3004                    1    player 1 rating
3005                    2    empty, white, or black (IGNORED)
3006                    3    player 2 name (not necessarily black)
3007                    4    player 2 rating
3008                    
3009                    The names/ratings are sorted out when the game
3010                    actually starts (below).
3011                 */
3012                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3013                 player1Rating = string_to_rating(star_match[1]);
3014                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3015                 player2Rating = string_to_rating(star_match[4]);
3016
3017                 if (appData.debugMode)
3018                   fprintf(debugFP, 
3019                           "Ratings from 'Creating:' %s %d, %s %d\n",
3020                           player1Name, player1Rating,
3021                           player2Name, player2Rating);
3022
3023                 continue;
3024             }
3025             
3026             /* Improved generic start/end-of-game messages */
3027             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3028                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3029                 /* If tkind == 0: */
3030                 /* star_match[0] is the game number */
3031                 /*           [1] is the white player's name */
3032                 /*           [2] is the black player's name */
3033                 /* For end-of-game: */
3034                 /*           [3] is the reason for the game end */
3035                 /*           [4] is a PGN end game-token, preceded by " " */
3036                 /* For start-of-game: */
3037                 /*           [3] begins with "Creating" or "Continuing" */
3038                 /*           [4] is " *" or empty (don't care). */
3039                 int gamenum = atoi(star_match[0]);
3040                 char *whitename, *blackname, *why, *endtoken;
3041                 ChessMove endtype = (ChessMove) 0;
3042
3043                 if (tkind == 0) {
3044                   whitename = star_match[1];
3045                   blackname = star_match[2];
3046                   why = star_match[3];
3047                   endtoken = star_match[4];
3048                 } else {
3049                   whitename = star_match[1];
3050                   blackname = star_match[3];
3051                   why = star_match[5];
3052                   endtoken = star_match[6];
3053                 }
3054
3055                 /* Game start messages */
3056                 if (strncmp(why, "Creating ", 9) == 0 ||
3057                     strncmp(why, "Continuing ", 11) == 0) {
3058                     gs_gamenum = gamenum;
3059                     strcpy(gs_kind, strchr(why, ' ') + 1);
3060 #if ZIPPY
3061                     if (appData.zippyPlay) {
3062                         ZippyGameStart(whitename, blackname);
3063                     }
3064 #endif /*ZIPPY*/
3065                     continue;
3066                 }
3067
3068                 /* Game end messages */
3069                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3070                     ics_gamenum != gamenum) {
3071                     continue;
3072                 }
3073                 while (endtoken[0] == ' ') endtoken++;
3074                 switch (endtoken[0]) {
3075                   case '*':
3076                   default:
3077                     endtype = GameUnfinished;
3078                     break;
3079                   case '0':
3080                     endtype = BlackWins;
3081                     break;
3082                   case '1':
3083                     if (endtoken[1] == '/')
3084                       endtype = GameIsDrawn;
3085                     else
3086                       endtype = WhiteWins;
3087                     break;
3088                 }
3089                 GameEnds(endtype, why, GE_ICS);
3090 #if ZIPPY
3091                 if (appData.zippyPlay && first.initDone) {
3092                     ZippyGameEnd(endtype, why);
3093                     if (first.pr == NULL) {
3094                       /* Start the next process early so that we'll
3095                          be ready for the next challenge */
3096                       StartChessProgram(&first);
3097                     }
3098                     /* Send "new" early, in case this command takes
3099                        a long time to finish, so that we'll be ready
3100                        for the next challenge. */
3101                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3102                     Reset(TRUE, TRUE);
3103                 }
3104 #endif /*ZIPPY*/
3105                 continue;
3106             }
3107
3108             if (looking_at(buf, &i, "Removing game * from observation") ||
3109                 looking_at(buf, &i, "no longer observing game *") ||
3110                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3111                 if (gameMode == IcsObserving &&
3112                     atoi(star_match[0]) == ics_gamenum)
3113                   {
3114                       /* icsEngineAnalyze */
3115                       if (appData.icsEngineAnalyze) {
3116                             ExitAnalyzeMode();
3117                             ModeHighlight();
3118                       }
3119                       StopClocks();
3120                       gameMode = IcsIdle;
3121                       ics_gamenum = -1;
3122                       ics_user_moved = FALSE;
3123                   }
3124                 continue;
3125             }
3126
3127             if (looking_at(buf, &i, "no longer examining game *")) {
3128                 if (gameMode == IcsExamining &&
3129                     atoi(star_match[0]) == ics_gamenum)
3130                   {
3131                       gameMode = IcsIdle;
3132                       ics_gamenum = -1;
3133                       ics_user_moved = FALSE;
3134                   }
3135                 continue;
3136             }
3137
3138             /* Advance leftover_start past any newlines we find,
3139                so only partial lines can get reparsed */
3140             if (looking_at(buf, &i, "\n")) {
3141                 prevColor = curColor;
3142                 if (curColor != ColorNormal) {
3143                     if (oldi > next_out) {
3144                         SendToPlayer(&buf[next_out], oldi - next_out);
3145                         next_out = oldi;
3146                     }
3147                     Colorize(ColorNormal, FALSE);
3148                     curColor = ColorNormal;
3149                 }
3150                 if (started == STARTED_BOARD) {
3151                     started = STARTED_NONE;
3152                     parse[parse_pos] = NULLCHAR;
3153                     ParseBoard12(parse);
3154                     ics_user_moved = 0;
3155
3156                     /* Send premove here */
3157                     if (appData.premove) {
3158                       char str[MSG_SIZ];
3159                       if (currentMove == 0 &&
3160                           gameMode == IcsPlayingWhite &&
3161                           appData.premoveWhite) {
3162                         sprintf(str, "%s%s\n", ics_prefix,
3163                                 appData.premoveWhiteText);
3164                         if (appData.debugMode)
3165                           fprintf(debugFP, "Sending premove:\n");
3166                         SendToICS(str);
3167                       } else if (currentMove == 1 &&
3168                                  gameMode == IcsPlayingBlack &&
3169                                  appData.premoveBlack) {
3170                         sprintf(str, "%s%s\n", ics_prefix,
3171                                 appData.premoveBlackText);
3172                         if (appData.debugMode)
3173                           fprintf(debugFP, "Sending premove:\n");
3174                         SendToICS(str);
3175                       } else if (gotPremove) {
3176                         gotPremove = 0;
3177                         ClearPremoveHighlights();
3178                         if (appData.debugMode)
3179                           fprintf(debugFP, "Sending premove:\n");
3180                           UserMoveEvent(premoveFromX, premoveFromY, 
3181                                         premoveToX, premoveToY, 
3182                                         premovePromoChar);
3183                       }
3184                     }
3185
3186                     /* Usually suppress following prompt */
3187                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3188                         if (looking_at(buf, &i, "*% ")) {
3189                             savingComment = FALSE;
3190                         }
3191                     }
3192                     next_out = i;
3193                 } else if (started == STARTED_HOLDINGS) {
3194                     int gamenum;
3195                     char new_piece[MSG_SIZ];
3196                     started = STARTED_NONE;
3197                     parse[parse_pos] = NULLCHAR;
3198                     if (appData.debugMode)
3199                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3200                                                         parse, currentMove);
3201                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3202                         gamenum == ics_gamenum) {
3203                         if (gameInfo.variant == VariantNormal) {
3204                           /* [HGM] We seem to switch variant during a game!
3205                            * Presumably no holdings were displayed, so we have
3206                            * to move the position two files to the right to
3207                            * create room for them!
3208                            */
3209                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3210                           /* Get a move list just to see the header, which
3211                              will tell us whether this is really bug or zh */
3212                           if (ics_getting_history == H_FALSE) {
3213                             ics_getting_history = H_REQUESTED;
3214                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3215                             SendToICS(str);
3216                           }
3217                         }
3218                         new_piece[0] = NULLCHAR;
3219                         sscanf(parse, "game %d white [%s black [%s <- %s",
3220                                &gamenum, white_holding, black_holding,
3221                                new_piece);
3222                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3223                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3224                         /* [HGM] copy holdings to board holdings area */
3225                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3226                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3227 #if ZIPPY
3228                         if (appData.zippyPlay && first.initDone) {
3229                             ZippyHoldings(white_holding, black_holding,
3230                                           new_piece);
3231                         }
3232 #endif /*ZIPPY*/
3233                         if (tinyLayout || smallLayout) {
3234                             char wh[16], bh[16];
3235                             PackHolding(wh, white_holding);
3236                             PackHolding(bh, black_holding);
3237                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3238                                     gameInfo.white, gameInfo.black);
3239                         } else {
3240                             sprintf(str, "%s [%s] vs. %s [%s]",
3241                                     gameInfo.white, white_holding,
3242                                     gameInfo.black, black_holding);
3243                         }
3244
3245                         DrawPosition(FALSE, boards[currentMove]);
3246                         DisplayTitle(str);
3247                     }
3248                     /* Suppress following prompt */
3249                     if (looking_at(buf, &i, "*% ")) {
3250                         savingComment = FALSE;
3251                     }
3252                     next_out = i;
3253                 }
3254                 continue;
3255             }
3256
3257             i++;                /* skip unparsed character and loop back */
3258         }
3259         
3260         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3261             started != STARTED_HOLDINGS && i > next_out) {
3262             SendToPlayer(&buf[next_out], i - next_out);
3263             next_out = i;
3264         }
3265         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3266         
3267         leftover_len = buf_len - leftover_start;
3268         /* if buffer ends with something we couldn't parse,
3269            reparse it after appending the next read */
3270         
3271     } else if (count == 0) {
3272         RemoveInputSource(isr);
3273         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3274     } else {
3275         DisplayFatalError(_("Error reading from ICS"), error, 1);
3276     }
3277 }
3278
3279
3280 /* Board style 12 looks like this:
3281    
3282    <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
3283    
3284  * The "<12> " is stripped before it gets to this routine.  The two
3285  * trailing 0's (flip state and clock ticking) are later addition, and
3286  * some chess servers may not have them, or may have only the first.
3287  * Additional trailing fields may be added in the future.  
3288  */
3289
3290 #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"
3291
3292 #define RELATION_OBSERVING_PLAYED    0
3293 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3294 #define RELATION_PLAYING_MYMOVE      1
3295 #define RELATION_PLAYING_NOTMYMOVE  -1
3296 #define RELATION_EXAMINING           2
3297 #define RELATION_ISOLATED_BOARD     -3
3298 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3299
3300 void
3301 ParseBoard12(string)
3302      char *string;
3303
3304     GameMode newGameMode;
3305     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3306     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3307     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3308     char to_play, board_chars[200];
3309     char move_str[500], str[500], elapsed_time[500];
3310     char black[32], white[32];
3311     Board board;
3312     int prevMove = currentMove;
3313     int ticking = 2;
3314     ChessMove moveType;
3315     int fromX, fromY, toX, toY;
3316     char promoChar;
3317     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3318     char *bookHit = NULL; // [HGM] book
3319
3320     fromX = fromY = toX = toY = -1;
3321     
3322     newGame = FALSE;
3323
3324     if (appData.debugMode)
3325       fprintf(debugFP, _("Parsing board: %s\n"), string);
3326
3327     move_str[0] = NULLCHAR;
3328     elapsed_time[0] = NULLCHAR;
3329     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3330         int  i = 0, j;
3331         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3332             if(string[i] == ' ') { ranks++; files = 0; }
3333             else files++;
3334             i++;
3335         }
3336         for(j = 0; j <i; j++) board_chars[j] = string[j];
3337         board_chars[i] = '\0';
3338         string += i + 1;
3339     }
3340     n = sscanf(string, PATTERN, &to_play, &double_push,
3341                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3342                &gamenum, white, black, &relation, &basetime, &increment,
3343                &white_stren, &black_stren, &white_time, &black_time,
3344                &moveNum, str, elapsed_time, move_str, &ics_flip,
3345                &ticking);
3346
3347     if (n < 21) {
3348         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3349         DisplayError(str, 0);
3350         return;
3351     }
3352
3353     /* Convert the move number to internal form */
3354     moveNum = (moveNum - 1) * 2;
3355     if (to_play == 'B') moveNum++;
3356     if (moveNum >= MAX_MOVES) {
3357       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3358                         0, 1);
3359       return;
3360     }
3361     
3362     switch (relation) {
3363       case RELATION_OBSERVING_PLAYED:
3364       case RELATION_OBSERVING_STATIC:
3365         if (gamenum == -1) {
3366             /* Old ICC buglet */
3367             relation = RELATION_OBSERVING_STATIC;
3368         }
3369         newGameMode = IcsObserving;
3370         break;
3371       case RELATION_PLAYING_MYMOVE:
3372       case RELATION_PLAYING_NOTMYMOVE:
3373         newGameMode =
3374           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3375             IcsPlayingWhite : IcsPlayingBlack;
3376         break;
3377       case RELATION_EXAMINING:
3378         newGameMode = IcsExamining;
3379         break;
3380       case RELATION_ISOLATED_BOARD:
3381       default:
3382         /* Just display this board.  If user was doing something else,
3383            we will forget about it until the next board comes. */ 
3384         newGameMode = IcsIdle;
3385         break;
3386       case RELATION_STARTING_POSITION:
3387         newGameMode = gameMode;
3388         break;
3389     }
3390     
3391     /* Modify behavior for initial board display on move listing
3392        of wild games.
3393        */
3394     switch (ics_getting_history) {
3395       case H_FALSE:
3396       case H_REQUESTED:
3397         break;
3398       case H_GOT_REQ_HEADER:
3399       case H_GOT_UNREQ_HEADER:
3400         /* This is the initial position of the current game */
3401         gamenum = ics_gamenum;
3402         moveNum = 0;            /* old ICS bug workaround */
3403         if (to_play == 'B') {
3404           startedFromSetupPosition = TRUE;
3405           blackPlaysFirst = TRUE;
3406           moveNum = 1;
3407           if (forwardMostMove == 0) forwardMostMove = 1;
3408           if (backwardMostMove == 0) backwardMostMove = 1;
3409           if (currentMove == 0) currentMove = 1;
3410         }
3411         newGameMode = gameMode;
3412         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3413         break;
3414       case H_GOT_UNWANTED_HEADER:
3415         /* This is an initial board that we don't want */
3416         return;
3417       case H_GETTING_MOVES:
3418         /* Should not happen */
3419         DisplayError(_("Error gathering move list: extra board"), 0);
3420         ics_getting_history = H_FALSE;
3421         return;
3422     }
3423     
3424     /* Take action if this is the first board of a new game, or of a
3425        different game than is currently being displayed.  */
3426     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3427         relation == RELATION_ISOLATED_BOARD) {
3428         
3429         /* Forget the old game and get the history (if any) of the new one */
3430         if (gameMode != BeginningOfGame) {
3431           Reset(FALSE, TRUE);
3432         }
3433         newGame = TRUE;
3434         if (appData.autoRaiseBoard) BoardToTop();
3435         prevMove = -3;
3436         if (gamenum == -1) {
3437             newGameMode = IcsIdle;
3438         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3439                    appData.getMoveList) {
3440             /* Need to get game history */
3441             ics_getting_history = H_REQUESTED;
3442             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3443             SendToICS(str);
3444         }
3445         
3446         /* Initially flip the board to have black on the bottom if playing
3447            black or if the ICS flip flag is set, but let the user change
3448            it with the Flip View button. */
3449         flipView = appData.autoFlipView ? 
3450           (newGameMode == IcsPlayingBlack) || ics_flip :
3451           appData.flipView;
3452         
3453         /* Done with values from previous mode; copy in new ones */
3454         gameMode = newGameMode;
3455         ModeHighlight();
3456         ics_gamenum = gamenum;
3457         if (gamenum == gs_gamenum) {
3458             int klen = strlen(gs_kind);
3459             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3460             sprintf(str, "ICS %s", gs_kind);
3461             gameInfo.event = StrSave(str);
3462         } else {
3463             gameInfo.event = StrSave("ICS game");
3464         }
3465         gameInfo.site = StrSave(appData.icsHost);
3466         gameInfo.date = PGNDate();
3467         gameInfo.round = StrSave("-");
3468         gameInfo.white = StrSave(white);
3469         gameInfo.black = StrSave(black);
3470         timeControl = basetime * 60 * 1000;
3471         timeControl_2 = 0;
3472         timeIncrement = increment * 1000;
3473         movesPerSession = 0;
3474         gameInfo.timeControl = TimeControlTagValue();
3475         VariantSwitch(board, StringToVariant(gameInfo.event) );
3476   if (appData.debugMode) {
3477     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3478     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3479     setbuf(debugFP, NULL);
3480   }
3481
3482         gameInfo.outOfBook = NULL;
3483         
3484         /* Do we have the ratings? */
3485         if (strcmp(player1Name, white) == 0 &&
3486             strcmp(player2Name, black) == 0) {
3487             if (appData.debugMode)
3488               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3489                       player1Rating, player2Rating);
3490             gameInfo.whiteRating = player1Rating;
3491             gameInfo.blackRating = player2Rating;
3492         } else if (strcmp(player2Name, white) == 0 &&
3493                    strcmp(player1Name, black) == 0) {
3494             if (appData.debugMode)
3495               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3496                       player2Rating, player1Rating);
3497             gameInfo.whiteRating = player2Rating;
3498             gameInfo.blackRating = player1Rating;
3499         }
3500         player1Name[0] = player2Name[0] = NULLCHAR;
3501
3502         /* Silence shouts if requested */
3503         if (appData.quietPlay &&
3504             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3505             SendToICS(ics_prefix);
3506             SendToICS("set shout 0\n");
3507         }
3508     }
3509     
3510     /* Deal with midgame name changes */
3511     if (!newGame) {
3512         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3513             if (gameInfo.white) free(gameInfo.white);
3514             gameInfo.white = StrSave(white);
3515         }
3516         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3517             if (gameInfo.black) free(gameInfo.black);
3518             gameInfo.black = StrSave(black);
3519         }
3520     }
3521     
3522     /* Throw away game result if anything actually changes in examine mode */
3523     if (gameMode == IcsExamining && !newGame) {
3524         gameInfo.result = GameUnfinished;
3525         if (gameInfo.resultDetails != NULL) {
3526             free(gameInfo.resultDetails);
3527             gameInfo.resultDetails = NULL;
3528         }
3529     }
3530     
3531     /* In pausing && IcsExamining mode, we ignore boards coming
3532        in if they are in a different variation than we are. */
3533     if (pauseExamInvalid) return;
3534     if (pausing && gameMode == IcsExamining) {
3535         if (moveNum <= pauseExamForwardMostMove) {
3536             pauseExamInvalid = TRUE;
3537             forwardMostMove = pauseExamForwardMostMove;
3538             return;
3539         }
3540     }
3541     
3542   if (appData.debugMode) {
3543     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3544   }
3545     /* Parse the board */
3546     for (k = 0; k < ranks; k++) {
3547       for (j = 0; j < files; j++)
3548         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3549       if(gameInfo.holdingsWidth > 1) {
3550            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3551            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3552       }
3553     }
3554     CopyBoard(boards[moveNum], board);
3555     if (moveNum == 0) {
3556         startedFromSetupPosition =
3557           !CompareBoards(board, initialPosition);
3558         if(startedFromSetupPosition)
3559             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3560     }
3561
3562     /* [HGM] Set castling rights. Take the outermost Rooks,
3563        to make it also work for FRC opening positions. Note that board12
3564        is really defective for later FRC positions, as it has no way to
3565        indicate which Rook can castle if they are on the same side of King.
3566        For the initial position we grant rights to the outermost Rooks,
3567        and remember thos rights, and we then copy them on positions
3568        later in an FRC game. This means WB might not recognize castlings with
3569        Rooks that have moved back to their original position as illegal,
3570        but in ICS mode that is not its job anyway.
3571     */
3572     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3573     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3574
3575         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3576             if(board[0][i] == WhiteRook) j = i;
3577         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3578         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3579             if(board[0][i] == WhiteRook) j = i;
3580         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3581         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3582             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3583         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3584         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3585             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3586         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3587
3588         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3589         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3590             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3591         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3592             if(board[BOARD_HEIGHT-1][k] == bKing)
3593                 initialRights[5] = castlingRights[moveNum][5] = k;
3594     } else { int r;
3595         r = castlingRights[moveNum][0] = initialRights[0];
3596         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3597         r = castlingRights[moveNum][1] = initialRights[1];
3598         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3599         r = castlingRights[moveNum][3] = initialRights[3];
3600         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3601         r = castlingRights[moveNum][4] = initialRights[4];
3602         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3603         /* wildcastle kludge: always assume King has rights */
3604         r = castlingRights[moveNum][2] = initialRights[2];
3605         r = castlingRights[moveNum][5] = initialRights[5];
3606     }
3607     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3608     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3609
3610     
3611     if (ics_getting_history == H_GOT_REQ_HEADER ||
3612         ics_getting_history == H_GOT_UNREQ_HEADER) {
3613         /* This was an initial position from a move list, not
3614            the current position */
3615         return;
3616     }
3617     
3618     /* Update currentMove and known move number limits */
3619     newMove = newGame || moveNum > forwardMostMove;
3620
3621     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3622     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3623         takeback = forwardMostMove - moveNum;
3624         for (i = 0; i < takeback; i++) {
3625              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3626              SendToProgram("undo\n", &first);
3627         }
3628     }
3629
3630     if (newGame) {
3631         forwardMostMove = backwardMostMove = currentMove = moveNum;
3632         if (gameMode == IcsExamining && moveNum == 0) {
3633           /* Workaround for ICS limitation: we are not told the wild
3634              type when starting to examine a game.  But if we ask for
3635              the move list, the move list header will tell us */
3636             ics_getting_history = H_REQUESTED;
3637             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3638             SendToICS(str);
3639         }
3640     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3641                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3642         forwardMostMove = moveNum;
3643         if (!pausing || currentMove > forwardMostMove)
3644           currentMove = forwardMostMove;
3645     } else {
3646         /* New part of history that is not contiguous with old part */ 
3647         if (pausing && gameMode == IcsExamining) {
3648             pauseExamInvalid = TRUE;
3649             forwardMostMove = pauseExamForwardMostMove;
3650             return;
3651         }
3652         forwardMostMove = backwardMostMove = currentMove = moveNum;
3653         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3654             ics_getting_history = H_REQUESTED;
3655             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3656             SendToICS(str);
3657         }
3658     }
3659     
3660     /* Update the clocks */
3661     if (strchr(elapsed_time, '.')) {
3662       /* Time is in ms */
3663       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3664       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3665     } else {
3666       /* Time is in seconds */
3667       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3668       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3669     }
3670       
3671
3672 #if ZIPPY
3673     if (appData.zippyPlay && newGame &&
3674         gameMode != IcsObserving && gameMode != IcsIdle &&
3675         gameMode != IcsExamining)
3676       ZippyFirstBoard(moveNum, basetime, increment);
3677 #endif
3678     
3679     /* Put the move on the move list, first converting
3680        to canonical algebraic form. */
3681     if (moveNum > 0) {
3682   if (appData.debugMode) {
3683     if (appData.debugMode) { int f = forwardMostMove;
3684         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3685                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3686     }
3687     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3688     fprintf(debugFP, "moveNum = %d\n", moveNum);
3689     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3690     setbuf(debugFP, NULL);
3691   }
3692         if (moveNum <= backwardMostMove) {
3693             /* We don't know what the board looked like before
3694                this move.  Punt. */
3695             strcpy(parseList[moveNum - 1], move_str);
3696             strcat(parseList[moveNum - 1], " ");
3697             strcat(parseList[moveNum - 1], elapsed_time);
3698             moveList[moveNum - 1][0] = NULLCHAR;
3699         } else if (strcmp(move_str, "none") == 0) {
3700             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3701             /* Again, we don't know what the board looked like;
3702                this is really the start of the game. */
3703             parseList[moveNum - 1][0] = NULLCHAR;
3704             moveList[moveNum - 1][0] = NULLCHAR;
3705             backwardMostMove = moveNum;
3706             startedFromSetupPosition = TRUE;
3707             fromX = fromY = toX = toY = -1;
3708         } else {
3709           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3710           //                 So we parse the long-algebraic move string in stead of the SAN move
3711           int valid; char buf[MSG_SIZ], *prom;
3712
3713           // str looks something like "Q/a1-a2"; kill the slash
3714           if(str[1] == '/') 
3715                 sprintf(buf, "%c%s", str[0], str+2);
3716           else  strcpy(buf, str); // might be castling
3717           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3718                 strcat(buf, prom); // long move lacks promo specification!
3719           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3720                 if(appData.debugMode) 
3721                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3722                 strcpy(move_str, buf);
3723           }
3724           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3725                                 &fromX, &fromY, &toX, &toY, &promoChar)
3726                || ParseOneMove(buf, moveNum - 1, &moveType,
3727                                 &fromX, &fromY, &toX, &toY, &promoChar);
3728           // end of long SAN patch
3729           if (valid) {
3730             (void) CoordsToAlgebraic(boards[moveNum - 1],
3731                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3732                                      fromY, fromX, toY, toX, promoChar,
3733                                      parseList[moveNum-1]);
3734             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3735                              castlingRights[moveNum]) ) {
3736               case MT_NONE:
3737               case MT_STALEMATE:
3738               default:
3739                 break;
3740               case MT_CHECK:
3741                 if(gameInfo.variant != VariantShogi)
3742                     strcat(parseList[moveNum - 1], "+");
3743                 break;
3744               case MT_CHECKMATE:
3745               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3746                 strcat(parseList[moveNum - 1], "#");
3747                 break;
3748             }
3749             strcat(parseList[moveNum - 1], " ");
3750             strcat(parseList[moveNum - 1], elapsed_time);
3751             /* currentMoveString is set as a side-effect of ParseOneMove */
3752             strcpy(moveList[moveNum - 1], currentMoveString);
3753             strcat(moveList[moveNum - 1], "\n");
3754           } else {
3755             /* Move from ICS was illegal!?  Punt. */
3756   if (appData.debugMode) {
3757     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3758     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3759   }
3760             strcpy(parseList[moveNum - 1], move_str);
3761             strcat(parseList[moveNum - 1], " ");
3762             strcat(parseList[moveNum - 1], elapsed_time);
3763             moveList[moveNum - 1][0] = NULLCHAR;
3764             fromX = fromY = toX = toY = -1;
3765           }
3766         }
3767   if (appData.debugMode) {
3768     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3769     setbuf(debugFP, NULL);
3770   }
3771
3772 #if ZIPPY
3773         /* Send move to chess program (BEFORE animating it). */
3774         if (appData.zippyPlay && !newGame && newMove && 
3775            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3776
3777             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3778                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3779                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3780                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3781                             move_str);
3782                     DisplayError(str, 0);
3783                 } else {
3784                     if (first.sendTime) {
3785                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3786                     }
3787                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3788                     if (firstMove && !bookHit) {
3789                         firstMove = FALSE;
3790                         if (first.useColors) {
3791                           SendToProgram(gameMode == IcsPlayingWhite ?
3792                                         "white\ngo\n" :
3793                                         "black\ngo\n", &first);
3794                         } else {
3795                           SendToProgram("go\n", &first);
3796                         }
3797                         first.maybeThinking = TRUE;
3798                     }
3799                 }
3800             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3801               if (moveList[moveNum - 1][0] == NULLCHAR) {
3802                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3803                 DisplayError(str, 0);
3804               } else {
3805                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3806                 SendMoveToProgram(moveNum - 1, &first);
3807               }
3808             }
3809         }
3810 #endif
3811     }
3812
3813     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3814         /* If move comes from a remote source, animate it.  If it
3815            isn't remote, it will have already been animated. */
3816         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3817             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3818         }
3819         if (!pausing && appData.highlightLastMove) {
3820             SetHighlights(fromX, fromY, toX, toY);
3821         }
3822     }
3823     
3824     /* Start the clocks */
3825     whiteFlag = blackFlag = FALSE;
3826     appData.clockMode = !(basetime == 0 && increment == 0);
3827     if (ticking == 0) {
3828       ics_clock_paused = TRUE;
3829       StopClocks();
3830     } else if (ticking == 1) {
3831       ics_clock_paused = FALSE;
3832     }
3833     if (gameMode == IcsIdle ||
3834         relation == RELATION_OBSERVING_STATIC ||
3835         relation == RELATION_EXAMINING ||
3836         ics_clock_paused)
3837       DisplayBothClocks();
3838     else
3839       StartClocks();
3840     
3841     /* Display opponents and material strengths */
3842     if (gameInfo.variant != VariantBughouse &&
3843         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3844         if (tinyLayout || smallLayout) {
3845             if(gameInfo.variant == VariantNormal)
3846                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3847                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3848                     basetime, increment);
3849             else
3850                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3851                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3852                     basetime, increment, (int) gameInfo.variant);
3853         } else {
3854             if(gameInfo.variant == VariantNormal)
3855                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3856                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3857                     basetime, increment);
3858             else
3859                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3860                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3861                     basetime, increment, VariantName(gameInfo.variant));
3862         }
3863         DisplayTitle(str);
3864   if (appData.debugMode) {
3865     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3866   }
3867     }
3868
3869    
3870     /* Display the board */
3871     if (!pausing && !appData.noGUI) {
3872       
3873       if (appData.premove)
3874           if (!gotPremove || 
3875              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3876              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3877               ClearPremoveHighlights();
3878
3879       DrawPosition(FALSE, boards[currentMove]);
3880       DisplayMove(moveNum - 1);
3881       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3882             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3883               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3884         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3885       }
3886     }
3887
3888     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3889 #if ZIPPY
3890     if(bookHit) { // [HGM] book: simulate book reply
3891         static char bookMove[MSG_SIZ]; // a bit generous?
3892
3893         programStats.nodes = programStats.depth = programStats.time = 
3894         programStats.score = programStats.got_only_move = 0;
3895         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3896
3897         strcpy(bookMove, "move ");
3898         strcat(bookMove, bookHit);
3899         HandleMachineMove(bookMove, &first);
3900     }
3901 #endif
3902 }
3903
3904 void
3905 GetMoveListEvent()
3906 {
3907     char buf[MSG_SIZ];
3908     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3909         ics_getting_history = H_REQUESTED;
3910         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3911         SendToICS(buf);
3912     }
3913 }
3914
3915 void
3916 AnalysisPeriodicEvent(force)
3917      int force;
3918 {
3919     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3920          && !force) || !appData.periodicUpdates)
3921       return;
3922
3923     /* Send . command to Crafty to collect stats */
3924     SendToProgram(".\n", &first);
3925
3926     /* Don't send another until we get a response (this makes
3927        us stop sending to old Crafty's which don't understand
3928        the "." command (sending illegal cmds resets node count & time,
3929        which looks bad)) */
3930     programStats.ok_to_send = 0;
3931 }
3932
3933 void
3934 SendMoveToProgram(moveNum, cps)
3935      int moveNum;
3936      ChessProgramState *cps;
3937 {
3938     char buf[MSG_SIZ];
3939
3940     if (cps->useUsermove) {
3941       SendToProgram("usermove ", cps);
3942     }
3943     if (cps->useSAN) {
3944       char *space;
3945       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3946         int len = space - parseList[moveNum];
3947         memcpy(buf, parseList[moveNum], len);
3948         buf[len++] = '\n';
3949         buf[len] = NULLCHAR;
3950       } else {
3951         sprintf(buf, "%s\n", parseList[moveNum]);
3952       }
3953       SendToProgram(buf, cps);
3954     } else {
3955       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3956         AlphaRank(moveList[moveNum], 4);
3957         SendToProgram(moveList[moveNum], cps);
3958         AlphaRank(moveList[moveNum], 4); // and back
3959       } else
3960       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3961        * the engine. It would be nice to have a better way to identify castle 
3962        * moves here. */
3963       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3964                                                                          && cps->useOOCastle) {
3965         int fromX = moveList[moveNum][0] - AAA; 
3966         int fromY = moveList[moveNum][1] - ONE;
3967         int toX = moveList[moveNum][2] - AAA; 
3968         int toY = moveList[moveNum][3] - ONE;
3969         if((boards[moveNum][fromY][fromX] == WhiteKing 
3970             && boards[moveNum][toY][toX] == WhiteRook)
3971            || (boards[moveNum][fromY][fromX] == BlackKing 
3972                && boards[moveNum][toY][toX] == BlackRook)) {
3973           if(toX > fromX) SendToProgram("O-O\n", cps);
3974           else SendToProgram("O-O-O\n", cps);
3975         }
3976         else SendToProgram(moveList[moveNum], cps);
3977       }
3978       else SendToProgram(moveList[moveNum], cps);
3979       /* End of additions by Tord */
3980     }
3981
3982     /* [HGM] setting up the opening has brought engine in force mode! */
3983     /*       Send 'go' if we are in a mode where machine should play. */
3984     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
3985         (gameMode == TwoMachinesPlay   ||
3986 #ifdef ZIPPY
3987          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
3988 #endif
3989          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
3990         SendToProgram("go\n", cps);
3991   if (appData.debugMode) {
3992     fprintf(debugFP, "(extra)\n");
3993   }
3994     }
3995     setboardSpoiledMachineBlack = 0;
3996 }
3997
3998 void
3999 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4000      ChessMove moveType;
4001      int fromX, fromY, toX, toY;
4002 {
4003     char user_move[MSG_SIZ];
4004
4005     switch (moveType) {
4006       default:
4007         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4008                 (int)moveType, fromX, fromY, toX, toY);
4009         DisplayError(user_move + strlen("say "), 0);
4010         break;
4011       case WhiteKingSideCastle:
4012       case BlackKingSideCastle:
4013       case WhiteQueenSideCastleWild:
4014       case BlackQueenSideCastleWild:
4015       /* PUSH Fabien */
4016       case WhiteHSideCastleFR:
4017       case BlackHSideCastleFR:
4018       /* POP Fabien */
4019         sprintf(user_move, "o-o\n");
4020         break;
4021       case WhiteQueenSideCastle:
4022       case BlackQueenSideCastle:
4023       case WhiteKingSideCastleWild:
4024       case BlackKingSideCastleWild:
4025       /* PUSH Fabien */
4026       case WhiteASideCastleFR:
4027       case BlackASideCastleFR:
4028       /* POP Fabien */
4029         sprintf(user_move, "o-o-o\n");
4030         break;
4031       case WhitePromotionQueen:
4032       case BlackPromotionQueen:
4033       case WhitePromotionRook:
4034       case BlackPromotionRook:
4035       case WhitePromotionBishop:
4036       case BlackPromotionBishop:
4037       case WhitePromotionKnight:
4038       case BlackPromotionKnight:
4039       case WhitePromotionKing:
4040       case BlackPromotionKing:
4041       case WhitePromotionChancellor:
4042       case BlackPromotionChancellor:
4043       case WhitePromotionArchbishop:
4044       case BlackPromotionArchbishop:
4045         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4046             sprintf(user_move, "%c%c%c%c=%c\n",
4047                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4048                 PieceToChar(WhiteFerz));
4049         else if(gameInfo.variant == VariantGreat)
4050             sprintf(user_move, "%c%c%c%c=%c\n",
4051                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4052                 PieceToChar(WhiteMan));
4053         else
4054             sprintf(user_move, "%c%c%c%c=%c\n",
4055                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4056                 PieceToChar(PromoPiece(moveType)));
4057         break;
4058       case WhiteDrop:
4059       case BlackDrop:
4060         sprintf(user_move, "%c@%c%c\n",
4061                 ToUpper(PieceToChar((ChessSquare) fromX)),
4062                 AAA + toX, ONE + toY);
4063         break;
4064       case NormalMove:
4065       case WhiteCapturesEnPassant:
4066       case BlackCapturesEnPassant:
4067       case IllegalMove:  /* could be a variant we don't quite understand */
4068         sprintf(user_move, "%c%c%c%c\n",
4069                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4070         break;
4071     }
4072     SendToICS(user_move);
4073     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4074         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4075 }
4076
4077 void
4078 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4079      int rf, ff, rt, ft;
4080      char promoChar;
4081      char move[7];
4082 {
4083     if (rf == DROP_RANK) {
4084         sprintf(move, "%c@%c%c\n",
4085                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4086     } else {
4087         if (promoChar == 'x' || promoChar == NULLCHAR) {
4088             sprintf(move, "%c%c%c%c\n",
4089                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4090         } else {
4091             sprintf(move, "%c%c%c%c%c\n",
4092                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4093         }
4094     }
4095 }
4096
4097 void
4098 ProcessICSInitScript(f)
4099      FILE *f;
4100 {
4101     char buf[MSG_SIZ];
4102
4103     while (fgets(buf, MSG_SIZ, f)) {
4104         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4105     }
4106
4107     fclose(f);
4108 }
4109
4110
4111 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4112 void
4113 AlphaRank(char *move, int n)
4114 {
4115 //    char *p = move, c; int x, y;
4116
4117     if (appData.debugMode) {
4118         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4119     }
4120
4121     if(move[1]=='*' && 
4122        move[2]>='0' && move[2]<='9' &&
4123        move[3]>='a' && move[3]<='x'    ) {
4124         move[1] = '@';
4125         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4126         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4127     } else
4128     if(move[0]>='0' && move[0]<='9' &&
4129        move[1]>='a' && move[1]<='x' &&
4130        move[2]>='0' && move[2]<='9' &&
4131        move[3]>='a' && move[3]<='x'    ) {
4132         /* input move, Shogi -> normal */
4133         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4134         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4135         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4136         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4137     } else
4138     if(move[1]=='@' &&
4139        move[3]>='0' && move[3]<='9' &&
4140        move[2]>='a' && move[2]<='x'    ) {
4141         move[1] = '*';
4142         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4143         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4144     } else
4145     if(
4146        move[0]>='a' && move[0]<='x' &&
4147        move[3]>='0' && move[3]<='9' &&
4148        move[2]>='a' && move[2]<='x'    ) {
4149          /* output move, normal -> Shogi */
4150         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4151         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4152         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4153         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4154         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4155     }
4156     if (appData.debugMode) {
4157         fprintf(debugFP, "   out = '%s'\n", move);
4158     }
4159 }
4160
4161 /* Parser for moves from gnuchess, ICS, or user typein box */
4162 Boolean
4163 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4164      char *move;
4165      int moveNum;
4166      ChessMove *moveType;
4167      int *fromX, *fromY, *toX, *toY;
4168      char *promoChar;
4169 {       
4170     if (appData.debugMode) {
4171         fprintf(debugFP, "move to parse: %s\n", move);
4172     }
4173     *moveType = yylexstr(moveNum, move);
4174
4175     switch (*moveType) {
4176       case WhitePromotionChancellor:
4177       case BlackPromotionChancellor:
4178       case WhitePromotionArchbishop:
4179       case BlackPromotionArchbishop:
4180       case WhitePromotionQueen:
4181       case BlackPromotionQueen:
4182       case WhitePromotionRook:
4183       case BlackPromotionRook:
4184       case WhitePromotionBishop:
4185       case BlackPromotionBishop:
4186       case WhitePromotionKnight:
4187       case BlackPromotionKnight:
4188       case WhitePromotionKing:
4189       case BlackPromotionKing:
4190       case NormalMove:
4191       case WhiteCapturesEnPassant:
4192       case BlackCapturesEnPassant:
4193       case WhiteKingSideCastle:
4194       case WhiteQueenSideCastle:
4195       case BlackKingSideCastle:
4196       case BlackQueenSideCastle:
4197       case WhiteKingSideCastleWild:
4198       case WhiteQueenSideCastleWild:
4199       case BlackKingSideCastleWild:
4200       case BlackQueenSideCastleWild:
4201       /* Code added by Tord: */
4202       case WhiteHSideCastleFR:
4203       case WhiteASideCastleFR:
4204       case BlackHSideCastleFR:
4205       case BlackASideCastleFR:
4206       /* End of code added by Tord */
4207       case IllegalMove:         /* bug or odd chess variant */
4208         *fromX = currentMoveString[0] - AAA;
4209         *fromY = currentMoveString[1] - ONE;
4210         *toX = currentMoveString[2] - AAA;
4211         *toY = currentMoveString[3] - ONE;
4212         *promoChar = currentMoveString[4];
4213         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4214             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4215     if (appData.debugMode) {
4216         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4217     }
4218             *fromX = *fromY = *toX = *toY = 0;
4219             return FALSE;
4220         }
4221         if (appData.testLegality) {
4222           return (*moveType != IllegalMove);
4223         } else {
4224           return !(fromX == fromY && toX == toY);
4225         }
4226
4227       case WhiteDrop:
4228       case BlackDrop:
4229         *fromX = *moveType == WhiteDrop ?
4230           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4231           (int) CharToPiece(ToLower(currentMoveString[0]));
4232         *fromY = DROP_RANK;
4233         *toX = currentMoveString[2] - AAA;
4234         *toY = currentMoveString[3] - ONE;
4235         *promoChar = NULLCHAR;
4236         return TRUE;
4237
4238       case AmbiguousMove:
4239       case ImpossibleMove:
4240       case (ChessMove) 0:       /* end of file */
4241       case ElapsedTime:
4242       case Comment:
4243       case PGNTag:
4244       case NAG:
4245       case WhiteWins:
4246       case BlackWins:
4247       case GameIsDrawn:
4248       default:
4249     if (appData.debugMode) {
4250         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4251     }
4252         /* bug? */
4253         *fromX = *fromY = *toX = *toY = 0;
4254         *promoChar = NULLCHAR;
4255         return FALSE;
4256     }
4257 }
4258
4259 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4260 // All positions will have equal probability, but the current method will not provide a unique
4261 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4262 #define DARK 1
4263 #define LITE 2
4264 #define ANY 3
4265
4266 int squaresLeft[4];
4267 int piecesLeft[(int)BlackPawn];
4268 int seed, nrOfShuffles;
4269
4270 void GetPositionNumber()
4271 {       // sets global variable seed
4272         int i;
4273
4274         seed = appData.defaultFrcPosition;
4275         if(seed < 0) { // randomize based on time for negative FRC position numbers
4276                 for(i=0; i<50; i++) seed += random();
4277                 seed = random() ^ random() >> 8 ^ random() << 8;
4278                 if(seed<0) seed = -seed;
4279         }
4280 }
4281
4282 int put(Board board, int pieceType, int rank, int n, int shade)
4283 // put the piece on the (n-1)-th empty squares of the given shade
4284 {
4285         int i;
4286
4287         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4288                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4289                         board[rank][i] = (ChessSquare) pieceType;
4290                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4291                         squaresLeft[ANY]--;
4292                         piecesLeft[pieceType]--; 
4293                         return i;
4294                 }
4295         }
4296         return -1;
4297 }
4298
4299
4300 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4301 // calculate where the next piece goes, (any empty square), and put it there
4302 {
4303         int i;
4304
4305         i = seed % squaresLeft[shade];
4306         nrOfShuffles *= squaresLeft[shade];
4307         seed /= squaresLeft[shade];
4308         put(board, pieceType, rank, i, shade);
4309 }
4310
4311 void AddTwoPieces(Board board, int pieceType, int rank)
4312 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4313 {
4314         int i, n=squaresLeft[ANY], j=n-1, k;
4315
4316         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4317         i = seed % k;  // pick one
4318         nrOfShuffles *= k;
4319         seed /= k;
4320         while(i >= j) i -= j--;
4321         j = n - 1 - j; i += j;
4322         put(board, pieceType, rank, j, ANY);
4323         put(board, pieceType, rank, i, ANY);
4324 }
4325
4326 void SetUpShuffle(Board board, int number)
4327 {
4328         int i, p, first=1;
4329
4330         GetPositionNumber(); nrOfShuffles = 1;
4331
4332         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4333         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4334         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4335
4336         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4337
4338         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4339             p = (int) board[0][i];
4340             if(p < (int) BlackPawn) piecesLeft[p] ++;
4341             board[0][i] = EmptySquare;
4342         }
4343
4344         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4345             // shuffles restricted to allow normal castling put KRR first
4346             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4347                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4348             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4349                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4350             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4351                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4352             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4353                 put(board, WhiteRook, 0, 0, ANY);
4354             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4355         }
4356
4357         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4358             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4359             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4360                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4361                 while(piecesLeft[p] >= 2) {
4362                     AddOnePiece(board, p, 0, LITE);
4363                     AddOnePiece(board, p, 0, DARK);
4364                 }
4365                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4366             }
4367
4368         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4369             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4370             // but we leave King and Rooks for last, to possibly obey FRC restriction
4371             if(p == (int)WhiteRook) continue;
4372             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4373             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4374         }
4375
4376         // now everything is placed, except perhaps King (Unicorn) and Rooks
4377
4378         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4379             // Last King gets castling rights
4380             while(piecesLeft[(int)WhiteUnicorn]) {
4381                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4382                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4383             }
4384
4385             while(piecesLeft[(int)WhiteKing]) {
4386                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4387                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4388             }
4389
4390
4391         } else {
4392             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4393             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4394         }
4395
4396         // Only Rooks can be left; simply place them all
4397         while(piecesLeft[(int)WhiteRook]) {
4398                 i = put(board, WhiteRook, 0, 0, ANY);
4399                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4400                         if(first) {
4401                                 first=0;
4402                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4403                         }
4404                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4405                 }
4406         }
4407         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4408             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4409         }
4410
4411         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4412 }
4413
4414 int SetCharTable( char *table, const char * map )
4415 /* [HGM] moved here from winboard.c because of its general usefulness */
4416 /*       Basically a safe strcpy that uses the last character as King */
4417 {
4418     int result = FALSE; int NrPieces;
4419
4420     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4421                     && NrPieces >= 12 && !(NrPieces&1)) {
4422         int i; /* [HGM] Accept even length from 12 to 34 */
4423
4424         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4425         for( i=0; i<NrPieces/2-1; i++ ) {
4426             table[i] = map[i];
4427             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4428         }
4429         table[(int) WhiteKing]  = map[NrPieces/2-1];
4430         table[(int) BlackKing]  = map[NrPieces-1];
4431
4432         result = TRUE;
4433     }
4434
4435     return result;
4436 }
4437
4438 void Prelude(Board board)
4439 {       // [HGM] superchess: random selection of exo-pieces
4440         int i, j, k; ChessSquare p; 
4441         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4442
4443         GetPositionNumber(); // use FRC position number
4444
4445         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4446             SetCharTable(pieceToChar, appData.pieceToCharTable);
4447             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4448                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4449         }
4450
4451         j = seed%4;                 seed /= 4; 
4452         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4453         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4454         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4455         j = seed%3 + (seed%3 >= j); seed /= 3; 
4456         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4457         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4458         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4459         j = seed%3;                 seed /= 3; 
4460         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4461         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4462         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4463         j = seed%2 + (seed%2 >= j); seed /= 2; 
4464         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4465         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4466         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4467         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4468         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4469         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4470         put(board, exoPieces[0],    0, 0, ANY);
4471         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4472 }
4473
4474 void
4475 InitPosition(redraw)
4476      int redraw;
4477 {
4478     ChessSquare (* pieces)[BOARD_SIZE];
4479     int i, j, pawnRow, overrule,
4480     oldx = gameInfo.boardWidth,
4481     oldy = gameInfo.boardHeight,
4482     oldh = gameInfo.holdingsWidth,
4483     oldv = gameInfo.variant;
4484
4485     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4486
4487     /* [AS] Initialize pv info list [HGM] and game status */
4488     {
4489         for( i=0; i<MAX_MOVES; i++ ) {
4490             pvInfoList[i].depth = 0;
4491             epStatus[i]=EP_NONE;
4492             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4493         }
4494
4495         initialRulePlies = 0; /* 50-move counter start */
4496
4497         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4498         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4499     }
4500
4501     
4502     /* [HGM] logic here is completely changed. In stead of full positions */
4503     /* the initialized data only consist of the two backranks. The switch */
4504     /* selects which one we will use, which is than copied to the Board   */
4505     /* initialPosition, which for the rest is initialized by Pawns and    */
4506     /* empty squares. This initial position is then copied to boards[0],  */
4507     /* possibly after shuffling, so that it remains available.            */
4508
4509     gameInfo.holdingsWidth = 0; /* default board sizes */
4510     gameInfo.boardWidth    = 8;
4511     gameInfo.boardHeight   = 8;
4512     gameInfo.holdingsSize  = 0;
4513     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4514     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4515     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4516
4517     switch (gameInfo.variant) {
4518     case VariantFischeRandom:
4519       shuffleOpenings = TRUE;
4520     default:
4521       pieces = FIDEArray;
4522       break;
4523     case VariantShatranj:
4524       pieces = ShatranjArray;
4525       nrCastlingRights = 0;
4526       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4527       break;
4528     case VariantTwoKings:
4529       pieces = twoKingsArray;
4530       break;
4531     case VariantCapaRandom:
4532       shuffleOpenings = TRUE;
4533     case VariantCapablanca:
4534       pieces = CapablancaArray;
4535       gameInfo.boardWidth = 10;
4536       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4537       break;
4538     case VariantGothic:
4539       pieces = GothicArray;
4540       gameInfo.boardWidth = 10;
4541       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4542       break;
4543     case VariantJanus:
4544       pieces = JanusArray;
4545       gameInfo.boardWidth = 10;
4546       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4547       nrCastlingRights = 6;
4548         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4549         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4550         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4551         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4552         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4553         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4554       break;
4555     case VariantFalcon:
4556       pieces = FalconArray;
4557       gameInfo.boardWidth = 10;
4558       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4559       break;
4560     case VariantXiangqi:
4561       pieces = XiangqiArray;
4562       gameInfo.boardWidth  = 9;
4563       gameInfo.boardHeight = 10;
4564       nrCastlingRights = 0;
4565       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4566       break;
4567     case VariantShogi:
4568       pieces = ShogiArray;
4569       gameInfo.boardWidth  = 9;
4570       gameInfo.boardHeight = 9;
4571       gameInfo.holdingsSize = 7;
4572       nrCastlingRights = 0;
4573       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4574       break;
4575     case VariantCourier:
4576       pieces = CourierArray;
4577       gameInfo.boardWidth  = 12;
4578       nrCastlingRights = 0;
4579       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4580       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4581       break;
4582     case VariantKnightmate:
4583       pieces = KnightmateArray;
4584       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4585       break;
4586     case VariantFairy:
4587       pieces = fairyArray;
4588       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4589       break;
4590     case VariantGreat:
4591       pieces = GreatArray;
4592       gameInfo.boardWidth = 10;
4593       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4594       gameInfo.holdingsSize = 8;
4595       break;
4596     case VariantSuper:
4597       pieces = FIDEArray;
4598       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4599       gameInfo.holdingsSize = 8;
4600       startedFromSetupPosition = TRUE;
4601       break;
4602     case VariantCrazyhouse:
4603     case VariantBughouse:
4604       pieces = FIDEArray;
4605       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4606       gameInfo.holdingsSize = 5;
4607       break;
4608     case VariantWildCastle:
4609       pieces = FIDEArray;
4610       /* !!?shuffle with kings guaranteed to be on d or e file */
4611       shuffleOpenings = 1;
4612       break;
4613     case VariantNoCastle:
4614       pieces = FIDEArray;
4615       nrCastlingRights = 0;
4616       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4617       /* !!?unconstrained back-rank shuffle */
4618       shuffleOpenings = 1;
4619       break;
4620     }
4621
4622     overrule = 0;
4623     if(appData.NrFiles >= 0) {
4624         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4625         gameInfo.boardWidth = appData.NrFiles;
4626     }
4627     if(appData.NrRanks >= 0) {
4628         gameInfo.boardHeight = appData.NrRanks;
4629     }
4630     if(appData.holdingsSize >= 0) {
4631         i = appData.holdingsSize;
4632         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4633         gameInfo.holdingsSize = i;
4634     }
4635     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4636     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4637         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4638
4639     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4640     if(pawnRow < 1) pawnRow = 1;
4641
4642     /* User pieceToChar list overrules defaults */
4643     if(appData.pieceToCharTable != NULL)
4644         SetCharTable(pieceToChar, appData.pieceToCharTable);
4645
4646     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4647
4648         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4649             s = (ChessSquare) 0; /* account holding counts in guard band */
4650         for( i=0; i<BOARD_HEIGHT; i++ )
4651             initialPosition[i][j] = s;
4652
4653         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4654         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4655         initialPosition[pawnRow][j] = WhitePawn;
4656         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4657         if(gameInfo.variant == VariantXiangqi) {
4658             if(j&1) {
4659                 initialPosition[pawnRow][j] = 
4660                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4661                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4662                    initialPosition[2][j] = WhiteCannon;
4663                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4664                 }
4665             }
4666         }
4667         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4668     }
4669     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4670
4671             j=BOARD_LEFT+1;
4672             initialPosition[1][j] = WhiteBishop;
4673             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4674             j=BOARD_RGHT-2;
4675             initialPosition[1][j] = WhiteRook;
4676             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4677     }
4678
4679     if( nrCastlingRights == -1) {
4680         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4681         /*       This sets default castling rights from none to normal corners   */
4682         /* Variants with other castling rights must set them themselves above    */
4683         nrCastlingRights = 6;
4684        
4685         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4686         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4687         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4688         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4689         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4690         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4691      }
4692
4693      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4694      if(gameInfo.variant == VariantGreat) { // promotion commoners
4695         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4696         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4697         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4698         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4699      }
4700   if (appData.debugMode) {
4701     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4702   }
4703     if(shuffleOpenings) {
4704         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4705         startedFromSetupPosition = TRUE;
4706     }
4707     if(startedFromPositionFile) {
4708       /* [HGM] loadPos: use PositionFile for every new game */
4709       CopyBoard(initialPosition, filePosition);
4710       for(i=0; i<nrCastlingRights; i++)
4711           castlingRights[0][i] = initialRights[i] = fileRights[i];
4712       startedFromSetupPosition = TRUE;
4713     }
4714
4715     CopyBoard(boards[0], initialPosition);
4716
4717     if(oldx != gameInfo.boardWidth ||
4718        oldy != gameInfo.boardHeight ||
4719        oldh != gameInfo.holdingsWidth
4720 #ifdef GOTHIC
4721        || oldv == VariantGothic ||        // For licensing popups
4722        gameInfo.variant == VariantGothic
4723 #endif
4724 #ifdef FALCON
4725        || oldv == VariantFalcon ||
4726        gameInfo.variant == VariantFalcon
4727 #endif
4728                                          )
4729             InitDrawingSizes(-2 ,0);
4730
4731     if (redraw)
4732       DrawPosition(TRUE, boards[currentMove]);
4733 }
4734
4735 void
4736 SendBoard(cps, moveNum)
4737      ChessProgramState *cps;
4738      int moveNum;
4739 {
4740     char message[MSG_SIZ];
4741     
4742     if (cps->useSetboard) {
4743       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4744       sprintf(message, "setboard %s\n", fen);
4745       SendToProgram(message, cps);
4746       free(fen);
4747
4748     } else {
4749       ChessSquare *bp;
4750       int i, j;
4751       /* Kludge to set black to move, avoiding the troublesome and now
4752        * deprecated "black" command.
4753        */
4754       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4755
4756       SendToProgram("edit\n", cps);
4757       SendToProgram("#\n", cps);
4758       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4759         bp = &boards[moveNum][i][BOARD_LEFT];
4760         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4761           if ((int) *bp < (int) BlackPawn) {
4762             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4763                     AAA + j, ONE + i);
4764             if(message[0] == '+' || message[0] == '~') {
4765                 sprintf(message, "%c%c%c+\n",
4766                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4767                         AAA + j, ONE + i);
4768             }
4769             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4770                 message[1] = BOARD_RGHT   - 1 - j + '1';
4771                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4772             }
4773             SendToProgram(message, cps);
4774           }
4775         }
4776       }
4777     
4778       SendToProgram("c\n", cps);
4779       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4780         bp = &boards[moveNum][i][BOARD_LEFT];
4781         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4782           if (((int) *bp != (int) EmptySquare)
4783               && ((int) *bp >= (int) BlackPawn)) {
4784             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4785                     AAA + j, ONE + i);
4786             if(message[0] == '+' || message[0] == '~') {
4787                 sprintf(message, "%c%c%c+\n",
4788                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4789                         AAA + j, ONE + i);
4790             }
4791             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4792                 message[1] = BOARD_RGHT   - 1 - j + '1';
4793                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4794             }
4795             SendToProgram(message, cps);
4796           }
4797         }
4798       }
4799     
4800       SendToProgram(".\n", cps);
4801     }
4802     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4803 }
4804
4805 int
4806 IsPromotion(fromX, fromY, toX, toY)
4807      int fromX, fromY, toX, toY;
4808 {
4809     /* [HGM] add Shogi promotions */
4810     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4811     ChessSquare piece;
4812
4813     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4814       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4815    /* [HGM] Note to self: line above also weeds out drops */
4816     piece = boards[currentMove][fromY][fromX];
4817     if(gameInfo.variant == VariantShogi) {
4818         promotionZoneSize = 3;
4819         highestPromotingPiece = (int)WhiteKing;
4820         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4821            and if in normal chess we then allow promotion to King, why not
4822            allow promotion of other piece in Shogi?                         */
4823     }
4824     if((int)piece >= BlackPawn) {
4825         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4826              return FALSE;
4827         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4828     } else {
4829         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4830            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4831     }
4832     return ( (int)piece <= highestPromotingPiece );
4833 }
4834
4835 int
4836 InPalace(row, column)
4837      int row, column;
4838 {   /* [HGM] for Xiangqi */
4839     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4840          column < (BOARD_WIDTH + 4)/2 &&
4841          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4842     return FALSE;
4843 }
4844
4845 int
4846 PieceForSquare (x, y)
4847      int x;
4848      int y;
4849 {
4850   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4851      return -1;
4852   else
4853      return boards[currentMove][y][x];
4854 }
4855
4856 int
4857 OKToStartUserMove(x, y)
4858      int x, y;
4859 {
4860     ChessSquare from_piece;
4861     int white_piece;
4862
4863     if (matchMode) return FALSE;
4864     if (gameMode == EditPosition) return TRUE;
4865
4866     if (x >= 0 && y >= 0)
4867       from_piece = boards[currentMove][y][x];
4868     else
4869       from_piece = EmptySquare;
4870
4871     if (from_piece == EmptySquare) return FALSE;
4872
4873     white_piece = (int)from_piece >= (int)WhitePawn &&
4874       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4875
4876     switch (gameMode) {
4877       case PlayFromGameFile:
4878       case AnalyzeFile:
4879       case TwoMachinesPlay:
4880       case EndOfGame:
4881         return FALSE;
4882
4883       case IcsObserving:
4884       case IcsIdle:
4885         return FALSE;
4886
4887       case MachinePlaysWhite:
4888       case IcsPlayingBlack:
4889         if (appData.zippyPlay) return FALSE;
4890         if (white_piece) {
4891             DisplayMoveError(_("You are playing Black"));
4892             return FALSE;
4893         }
4894         break;
4895
4896       case MachinePlaysBlack:
4897       case IcsPlayingWhite:
4898         if (appData.zippyPlay) return FALSE;
4899         if (!white_piece) {
4900             DisplayMoveError(_("You are playing White"));
4901             return FALSE;
4902         }
4903         break;
4904
4905       case EditGame:
4906         if (!white_piece && WhiteOnMove(currentMove)) {
4907             DisplayMoveError(_("It is White's turn"));
4908             return FALSE;
4909         }           
4910         if (white_piece && !WhiteOnMove(currentMove)) {
4911             DisplayMoveError(_("It is Black's turn"));
4912             return FALSE;
4913         }           
4914         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4915             /* Editing correspondence game history */
4916             /* Could disallow this or prompt for confirmation */
4917             cmailOldMove = -1;
4918         }
4919         if (currentMove < forwardMostMove) {
4920             /* Discarding moves */
4921             /* Could prompt for confirmation here,
4922                but I don't think that's such a good idea */
4923             forwardMostMove = currentMove;
4924         }
4925         break;
4926
4927       case BeginningOfGame:
4928         if (appData.icsActive) return FALSE;
4929         if (!appData.noChessProgram) {
4930             if (!white_piece) {
4931                 DisplayMoveError(_("You are playing White"));
4932                 return FALSE;
4933             }
4934         }
4935         break;
4936         
4937       case Training:
4938         if (!white_piece && WhiteOnMove(currentMove)) {
4939             DisplayMoveError(_("It is White's turn"));
4940             return FALSE;
4941         }           
4942         if (white_piece && !WhiteOnMove(currentMove)) {
4943             DisplayMoveError(_("It is Black's turn"));
4944             return FALSE;
4945         }           
4946         break;
4947
4948       default:
4949       case IcsExamining:
4950         break;
4951     }
4952     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4953         && gameMode != AnalyzeFile && gameMode != Training) {
4954         DisplayMoveError(_("Displayed position is not current"));
4955         return FALSE;
4956     }
4957     return TRUE;
4958 }
4959
4960 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4961 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4962 int lastLoadGameUseList = FALSE;
4963 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4964 ChessMove lastLoadGameStart = (ChessMove) 0;
4965
4966 ChessMove
4967 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
4968      int fromX, fromY, toX, toY;
4969      int promoChar;
4970      Boolean captureOwn;
4971 {
4972     ChessMove moveType;
4973     ChessSquare pdown, pup;
4974
4975     if (fromX < 0 || fromY < 0) return ImpossibleMove;
4976
4977     /* [HGM] suppress all moves into holdings area and guard band */
4978     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
4979             return ImpossibleMove;
4980
4981     /* [HGM] <sameColor> moved to here from winboard.c */
4982     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
4983     pdown = boards[currentMove][fromY][fromX];
4984     pup = boards[currentMove][toY][toX];
4985     if (    gameMode != EditPosition && !captureOwn &&
4986             (WhitePawn <= pdown && pdown < BlackPawn &&
4987              WhitePawn <= pup && pup < BlackPawn  ||
4988              BlackPawn <= pdown && pdown < EmptySquare &&
4989              BlackPawn <= pup && pup < EmptySquare 
4990             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
4991                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
4992                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
4993                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
4994                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
4995         )           )
4996          return Comment;
4997
4998     /* Check if the user is playing in turn.  This is complicated because we
4999        let the user "pick up" a piece before it is his turn.  So the piece he
5000        tried to pick up may have been captured by the time he puts it down!
5001        Therefore we use the color the user is supposed to be playing in this
5002        test, not the color of the piece that is currently on the starting
5003        square---except in EditGame mode, where the user is playing both
5004        sides; fortunately there the capture race can't happen.  (It can
5005        now happen in IcsExamining mode, but that's just too bad.  The user
5006        will get a somewhat confusing message in that case.)
5007        */
5008
5009     switch (gameMode) {
5010       case PlayFromGameFile:
5011       case AnalyzeFile:
5012       case TwoMachinesPlay:
5013       case EndOfGame:
5014       case IcsObserving:
5015       case IcsIdle:
5016         /* We switched into a game mode where moves are not accepted,
5017            perhaps while the mouse button was down. */
5018         return ImpossibleMove;
5019
5020       case MachinePlaysWhite:
5021         /* User is moving for Black */
5022         if (WhiteOnMove(currentMove)) {
5023             DisplayMoveError(_("It is White's turn"));
5024             return ImpossibleMove;
5025         }
5026         break;
5027
5028       case MachinePlaysBlack:
5029         /* User is moving for White */
5030         if (!WhiteOnMove(currentMove)) {
5031             DisplayMoveError(_("It is Black's turn"));
5032             return ImpossibleMove;
5033         }
5034         break;
5035
5036       case EditGame:
5037       case IcsExamining:
5038       case BeginningOfGame:
5039       case AnalyzeMode:
5040       case Training:
5041         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5042             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5043             /* User is moving for Black */
5044             if (WhiteOnMove(currentMove)) {
5045                 DisplayMoveError(_("It is White's turn"));
5046                 return ImpossibleMove;
5047             }
5048         } else {
5049             /* User is moving for White */
5050             if (!WhiteOnMove(currentMove)) {
5051                 DisplayMoveError(_("It is Black's turn"));
5052                 return ImpossibleMove;
5053             }
5054         }
5055         break;
5056
5057       case IcsPlayingBlack:
5058         /* User is moving for Black */
5059         if (WhiteOnMove(currentMove)) {
5060             if (!appData.premove) {
5061                 DisplayMoveError(_("It is White's turn"));
5062             } else if (toX >= 0 && toY >= 0) {
5063                 premoveToX = toX;
5064                 premoveToY = toY;
5065                 premoveFromX = fromX;
5066                 premoveFromY = fromY;
5067                 premovePromoChar = promoChar;
5068                 gotPremove = 1;
5069                 if (appData.debugMode) 
5070                     fprintf(debugFP, "Got premove: fromX %d,"
5071                             "fromY %d, toX %d, toY %d\n",
5072                             fromX, fromY, toX, toY);
5073             }
5074             return ImpossibleMove;
5075         }
5076         break;
5077
5078       case IcsPlayingWhite:
5079         /* User is moving for White */
5080         if (!WhiteOnMove(currentMove)) {
5081             if (!appData.premove) {
5082                 DisplayMoveError(_("It is Black's turn"));
5083             } else if (toX >= 0 && toY >= 0) {
5084                 premoveToX = toX;
5085                 premoveToY = toY;
5086                 premoveFromX = fromX;
5087                 premoveFromY = fromY;
5088                 premovePromoChar = promoChar;
5089                 gotPremove = 1;
5090                 if (appData.debugMode) 
5091                     fprintf(debugFP, "Got premove: fromX %d,"
5092                             "fromY %d, toX %d, toY %d\n",
5093                             fromX, fromY, toX, toY);
5094             }
5095             return ImpossibleMove;
5096         }
5097         break;
5098
5099       default:
5100         break;
5101
5102       case EditPosition:
5103         /* EditPosition, empty square, or different color piece;
5104            click-click move is possible */
5105         if (toX == -2 || toY == -2) {
5106             boards[0][fromY][fromX] = EmptySquare;
5107             return AmbiguousMove;
5108         } else if (toX >= 0 && toY >= 0) {
5109             boards[0][toY][toX] = boards[0][fromY][fromX];
5110             boards[0][fromY][fromX] = EmptySquare;
5111             return AmbiguousMove;
5112         }
5113         return ImpossibleMove;
5114     }
5115
5116     /* [HGM] If move started in holdings, it means a drop */
5117     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5118          if( pup != EmptySquare ) return ImpossibleMove;
5119          if(appData.testLegality) {
5120              /* it would be more logical if LegalityTest() also figured out
5121               * which drops are legal. For now we forbid pawns on back rank.
5122               * Shogi is on its own here...
5123               */
5124              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5125                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5126                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5127          }
5128          return WhiteDrop; /* Not needed to specify white or black yet */
5129     }
5130
5131     userOfferedDraw = FALSE;
5132         
5133     /* [HGM] always test for legality, to get promotion info */
5134     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5135                           epStatus[currentMove], castlingRights[currentMove],
5136                                          fromY, fromX, toY, toX, promoChar);
5137     /* [HGM] but possibly ignore an IllegalMove result */
5138     if (appData.testLegality) {
5139         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5140             DisplayMoveError(_("Illegal move"));
5141             return ImpossibleMove;
5142         }
5143     }
5144 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5145     return moveType;
5146     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5147        function is made into one that returns an OK move type if FinishMove
5148        should be called. This to give the calling driver routine the
5149        opportunity to finish the userMove input with a promotion popup,
5150        without bothering the user with this for invalid or illegal moves */
5151
5152 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5153 }
5154
5155 /* Common tail of UserMoveEvent and DropMenuEvent */
5156 int
5157 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5158      ChessMove moveType;
5159      int fromX, fromY, toX, toY;
5160      /*char*/int promoChar;
5161 {
5162     char *bookHit = 0;
5163 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5164     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5165         // [HGM] superchess: suppress promotions to non-available piece
5166         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5167         if(WhiteOnMove(currentMove)) {
5168             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5169         } else {
5170             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5171         }
5172     }
5173
5174     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5175        move type in caller when we know the move is a legal promotion */
5176     if(moveType == NormalMove && promoChar)
5177         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5178 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5179     /* [HGM] convert drag-and-drop piece drops to standard form */
5180     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5181          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5182            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5183                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5184 //         fromX = boards[currentMove][fromY][fromX];
5185            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5186            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5187            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5188            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5189          fromY = DROP_RANK;
5190     }
5191
5192     /* [HGM] <popupFix> The following if has been moved here from
5193        UserMoveEvent(). Because it seemed to belon here (why not allow
5194        piece drops in training games?), and because it can only be
5195        performed after it is known to what we promote. */
5196     if (gameMode == Training) {
5197       /* compare the move played on the board to the next move in the
5198        * game. If they match, display the move and the opponent's response. 
5199        * If they don't match, display an error message.
5200        */
5201       int saveAnimate;
5202       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5203       CopyBoard(testBoard, boards[currentMove]);
5204       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5205
5206       if (CompareBoards(testBoard, boards[currentMove+1])) {
5207         ForwardInner(currentMove+1);
5208
5209         /* Autoplay the opponent's response.
5210          * if appData.animate was TRUE when Training mode was entered,
5211          * the response will be animated.
5212          */
5213         saveAnimate = appData.animate;
5214         appData.animate = animateTraining;
5215         ForwardInner(currentMove+1);
5216         appData.animate = saveAnimate;
5217
5218         /* check for the end of the game */
5219         if (currentMove >= forwardMostMove) {
5220           gameMode = PlayFromGameFile;
5221           ModeHighlight();
5222           SetTrainingModeOff();
5223           DisplayInformation(_("End of game"));
5224         }
5225       } else {
5226         DisplayError(_("Incorrect move"), 0);
5227       }
5228       return 1;
5229     }
5230
5231   /* Ok, now we know that the move is good, so we can kill
5232      the previous line in Analysis Mode */
5233   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5234     forwardMostMove = currentMove;
5235   }
5236
5237   /* If we need the chess program but it's dead, restart it */
5238   ResurrectChessProgram();
5239
5240   /* A user move restarts a paused game*/
5241   if (pausing)
5242     PauseEvent();
5243
5244   thinkOutput[0] = NULLCHAR;
5245
5246   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5247
5248   if (gameMode == BeginningOfGame) {
5249     if (appData.noChessProgram) {
5250       gameMode = EditGame;
5251       SetGameInfo();
5252     } else {
5253       char buf[MSG_SIZ];
5254       gameMode = MachinePlaysBlack;
5255       StartClocks();
5256       SetGameInfo();
5257       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5258       DisplayTitle(buf);
5259       if (first.sendName) {
5260         sprintf(buf, "name %s\n", gameInfo.white);
5261         SendToProgram(buf, &first);
5262       }
5263       StartClocks();
5264     }
5265     ModeHighlight();
5266   }
5267 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5268   /* Relay move to ICS or chess engine */
5269   if (appData.icsActive) {
5270     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5271         gameMode == IcsExamining) {
5272       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5273       ics_user_moved = 1;
5274     }
5275   } else {
5276     if (first.sendTime && (gameMode == BeginningOfGame ||
5277                            gameMode == MachinePlaysWhite ||
5278                            gameMode == MachinePlaysBlack)) {
5279       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5280     }
5281     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5282          // [HGM] book: if program might be playing, let it use book
5283         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5284         first.maybeThinking = TRUE;
5285     } else SendMoveToProgram(forwardMostMove-1, &first);
5286     if (currentMove == cmailOldMove + 1) {
5287       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5288     }
5289   }
5290
5291   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5292
5293   switch (gameMode) {
5294   case EditGame:
5295     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5296                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5297     case MT_NONE:
5298     case MT_CHECK:
5299       break;
5300     case MT_CHECKMATE:
5301     case MT_STAINMATE:
5302       if (WhiteOnMove(currentMove)) {
5303         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5304       } else {
5305         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5306       }
5307       break;
5308     case MT_STALEMATE:
5309       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5310       break;
5311     }
5312     break;
5313     
5314   case MachinePlaysBlack:
5315   case MachinePlaysWhite:
5316     /* disable certain menu options while machine is thinking */
5317     SetMachineThinkingEnables();
5318     break;
5319
5320   default:
5321     break;
5322   }
5323
5324   if(bookHit) { // [HGM] book: simulate book reply
5325         static char bookMove[MSG_SIZ]; // a bit generous?
5326
5327         programStats.nodes = programStats.depth = programStats.time = 
5328         programStats.score = programStats.got_only_move = 0;
5329         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5330
5331         strcpy(bookMove, "move ");
5332         strcat(bookMove, bookHit);
5333         HandleMachineMove(bookMove, &first);
5334   }
5335   return 1;
5336 }
5337
5338 void
5339 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5340      int fromX, fromY, toX, toY;
5341      int promoChar;
5342 {
5343     /* [HGM] This routine was added to allow calling of its two logical
5344        parts from other modules in the old way. Before, UserMoveEvent()
5345        automatically called FinishMove() if the move was OK, and returned
5346        otherwise. I separated the two, in order to make it possible to
5347        slip a promotion popup in between. But that it always needs two
5348        calls, to the first part, (now called UserMoveTest() ), and to
5349        FinishMove if the first part succeeded. Calls that do not need
5350        to do anything in between, can call this routine the old way. 
5351     */
5352     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5353 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5354     if(moveType == AmbiguousMove)
5355         DrawPosition(FALSE, boards[currentMove]);
5356     else if(moveType != ImpossibleMove && moveType != Comment)
5357         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5358 }
5359
5360 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5361 {
5362 //    char * hint = lastHint;
5363     FrontEndProgramStats stats;
5364
5365     stats.which = cps == &first ? 0 : 1;
5366     stats.depth = cpstats->depth;
5367     stats.nodes = cpstats->nodes;
5368     stats.score = cpstats->score;
5369     stats.time = cpstats->time;
5370     stats.pv = cpstats->movelist;
5371     stats.hint = lastHint;
5372     stats.an_move_index = 0;
5373     stats.an_move_count = 0;
5374
5375     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5376         stats.hint = cpstats->move_name;
5377         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5378         stats.an_move_count = cpstats->nr_moves;
5379     }
5380
5381     SetProgramStats( &stats );
5382 }
5383
5384 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5385 {   // [HGM] book: this routine intercepts moves to simulate book replies
5386     char *bookHit = NULL;
5387
5388     //first determine if the incoming move brings opponent into his book
5389     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5390         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5391     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5392     if(bookHit != NULL && !cps->bookSuspend) {
5393         // make sure opponent is not going to reply after receiving move to book position
5394         SendToProgram("force\n", cps);
5395         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5396     }
5397     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5398     // now arrange restart after book miss
5399     if(bookHit) {
5400         // after a book hit we never send 'go', and the code after the call to this routine
5401         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5402         char buf[MSG_SIZ];
5403         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5404         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5405         SendToProgram(buf, cps);
5406         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5407     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5408         SendToProgram("go\n", cps);
5409         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5410     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5411         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5412             SendToProgram("go\n", cps); 
5413         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5414     }
5415     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5416 }
5417
5418 char *savedMessage;
5419 ChessProgramState *savedState;
5420 void DeferredBookMove(void)
5421 {
5422         if(savedState->lastPing != savedState->lastPong)
5423                     ScheduleDelayedEvent(DeferredBookMove, 10);
5424         else
5425         HandleMachineMove(savedMessage, savedState);
5426 }
5427
5428 void
5429 HandleMachineMove(message, cps)
5430      char *message;
5431      ChessProgramState *cps;
5432 {
5433     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5434     char realname[MSG_SIZ];
5435     int fromX, fromY, toX, toY;
5436     ChessMove moveType;
5437     char promoChar;
5438     char *p;
5439     int machineWhite;
5440     char *bookHit;
5441
5442 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5443     /*
5444      * Kludge to ignore BEL characters
5445      */
5446     while (*message == '\007') message++;
5447
5448     /*
5449      * [HGM] engine debug message: ignore lines starting with '#' character
5450      */
5451     if(cps->debug && *message == '#') return;
5452
5453     /*
5454      * Look for book output
5455      */
5456     if (cps == &first && bookRequested) {
5457         if (message[0] == '\t' || message[0] == ' ') {
5458             /* Part of the book output is here; append it */
5459             strcat(bookOutput, message);
5460             strcat(bookOutput, "  \n");
5461             return;
5462         } else if (bookOutput[0] != NULLCHAR) {
5463             /* All of book output has arrived; display it */
5464             char *p = bookOutput;
5465             while (*p != NULLCHAR) {
5466                 if (*p == '\t') *p = ' ';
5467                 p++;
5468             }
5469             DisplayInformation(bookOutput);
5470             bookRequested = FALSE;
5471             /* Fall through to parse the current output */
5472         }
5473     }
5474
5475     /*
5476      * Look for machine move.
5477      */
5478     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5479         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5480     {
5481         /* This method is only useful on engines that support ping */
5482         if (cps->lastPing != cps->lastPong) {
5483           if (gameMode == BeginningOfGame) {
5484             /* Extra move from before last new; ignore */
5485             if (appData.debugMode) {
5486                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5487             }
5488           } else {
5489             if (appData.debugMode) {
5490                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5491                         cps->which, gameMode);
5492             }
5493
5494             SendToProgram("undo\n", cps);
5495           }
5496           return;
5497         }
5498
5499         switch (gameMode) {
5500           case BeginningOfGame:
5501             /* Extra move from before last reset; ignore */
5502             if (appData.debugMode) {
5503                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5504             }
5505             return;
5506
5507           case EndOfGame:
5508           case IcsIdle:
5509           default:
5510             /* Extra move after we tried to stop.  The mode test is
5511                not a reliable way of detecting this problem, but it's
5512                the best we can do on engines that don't support ping.
5513             */
5514             if (appData.debugMode) {
5515                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5516                         cps->which, gameMode);
5517             }
5518             SendToProgram("undo\n", cps);
5519             return;
5520
5521           case MachinePlaysWhite:
5522           case IcsPlayingWhite:
5523             machineWhite = TRUE;
5524             break;
5525
5526           case MachinePlaysBlack:
5527           case IcsPlayingBlack:
5528             machineWhite = FALSE;
5529             break;
5530
5531           case TwoMachinesPlay:
5532             machineWhite = (cps->twoMachinesColor[0] == 'w');
5533             break;
5534         }
5535         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5536             if (appData.debugMode) {
5537                 fprintf(debugFP,
5538                         "Ignoring move out of turn by %s, gameMode %d"
5539                         ", forwardMost %d\n",
5540                         cps->which, gameMode, forwardMostMove);
5541             }
5542             return;
5543         }
5544
5545     if (appData.debugMode) { int f = forwardMostMove;
5546         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5547                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5548     }
5549         if(cps->alphaRank) AlphaRank(machineMove, 4);
5550         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5551                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5552             /* Machine move could not be parsed; ignore it. */
5553             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5554                     machineMove, cps->which);
5555             DisplayError(buf1, 0);
5556             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5557                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5558             if (gameMode == TwoMachinesPlay) {
5559               GameEnds(machineWhite ? BlackWins : WhiteWins,
5560                        buf1, GE_XBOARD);
5561             }
5562             return;
5563         }
5564
5565         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5566         /* So we have to redo legality test with true e.p. status here,  */
5567         /* to make sure an illegal e.p. capture does not slip through,   */
5568         /* to cause a forfeit on a justified illegal-move complaint      */
5569         /* of the opponent.                                              */
5570         if( gameMode==TwoMachinesPlay && appData.testLegality
5571             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5572                                                               ) {
5573            ChessMove moveType;
5574            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5575                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5576                              fromY, fromX, toY, toX, promoChar);
5577             if (appData.debugMode) {
5578                 int i;
5579                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5580                     castlingRights[forwardMostMove][i], castlingRank[i]);
5581                 fprintf(debugFP, "castling rights\n");
5582             }
5583             if(moveType == IllegalMove) {
5584                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5585                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5586                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5587                            buf1, GE_XBOARD);
5588                 return;
5589            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5590            /* [HGM] Kludge to handle engines that send FRC-style castling
5591               when they shouldn't (like TSCP-Gothic) */
5592            switch(moveType) {
5593              case WhiteASideCastleFR:
5594              case BlackASideCastleFR:
5595                toX+=2;
5596                currentMoveString[2]++;
5597                break;
5598              case WhiteHSideCastleFR:
5599              case BlackHSideCastleFR:
5600                toX--;
5601                currentMoveString[2]--;
5602                break;
5603              default: ; // nothing to do, but suppresses warning of pedantic compilers
5604            }
5605         }
5606         hintRequested = FALSE;
5607         lastHint[0] = NULLCHAR;
5608         bookRequested = FALSE;
5609         /* Program may be pondering now */
5610         cps->maybeThinking = TRUE;
5611         if (cps->sendTime == 2) cps->sendTime = 1;
5612         if (cps->offeredDraw) cps->offeredDraw--;
5613
5614 #if ZIPPY
5615         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5616             first.initDone) {
5617           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5618           ics_user_moved = 1;
5619           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5620                 char buf[3*MSG_SIZ];
5621
5622                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5623                         programStats.score / 100.,
5624                         programStats.depth,
5625                         programStats.time / 100.,
5626                         (unsigned int)programStats.nodes,
5627                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5628                         programStats.movelist);
5629                 SendToICS(buf);
5630 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5631           }
5632         }
5633 #endif
5634         /* currentMoveString is set as a side-effect of ParseOneMove */
5635         strcpy(machineMove, currentMoveString);
5636         strcat(machineMove, "\n");
5637         strcpy(moveList[forwardMostMove], machineMove);
5638
5639         /* [AS] Save move info and clear stats for next move */
5640         pvInfoList[ forwardMostMove ].score = programStats.score;
5641         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5642         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5643         ClearProgramStats();
5644         thinkOutput[0] = NULLCHAR;
5645         hiddenThinkOutputState = 0;
5646
5647         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5648
5649         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5650         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5651             int count = 0;
5652
5653             while( count < adjudicateLossPlies ) {
5654                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5655
5656                 if( count & 1 ) {
5657                     score = -score; /* Flip score for winning side */
5658                 }
5659
5660                 if( score > adjudicateLossThreshold ) {
5661                     break;
5662                 }
5663
5664                 count++;
5665             }
5666
5667             if( count >= adjudicateLossPlies ) {
5668                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5669
5670                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5671                     "Xboard adjudication", 
5672                     GE_XBOARD );
5673
5674                 return;
5675             }
5676         }
5677
5678         if( gameMode == TwoMachinesPlay ) {
5679           // [HGM] some adjudications useful with buggy engines
5680             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5681           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5682
5683
5684             if( appData.testLegality )
5685             {   /* [HGM] Some more adjudications for obstinate engines */
5686                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5687                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5688                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5689                 static int moveCount = 6;
5690                 ChessMove result;
5691                 char *reason = NULL;
5692
5693                 /* Count what is on board. */
5694                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5695                 {   ChessSquare p = boards[forwardMostMove][i][j];
5696                     int m=i;
5697
5698                     switch((int) p)
5699                     {   /* count B,N,R and other of each side */
5700                         case WhiteKing:
5701                         case BlackKing:
5702                              NrK++; break; // [HGM] atomic: count Kings
5703                         case WhiteKnight:
5704                              NrWN++; break;
5705                         case WhiteBishop:
5706                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5707                              bishopsColor |= 1 << ((i^j)&1);
5708                              NrWB++; break;
5709                         case BlackKnight:
5710                              NrBN++; break;
5711                         case BlackBishop:
5712                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5713                              bishopsColor |= 1 << ((i^j)&1);
5714                              NrBB++; break;
5715                         case WhiteRook:
5716                              NrWR++; break;
5717                         case BlackRook:
5718                              NrBR++; break;
5719                         case WhiteQueen:
5720                              NrWQ++; break;
5721                         case BlackQueen:
5722                              NrBQ++; break;
5723                         case EmptySquare: 
5724                              break;
5725                         case BlackPawn:
5726                              m = 7-i;
5727                         case WhitePawn:
5728                              PawnAdvance += m; NrPawns++;
5729                     }
5730                     NrPieces += (p != EmptySquare);
5731                     NrW += ((int)p < (int)BlackPawn);
5732                     if(gameInfo.variant == VariantXiangqi && 
5733                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5734                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5735                         NrW -= ((int)p < (int)BlackPawn);
5736                     }
5737                 }
5738
5739                 /* Some material-based adjudications that have to be made before stalemate test */
5740                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5741                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5742                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5743                      if(appData.checkMates) {
5744                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5745                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5746                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5747                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5748                          return;
5749                      }
5750                 }
5751
5752                 /* Bare King in Shatranj (loses) or Losers (wins) */
5753                 if( NrW == 1 || NrPieces - NrW == 1) {
5754                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5755                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5756                      if(appData.checkMates) {
5757                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5758                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5759                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5760                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5761                          return;
5762                      }
5763                   } else
5764                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5765                   {    /* bare King */
5766                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5767                         if(appData.checkMates) {
5768                             /* but only adjudicate if adjudication enabled */
5769                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5770                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5771                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5772                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5773                             return;
5774                         }
5775                   }
5776                 } else bare = 1;
5777
5778
5779             // don't wait for engine to announce game end if we can judge ourselves
5780             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5781                                        castlingRights[forwardMostMove]) ) {
5782               case MT_CHECK:
5783                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5784                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5785                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5786                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5787                             checkCnt++;
5788                         if(checkCnt >= 2) {
5789                             reason = "Xboard adjudication: 3rd check";
5790                             epStatus[forwardMostMove] = EP_CHECKMATE;
5791                             break;
5792                         }
5793                     }
5794                 }
5795               case MT_NONE:
5796               default:
5797                 break;
5798               case MT_STALEMATE:
5799               case MT_STAINMATE:
5800                 reason = "Xboard adjudication: Stalemate";
5801                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5802                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5803                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5804                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5805                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5806                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5807                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5808                                                                         EP_CHECKMATE : EP_WINS);
5809                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5810                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5811                 }
5812                 break;
5813               case MT_CHECKMATE:
5814                 reason = "Xboard adjudication: Checkmate";
5815                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5816                 break;
5817             }
5818
5819                 switch(i = epStatus[forwardMostMove]) {
5820                     case EP_STALEMATE:
5821                         result = GameIsDrawn; break;
5822                     case EP_CHECKMATE:
5823                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5824                     case EP_WINS:
5825                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5826                     default:
5827                         result = (ChessMove) 0;
5828                 }
5829                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5830                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5831                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5832                     GameEnds( result, reason, GE_XBOARD );
5833                     return;
5834                 }
5835
5836                 /* Next absolutely insufficient mating material. */
5837                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5838                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5839                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5840                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5841                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5842
5843                      /* always flag draws, for judging claims */
5844                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5845
5846                      if(appData.materialDraws) {
5847                          /* but only adjudicate them if adjudication enabled */
5848                          SendToProgram("force\n", cps->other); // suppress reply
5849                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5850                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5851                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5852                          return;
5853                      }
5854                 }
5855
5856                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5857                 if(NrPieces == 4 && 
5858                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5859                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5860                    || NrWN==2 || NrBN==2     /* KNNK */
5861                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5862                   ) ) {
5863                      if(--moveCount < 0 && appData.trivialDraws)
5864                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5865                           SendToProgram("force\n", cps->other); // suppress reply
5866                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5867                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5868                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5869                           return;
5870                      }
5871                 } else moveCount = 6;
5872             }
5873           }
5874           
5875           if (appData.debugMode) { int i;
5876             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5877                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5878                     appData.drawRepeats);
5879             for( i=forwardMostMove; i>=backwardMostMove; i-- )
5880               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5881             
5882           }
5883
5884                 /* Check for rep-draws */
5885                 count = 0;
5886                 for(k = forwardMostMove-2;
5887                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5888                         epStatus[k] < EP_UNKNOWN &&
5889                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5890                     k-=2)
5891                 {   int rights=0;
5892                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5893                         /* compare castling rights */
5894                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5895                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5896                                 rights++; /* King lost rights, while rook still had them */
5897                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5898                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5899                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5900                                    rights++; /* but at least one rook lost them */
5901                         }
5902                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5903                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5904                                 rights++; 
5905                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5906                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5907                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5908                                    rights++;
5909                         }
5910                         if( rights == 0 && ++count > appData.drawRepeats-2
5911                             && appData.drawRepeats > 1) {
5912                              /* adjudicate after user-specified nr of repeats */
5913                              SendToProgram("force\n", cps->other); // suppress reply
5914                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5915                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5916                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5917                                 // [HGM] xiangqi: check for forbidden perpetuals
5918                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5919                                 for(m=forwardMostMove; m>k; m-=2) {
5920                                     if(MateTest(boards[m], PosFlags(m), 
5921                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5922                                         ourPerpetual = 0; // the current mover did not always check
5923                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5924                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5925                                         hisPerpetual = 0; // the opponent did not always check
5926                                 }
5927                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5928                                                                         ourPerpetual, hisPerpetual);
5929                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5930                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5931                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5932                                     return;
5933                                 }
5934                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5935                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5936                                 // Now check for perpetual chases
5937                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5938                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5939                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5940                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5941                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5942                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5943                                         return;
5944                                     }
5945                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5946                                         break; // Abort repetition-checking loop.
5947                                 }
5948                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5949                              }
5950                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5951                              return;
5952                         }
5953                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5954                              epStatus[forwardMostMove] = EP_REP_DRAW;
5955                     }
5956                 }
5957
5958                 /* Now we test for 50-move draws. Determine ply count */
5959                 count = forwardMostMove;
5960                 /* look for last irreversble move */
5961                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5962                     count--;
5963                 /* if we hit starting position, add initial plies */
5964                 if( count == backwardMostMove )
5965                     count -= initialRulePlies;
5966                 count = forwardMostMove - count; 
5967                 if( count >= 100)
5968                          epStatus[forwardMostMove] = EP_RULE_DRAW;
5969                          /* this is used to judge if draw claims are legal */
5970                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
5971                          SendToProgram("force\n", cps->other); // suppress reply
5972                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5973                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5974                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
5975                          return;
5976                 }
5977
5978                 /* if draw offer is pending, treat it as a draw claim
5979                  * when draw condition present, to allow engines a way to
5980                  * claim draws before making their move to avoid a race
5981                  * condition occurring after their move
5982                  */
5983                 if( cps->other->offeredDraw || cps->offeredDraw ) {
5984                          char *p = NULL;
5985                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
5986                              p = "Draw claim: 50-move rule";
5987                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
5988                              p = "Draw claim: 3-fold repetition";
5989                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
5990                              p = "Draw claim: insufficient mating material";
5991                          if( p != NULL ) {
5992                              SendToProgram("force\n", cps->other); // suppress reply
5993                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5994                              GameEnds( GameIsDrawn, p, GE_XBOARD );
5995                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5996                              return;
5997                          }
5998                 }
5999
6000
6001                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6002                     SendToProgram("force\n", cps->other); // suppress reply
6003                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6004                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6005
6006                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6007
6008                     return;
6009                 }
6010         }
6011
6012         bookHit = NULL;
6013         if (gameMode == TwoMachinesPlay) {
6014             /* [HGM] relaying draw offers moved to after reception of move */
6015             /* and interpreting offer as claim if it brings draw condition */
6016             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6017                 SendToProgram("draw\n", cps->other);
6018             }
6019             if (cps->other->sendTime) {
6020                 SendTimeRemaining(cps->other,
6021                                   cps->other->twoMachinesColor[0] == 'w');
6022             }
6023             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6024             if (firstMove && !bookHit) {
6025                 firstMove = FALSE;
6026                 if (cps->other->useColors) {
6027                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6028                 }
6029                 SendToProgram("go\n", cps->other);
6030             }
6031             cps->other->maybeThinking = TRUE;
6032         }
6033
6034         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6035         
6036         if (!pausing && appData.ringBellAfterMoves) {
6037             RingBell();
6038         }
6039
6040         /* 
6041          * Reenable menu items that were disabled while
6042          * machine was thinking
6043          */
6044         if (gameMode != TwoMachinesPlay)
6045             SetUserThinkingEnables();
6046
6047         // [HGM] book: after book hit opponent has received move and is now in force mode
6048         // force the book reply into it, and then fake that it outputted this move by jumping
6049         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6050         if(bookHit) {
6051                 static char bookMove[MSG_SIZ]; // a bit generous?
6052
6053                 strcpy(bookMove, "move ");
6054                 strcat(bookMove, bookHit);
6055                 message = bookMove;
6056                 cps = cps->other;
6057                 programStats.nodes = programStats.depth = programStats.time = 
6058                 programStats.score = programStats.got_only_move = 0;
6059                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6060
6061                 if(cps->lastPing != cps->lastPong) {
6062                     savedMessage = message; // args for deferred call
6063                     savedState = cps;
6064                     ScheduleDelayedEvent(DeferredBookMove, 10);
6065                     return;
6066                 }
6067                 goto FakeBookMove;
6068         }
6069
6070         return;
6071     }
6072
6073     /* Set special modes for chess engines.  Later something general
6074      *  could be added here; for now there is just one kludge feature,
6075      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6076      *  when "xboard" is given as an interactive command.
6077      */
6078     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6079         cps->useSigint = FALSE;
6080         cps->useSigterm = FALSE;
6081     }
6082     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6083       ParseFeatures(message+8, cps);
6084       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6085     }
6086
6087     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6088      * want this, I was asked to put it in, and obliged.
6089      */
6090     if (!strncmp(message, "setboard ", 9)) {
6091         Board initial_position; int i;
6092
6093         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6094
6095         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6096             DisplayError(_("Bad FEN received from engine"), 0);
6097             return ;
6098         } else {
6099            Reset(FALSE, FALSE);
6100            CopyBoard(boards[0], initial_position);
6101            initialRulePlies = FENrulePlies;
6102            epStatus[0] = FENepStatus;
6103            for( i=0; i<nrCastlingRights; i++ )
6104                 castlingRights[0][i] = FENcastlingRights[i];
6105            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6106            else gameMode = MachinePlaysBlack;                 
6107            DrawPosition(FALSE, boards[currentMove]);
6108         }
6109         return;
6110     }
6111
6112     /*
6113      * Look for communication commands
6114      */
6115     if (!strncmp(message, "telluser ", 9)) {
6116         DisplayNote(message + 9);
6117         return;
6118     }
6119     if (!strncmp(message, "tellusererror ", 14)) {
6120         DisplayError(message + 14, 0);
6121         return;
6122     }
6123     if (!strncmp(message, "tellopponent ", 13)) {
6124       if (appData.icsActive) {
6125         if (loggedOn) {
6126           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6127           SendToICS(buf1);
6128         }
6129       } else {
6130         DisplayNote(message + 13);
6131       }
6132       return;
6133     }
6134     if (!strncmp(message, "tellothers ", 11)) {
6135       if (appData.icsActive) {
6136         if (loggedOn) {
6137           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6138           SendToICS(buf1);
6139         }
6140       }
6141       return;
6142     }
6143     if (!strncmp(message, "tellall ", 8)) {
6144       if (appData.icsActive) {
6145         if (loggedOn) {
6146           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6147           SendToICS(buf1);
6148         }
6149       } else {
6150         DisplayNote(message + 8);
6151       }
6152       return;
6153     }
6154     if (strncmp(message, "warning", 7) == 0) {
6155         /* Undocumented feature, use tellusererror in new code */
6156         DisplayError(message, 0);
6157         return;
6158     }
6159     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6160         strcpy(realname, cps->tidy);
6161         strcat(realname, " query");
6162         AskQuestion(realname, buf2, buf1, cps->pr);
6163         return;
6164     }
6165     /* Commands from the engine directly to ICS.  We don't allow these to be 
6166      *  sent until we are logged on. Crafty kibitzes have been known to 
6167      *  interfere with the login process.
6168      */
6169     if (loggedOn) {
6170         if (!strncmp(message, "tellics ", 8)) {
6171             SendToICS(message + 8);
6172             SendToICS("\n");
6173             return;
6174         }
6175         if (!strncmp(message, "tellicsnoalias ", 15)) {
6176             SendToICS(ics_prefix);
6177             SendToICS(message + 15);
6178             SendToICS("\n");
6179             return;
6180         }
6181         /* The following are for backward compatibility only */
6182         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6183             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6184             SendToICS(ics_prefix);
6185             SendToICS(message);
6186             SendToICS("\n");
6187             return;
6188         }
6189     }
6190     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6191         return;
6192     }
6193     /*
6194      * If the move is illegal, cancel it and redraw the board.
6195      * Also deal with other error cases.  Matching is rather loose
6196      * here to accommodate engines written before the spec.
6197      */
6198     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6199         strncmp(message, "Error", 5) == 0) {
6200         if (StrStr(message, "name") || 
6201             StrStr(message, "rating") || StrStr(message, "?") ||
6202             StrStr(message, "result") || StrStr(message, "board") ||
6203             StrStr(message, "bk") || StrStr(message, "computer") ||
6204             StrStr(message, "variant") || StrStr(message, "hint") ||
6205             StrStr(message, "random") || StrStr(message, "depth") ||
6206             StrStr(message, "accepted")) {
6207             return;
6208         }
6209         if (StrStr(message, "protover")) {
6210           /* Program is responding to input, so it's apparently done
6211              initializing, and this error message indicates it is
6212              protocol version 1.  So we don't need to wait any longer
6213              for it to initialize and send feature commands. */
6214           FeatureDone(cps, 1);
6215           cps->protocolVersion = 1;
6216           return;
6217         }
6218         cps->maybeThinking = FALSE;
6219
6220         if (StrStr(message, "draw")) {
6221             /* Program doesn't have "draw" command */
6222             cps->sendDrawOffers = 0;
6223             return;
6224         }
6225         if (cps->sendTime != 1 &&
6226             (StrStr(message, "time") || StrStr(message, "otim"))) {
6227           /* Program apparently doesn't have "time" or "otim" command */
6228           cps->sendTime = 0;
6229           return;
6230         }
6231         if (StrStr(message, "analyze")) {
6232             cps->analysisSupport = FALSE;
6233             cps->analyzing = FALSE;
6234             Reset(FALSE, TRUE);
6235             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6236             DisplayError(buf2, 0);
6237             return;
6238         }
6239         if (StrStr(message, "(no matching move)st")) {
6240           /* Special kludge for GNU Chess 4 only */
6241           cps->stKludge = TRUE;
6242           SendTimeControl(cps, movesPerSession, timeControl,
6243                           timeIncrement, appData.searchDepth,
6244                           searchTime);
6245           return;
6246         }
6247         if (StrStr(message, "(no matching move)sd")) {
6248           /* Special kludge for GNU Chess 4 only */
6249           cps->sdKludge = TRUE;
6250           SendTimeControl(cps, movesPerSession, timeControl,
6251                           timeIncrement, appData.searchDepth,
6252                           searchTime);
6253           return;
6254         }
6255         if (!StrStr(message, "llegal")) {
6256             return;
6257         }
6258         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6259             gameMode == IcsIdle) return;
6260         if (forwardMostMove <= backwardMostMove) return;
6261         if (pausing) PauseEvent();
6262       if(appData.forceIllegal) {
6263             // [HGM] illegal: machine refused move; force position after move into it
6264           SendToProgram("force\n", cps);
6265           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6266                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6267                 // when black is to move, while there might be nothing on a2 or black
6268                 // might already have the move. So send the board as if white has the move.
6269                 // But first we must change the stm of the engine, as it refused the last move
6270                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6271                 if(WhiteOnMove(forwardMostMove)) {
6272                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6273                     SendBoard(cps, forwardMostMove); // kludgeless board
6274                 } else {
6275                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6276                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6277                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6278                 }
6279           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6280             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6281                  gameMode == TwoMachinesPlay)
6282               SendToProgram("go\n", cps);
6283             return;
6284       } else
6285         if (gameMode == PlayFromGameFile) {
6286             /* Stop reading this game file */
6287             gameMode = EditGame;
6288             ModeHighlight();
6289         }
6290         currentMove = --forwardMostMove;
6291         DisplayMove(currentMove-1); /* before DisplayMoveError */
6292         SwitchClocks();
6293         DisplayBothClocks();
6294         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6295                 parseList[currentMove], cps->which);
6296         DisplayMoveError(buf1);
6297         DrawPosition(FALSE, boards[currentMove]);
6298
6299         /* [HGM] illegal-move claim should forfeit game when Xboard */
6300         /* only passes fully legal moves                            */
6301         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6302             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6303                                 "False illegal-move claim", GE_XBOARD );
6304         }
6305         return;
6306     }
6307     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6308         /* Program has a broken "time" command that
6309            outputs a string not ending in newline.
6310            Don't use it. */
6311         cps->sendTime = 0;
6312     }
6313     
6314     /*
6315      * If chess program startup fails, exit with an error message.
6316      * Attempts to recover here are futile.
6317      */
6318     if ((StrStr(message, "unknown host") != NULL)
6319         || (StrStr(message, "No remote directory") != NULL)
6320         || (StrStr(message, "not found") != NULL)
6321         || (StrStr(message, "No such file") != NULL)
6322         || (StrStr(message, "can't alloc") != NULL)
6323         || (StrStr(message, "Permission denied") != NULL)) {
6324
6325         cps->maybeThinking = FALSE;
6326         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6327                 cps->which, cps->program, cps->host, message);
6328         RemoveInputSource(cps->isr);
6329         DisplayFatalError(buf1, 0, 1);
6330         return;
6331     }
6332     
6333     /* 
6334      * Look for hint output
6335      */
6336     if (sscanf(message, "Hint: %s", buf1) == 1) {
6337         if (cps == &first && hintRequested) {
6338             hintRequested = FALSE;
6339             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6340                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6341                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6342                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6343                                     fromY, fromX, toY, toX, promoChar, buf1);
6344                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6345                 DisplayInformation(buf2);
6346             } else {
6347                 /* Hint move could not be parsed!? */
6348               snprintf(buf2, sizeof(buf2),
6349                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6350                         buf1, cps->which);
6351                 DisplayError(buf2, 0);
6352             }
6353         } else {
6354             strcpy(lastHint, buf1);
6355         }
6356         return;
6357     }
6358
6359     /*
6360      * Ignore other messages if game is not in progress
6361      */
6362     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6363         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6364
6365     /*
6366      * look for win, lose, draw, or draw offer
6367      */
6368     if (strncmp(message, "1-0", 3) == 0) {
6369         char *p, *q, *r = "";
6370         p = strchr(message, '{');
6371         if (p) {
6372             q = strchr(p, '}');
6373             if (q) {
6374                 *q = NULLCHAR;
6375                 r = p + 1;
6376             }
6377         }
6378         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6379         return;
6380     } else if (strncmp(message, "0-1", 3) == 0) {
6381         char *p, *q, *r = "";
6382         p = strchr(message, '{');
6383         if (p) {
6384             q = strchr(p, '}');
6385             if (q) {
6386                 *q = NULLCHAR;
6387                 r = p + 1;
6388             }
6389         }
6390         /* Kludge for Arasan 4.1 bug */
6391         if (strcmp(r, "Black resigns") == 0) {
6392             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6393             return;
6394         }
6395         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6396         return;
6397     } else if (strncmp(message, "1/2", 3) == 0) {
6398         char *p, *q, *r = "";
6399         p = strchr(message, '{');
6400         if (p) {
6401             q = strchr(p, '}');
6402             if (q) {
6403                 *q = NULLCHAR;
6404                 r = p + 1;
6405             }
6406         }
6407             
6408         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6409         return;
6410
6411     } else if (strncmp(message, "White resign", 12) == 0) {
6412         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6413         return;
6414     } else if (strncmp(message, "Black resign", 12) == 0) {
6415         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6416         return;
6417     } else if (strncmp(message, "White matches", 13) == 0 ||
6418                strncmp(message, "Black matches", 13) == 0   ) {
6419         /* [HGM] ignore GNUShogi noises */
6420         return;
6421     } else if (strncmp(message, "White", 5) == 0 &&
6422                message[5] != '(' &&
6423                StrStr(message, "Black") == NULL) {
6424         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6425         return;
6426     } else if (strncmp(message, "Black", 5) == 0 &&
6427                message[5] != '(') {
6428         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6429         return;
6430     } else if (strcmp(message, "resign") == 0 ||
6431                strcmp(message, "computer resigns") == 0) {
6432         switch (gameMode) {
6433           case MachinePlaysBlack:
6434           case IcsPlayingBlack:
6435             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6436             break;
6437           case MachinePlaysWhite:
6438           case IcsPlayingWhite:
6439             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6440             break;
6441           case TwoMachinesPlay:
6442             if (cps->twoMachinesColor[0] == 'w')
6443               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6444             else
6445               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6446             break;
6447           default:
6448             /* can't happen */
6449             break;
6450         }
6451         return;
6452     } else if (strncmp(message, "opponent mates", 14) == 0) {
6453         switch (gameMode) {
6454           case MachinePlaysBlack:
6455           case IcsPlayingBlack:
6456             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6457             break;
6458           case MachinePlaysWhite:
6459           case IcsPlayingWhite:
6460             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6461             break;
6462           case TwoMachinesPlay:
6463             if (cps->twoMachinesColor[0] == 'w')
6464               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6465             else
6466               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6467             break;
6468           default:
6469             /* can't happen */
6470             break;
6471         }
6472         return;
6473     } else if (strncmp(message, "computer mates", 14) == 0) {
6474         switch (gameMode) {
6475           case MachinePlaysBlack:
6476           case IcsPlayingBlack:
6477             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6478             break;
6479           case MachinePlaysWhite:
6480           case IcsPlayingWhite:
6481             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6482             break;
6483           case TwoMachinesPlay:
6484             if (cps->twoMachinesColor[0] == 'w')
6485               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6486             else
6487               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6488             break;
6489           default:
6490             /* can't happen */
6491             break;
6492         }
6493         return;
6494     } else if (strncmp(message, "checkmate", 9) == 0) {
6495         if (WhiteOnMove(forwardMostMove)) {
6496             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6497         } else {
6498             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6499         }
6500         return;
6501     } else if (strstr(message, "Draw") != NULL ||
6502                strstr(message, "game is a draw") != NULL) {
6503         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6504         return;
6505     } else if (strstr(message, "offer") != NULL &&
6506                strstr(message, "draw") != NULL) {
6507 #if ZIPPY
6508         if (appData.zippyPlay && first.initDone) {
6509             /* Relay offer to ICS */
6510             SendToICS(ics_prefix);
6511             SendToICS("draw\n");
6512         }
6513 #endif
6514         cps->offeredDraw = 2; /* valid until this engine moves twice */
6515         if (gameMode == TwoMachinesPlay) {
6516             if (cps->other->offeredDraw) {
6517                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6518             /* [HGM] in two-machine mode we delay relaying draw offer      */
6519             /* until after we also have move, to see if it is really claim */
6520             }
6521         } else if (gameMode == MachinePlaysWhite ||
6522                    gameMode == MachinePlaysBlack) {
6523           if (userOfferedDraw) {
6524             DisplayInformation(_("Machine accepts your draw offer"));
6525             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6526           } else {
6527             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6528           }
6529         }
6530     }
6531
6532     
6533     /*
6534      * Look for thinking output
6535      */
6536     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6537           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6538                                 ) {
6539         int plylev, mvleft, mvtot, curscore, time;
6540         char mvname[MOVE_LEN];
6541         u64 nodes; // [DM]
6542         char plyext;
6543         int ignore = FALSE;
6544         int prefixHint = FALSE;
6545         mvname[0] = NULLCHAR;
6546
6547         switch (gameMode) {
6548           case MachinePlaysBlack:
6549           case IcsPlayingBlack:
6550             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6551             break;
6552           case MachinePlaysWhite:
6553           case IcsPlayingWhite:
6554             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6555             break;
6556           case AnalyzeMode:
6557           case AnalyzeFile:
6558             break;
6559           case IcsObserving: /* [DM] icsEngineAnalyze */
6560             if (!appData.icsEngineAnalyze) ignore = TRUE;
6561             break;
6562           case TwoMachinesPlay:
6563             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6564                 ignore = TRUE;
6565             }
6566             break;
6567           default:
6568             ignore = TRUE;
6569             break;
6570         }
6571
6572         if (!ignore) {
6573             buf1[0] = NULLCHAR;
6574             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6575                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6576
6577                 if (plyext != ' ' && plyext != '\t') {
6578                     time *= 100;
6579                 }
6580
6581                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6582                 if( cps->scoreIsAbsolute && 
6583                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6584                 {
6585                     curscore = -curscore;
6586                 }
6587
6588
6589                 programStats.depth = plylev;
6590                 programStats.nodes = nodes;
6591                 programStats.time = time;
6592                 programStats.score = curscore;
6593                 programStats.got_only_move = 0;
6594
6595                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6596                         int ticklen;
6597
6598                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6599                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6600                         if(WhiteOnMove(forwardMostMove)) 
6601                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6602                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6603                 }
6604
6605                 /* Buffer overflow protection */
6606                 if (buf1[0] != NULLCHAR) {
6607                     if (strlen(buf1) >= sizeof(programStats.movelist)
6608                         && appData.debugMode) {
6609                         fprintf(debugFP,
6610                                 "PV is too long; using the first %d bytes.\n",
6611                                 sizeof(programStats.movelist) - 1);
6612                     }
6613
6614                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6615                 } else {
6616                     sprintf(programStats.movelist, " no PV\n");
6617                 }
6618
6619                 if (programStats.seen_stat) {
6620                     programStats.ok_to_send = 1;
6621                 }
6622
6623                 if (strchr(programStats.movelist, '(') != NULL) {
6624                     programStats.line_is_book = 1;
6625                     programStats.nr_moves = 0;
6626                     programStats.moves_left = 0;
6627                 } else {
6628                     programStats.line_is_book = 0;
6629                 }
6630
6631                 SendProgramStatsToFrontend( cps, &programStats );
6632
6633                 /* 
6634                     [AS] Protect the thinkOutput buffer from overflow... this
6635                     is only useful if buf1 hasn't overflowed first!
6636                 */
6637                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6638                         plylev, 
6639                         (gameMode == TwoMachinesPlay ?
6640                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6641                         ((double) curscore) / 100.0,
6642                         prefixHint ? lastHint : "",
6643                         prefixHint ? " " : "" );
6644
6645                 if( buf1[0] != NULLCHAR ) {
6646                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6647
6648                     if( strlen(buf1) > max_len ) {
6649                         if( appData.debugMode) {
6650                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6651                         }
6652                         buf1[max_len+1] = '\0';
6653                     }
6654
6655                     strcat( thinkOutput, buf1 );
6656                 }
6657
6658                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6659                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6660                     DisplayMove(currentMove - 1);
6661                     DisplayAnalysis();
6662                 }
6663                 return;
6664
6665             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6666                 /* crafty (9.25+) says "(only move) <move>"
6667                  * if there is only 1 legal move
6668                  */
6669                 sscanf(p, "(only move) %s", buf1);
6670                 sprintf(thinkOutput, "%s (only move)", buf1);
6671                 sprintf(programStats.movelist, "%s (only move)", buf1);
6672                 programStats.depth = 1;
6673                 programStats.nr_moves = 1;
6674                 programStats.moves_left = 1;
6675                 programStats.nodes = 1;
6676                 programStats.time = 1;
6677                 programStats.got_only_move = 1;
6678
6679                 /* Not really, but we also use this member to
6680                    mean "line isn't going to change" (Crafty
6681                    isn't searching, so stats won't change) */
6682                 programStats.line_is_book = 1;
6683
6684                 SendProgramStatsToFrontend( cps, &programStats );
6685                 
6686                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6687                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6688                     DisplayMove(currentMove - 1);
6689                     DisplayAnalysis();
6690                 }
6691                 return;
6692             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6693                               &time, &nodes, &plylev, &mvleft,
6694                               &mvtot, mvname) >= 5) {
6695                 /* The stat01: line is from Crafty (9.29+) in response
6696                    to the "." command */
6697                 programStats.seen_stat = 1;
6698                 cps->maybeThinking = TRUE;
6699
6700                 if (programStats.got_only_move || !appData.periodicUpdates)
6701                   return;
6702
6703                 programStats.depth = plylev;
6704                 programStats.time = time;
6705                 programStats.nodes = nodes;
6706                 programStats.moves_left = mvleft;
6707                 programStats.nr_moves = mvtot;
6708                 strcpy(programStats.move_name, mvname);
6709                 programStats.ok_to_send = 1;
6710                 programStats.movelist[0] = '\0';
6711
6712                 SendProgramStatsToFrontend( cps, &programStats );
6713
6714                 DisplayAnalysis();
6715                 return;
6716
6717             } else if (strncmp(message,"++",2) == 0) {
6718                 /* Crafty 9.29+ outputs this */
6719                 programStats.got_fail = 2;
6720                 return;
6721
6722             } else if (strncmp(message,"--",2) == 0) {
6723                 /* Crafty 9.29+ outputs this */
6724                 programStats.got_fail = 1;
6725                 return;
6726
6727             } else if (thinkOutput[0] != NULLCHAR &&
6728                        strncmp(message, "    ", 4) == 0) {
6729                 unsigned message_len;
6730
6731                 p = message;
6732                 while (*p && *p == ' ') p++;
6733
6734                 message_len = strlen( p );
6735
6736                 /* [AS] Avoid buffer overflow */
6737                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6738                     strcat(thinkOutput, " ");
6739                     strcat(thinkOutput, p);
6740                 }
6741
6742                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6743                     strcat(programStats.movelist, " ");
6744                     strcat(programStats.movelist, p);
6745                 }
6746
6747                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6748                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6749                     DisplayMove(currentMove - 1);
6750                     DisplayAnalysis();
6751                 }
6752                 return;
6753             }
6754         }
6755         else {
6756             buf1[0] = NULLCHAR;
6757
6758             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6759                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6760             {
6761                 ChessProgramStats cpstats;
6762
6763                 if (plyext != ' ' && plyext != '\t') {
6764                     time *= 100;
6765                 }
6766
6767                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6768                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6769                     curscore = -curscore;
6770                 }
6771
6772                 cpstats.depth = plylev;
6773                 cpstats.nodes = nodes;
6774                 cpstats.time = time;
6775                 cpstats.score = curscore;
6776                 cpstats.got_only_move = 0;
6777                 cpstats.movelist[0] = '\0';
6778
6779                 if (buf1[0] != NULLCHAR) {
6780                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6781                 }
6782
6783                 cpstats.ok_to_send = 0;
6784                 cpstats.line_is_book = 0;
6785                 cpstats.nr_moves = 0;
6786                 cpstats.moves_left = 0;
6787
6788                 SendProgramStatsToFrontend( cps, &cpstats );
6789             }
6790         }
6791     }
6792 }
6793
6794
6795 /* Parse a game score from the character string "game", and
6796    record it as the history of the current game.  The game
6797    score is NOT assumed to start from the standard position. 
6798    The display is not updated in any way.
6799    */
6800 void
6801 ParseGameHistory(game)
6802      char *game;
6803 {
6804     ChessMove moveType;
6805     int fromX, fromY, toX, toY, boardIndex;
6806     char promoChar;
6807     char *p, *q;
6808     char buf[MSG_SIZ];
6809
6810     if (appData.debugMode)
6811       fprintf(debugFP, "Parsing game history: %s\n", game);
6812
6813     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6814     gameInfo.site = StrSave(appData.icsHost);
6815     gameInfo.date = PGNDate();
6816     gameInfo.round = StrSave("-");
6817
6818     /* Parse out names of players */
6819     while (*game == ' ') game++;
6820     p = buf;
6821     while (*game != ' ') *p++ = *game++;
6822     *p = NULLCHAR;
6823     gameInfo.white = StrSave(buf);
6824     while (*game == ' ') game++;
6825     p = buf;
6826     while (*game != ' ' && *game != '\n') *p++ = *game++;
6827     *p = NULLCHAR;
6828     gameInfo.black = StrSave(buf);
6829
6830     /* Parse moves */
6831     boardIndex = blackPlaysFirst ? 1 : 0;
6832     yynewstr(game);
6833     for (;;) {
6834         yyboardindex = boardIndex;
6835         moveType = (ChessMove) yylex();
6836         switch (moveType) {
6837           case IllegalMove:             /* maybe suicide chess, etc. */
6838   if (appData.debugMode) {
6839     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6840     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6841     setbuf(debugFP, NULL);
6842   }
6843           case WhitePromotionChancellor:
6844           case BlackPromotionChancellor:
6845           case WhitePromotionArchbishop:
6846           case BlackPromotionArchbishop:
6847           case WhitePromotionQueen:
6848           case BlackPromotionQueen:
6849           case WhitePromotionRook:
6850           case BlackPromotionRook:
6851           case WhitePromotionBishop:
6852           case BlackPromotionBishop:
6853           case WhitePromotionKnight:
6854           case BlackPromotionKnight:
6855           case WhitePromotionKing:
6856           case BlackPromotionKing:
6857           case NormalMove:
6858           case WhiteCapturesEnPassant:
6859           case BlackCapturesEnPassant:
6860           case WhiteKingSideCastle:
6861           case WhiteQueenSideCastle:
6862           case BlackKingSideCastle:
6863           case BlackQueenSideCastle:
6864           case WhiteKingSideCastleWild:
6865           case WhiteQueenSideCastleWild:
6866           case BlackKingSideCastleWild:
6867           case BlackQueenSideCastleWild:
6868           /* PUSH Fabien */
6869           case WhiteHSideCastleFR:
6870           case WhiteASideCastleFR:
6871           case BlackHSideCastleFR:
6872           case BlackASideCastleFR:
6873           /* POP Fabien */
6874             fromX = currentMoveString[0] - AAA;
6875             fromY = currentMoveString[1] - ONE;
6876             toX = currentMoveString[2] - AAA;
6877             toY = currentMoveString[3] - ONE;
6878             promoChar = currentMoveString[4];
6879             break;
6880           case WhiteDrop:
6881           case BlackDrop:
6882             fromX = moveType == WhiteDrop ?
6883               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6884             (int) CharToPiece(ToLower(currentMoveString[0]));
6885             fromY = DROP_RANK;
6886             toX = currentMoveString[2] - AAA;
6887             toY = currentMoveString[3] - ONE;
6888             promoChar = NULLCHAR;
6889             break;
6890           case AmbiguousMove:
6891             /* bug? */
6892             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6893   if (appData.debugMode) {
6894     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6895     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6896     setbuf(debugFP, NULL);
6897   }
6898             DisplayError(buf, 0);
6899             return;
6900           case ImpossibleMove:
6901             /* bug? */
6902             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6903   if (appData.debugMode) {
6904     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6905     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6906     setbuf(debugFP, NULL);
6907   }
6908             DisplayError(buf, 0);
6909             return;
6910           case (ChessMove) 0:   /* end of file */
6911             if (boardIndex < backwardMostMove) {
6912                 /* Oops, gap.  How did that happen? */
6913                 DisplayError(_("Gap in move list"), 0);
6914                 return;
6915             }
6916             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6917             if (boardIndex > forwardMostMove) {
6918                 forwardMostMove = boardIndex;
6919             }
6920             return;
6921           case ElapsedTime:
6922             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6923                 strcat(parseList[boardIndex-1], " ");
6924                 strcat(parseList[boardIndex-1], yy_text);
6925             }
6926             continue;
6927           case Comment:
6928           case PGNTag:
6929           case NAG:
6930           default:
6931             /* ignore */
6932             continue;
6933           case WhiteWins:
6934           case BlackWins:
6935           case GameIsDrawn:
6936           case GameUnfinished:
6937             if (gameMode == IcsExamining) {
6938                 if (boardIndex < backwardMostMove) {
6939                     /* Oops, gap.  How did that happen? */
6940                     return;
6941                 }
6942                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6943                 return;
6944             }
6945             gameInfo.result = moveType;
6946             p = strchr(yy_text, '{');
6947             if (p == NULL) p = strchr(yy_text, '(');
6948             if (p == NULL) {
6949                 p = yy_text;
6950                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6951             } else {
6952                 q = strchr(p, *p == '{' ? '}' : ')');
6953                 if (q != NULL) *q = NULLCHAR;
6954                 p++;
6955             }
6956             gameInfo.resultDetails = StrSave(p);
6957             continue;
6958         }
6959         if (boardIndex >= forwardMostMove &&
6960             !(gameMode == IcsObserving && ics_gamenum == -1)) {
6961             backwardMostMove = blackPlaysFirst ? 1 : 0;
6962             return;
6963         }
6964         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6965                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6966                                  parseList[boardIndex]);
6967         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6968         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6969         /* currentMoveString is set as a side-effect of yylex */
6970         strcpy(moveList[boardIndex], currentMoveString);
6971         strcat(moveList[boardIndex], "\n");
6972         boardIndex++;
6973         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
6974                                         castlingRights[boardIndex], &epStatus[boardIndex]);
6975         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
6976                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
6977           case MT_NONE:
6978           case MT_STALEMATE:
6979           default:
6980             break;
6981           case MT_CHECK:
6982             if(gameInfo.variant != VariantShogi)
6983                 strcat(parseList[boardIndex - 1], "+");
6984             break;
6985           case MT_CHECKMATE:
6986           case MT_STAINMATE:
6987             strcat(parseList[boardIndex - 1], "#");
6988             break;
6989         }
6990     }
6991 }
6992
6993
6994 /* Apply a move to the given board  */
6995 void
6996 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
6997      int fromX, fromY, toX, toY;
6998      int promoChar;
6999      Board board;
7000      char *castling;
7001      char *ep;
7002 {
7003   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7004
7005     /* [HGM] compute & store e.p. status and castling rights for new position */
7006     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7007     { int i;
7008
7009       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7010       oldEP = *ep;
7011       *ep = EP_NONE;
7012
7013       if( board[toY][toX] != EmptySquare ) 
7014            *ep = EP_CAPTURE;  
7015
7016       if( board[fromY][fromX] == WhitePawn ) {
7017            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7018                *ep = EP_PAWN_MOVE;
7019            if( toY-fromY==2) {
7020                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7021                         gameInfo.variant != VariantBerolina || toX < fromX)
7022                       *ep = toX | berolina;
7023                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7024                         gameInfo.variant != VariantBerolina || toX > fromX) 
7025                       *ep = toX;
7026            }
7027       } else 
7028       if( board[fromY][fromX] == BlackPawn ) {
7029            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7030                *ep = EP_PAWN_MOVE; 
7031            if( toY-fromY== -2) {
7032                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7033                         gameInfo.variant != VariantBerolina || toX < fromX)
7034                       *ep = toX | berolina;
7035                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7036                         gameInfo.variant != VariantBerolina || toX > fromX) 
7037                       *ep = toX;
7038            }
7039        }
7040
7041        for(i=0; i<nrCastlingRights; i++) {
7042            if(castling[i] == fromX && castlingRank[i] == fromY ||
7043               castling[i] == toX   && castlingRank[i] == toY   
7044              ) castling[i] = -1; // revoke for moved or captured piece
7045        }
7046
7047     }
7048
7049   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7050   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7051        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7052          
7053   if (fromX == toX && fromY == toY) return;
7054
7055   if (fromY == DROP_RANK) {
7056         /* must be first */
7057         piece = board[toY][toX] = (ChessSquare) fromX;
7058   } else {
7059      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7060      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7061      if(gameInfo.variant == VariantKnightmate)
7062          king += (int) WhiteUnicorn - (int) WhiteKing;
7063
7064     /* Code added by Tord: */
7065     /* FRC castling assumed when king captures friendly rook. */
7066     if (board[fromY][fromX] == WhiteKing &&
7067              board[toY][toX] == WhiteRook) {
7068       board[fromY][fromX] = EmptySquare;
7069       board[toY][toX] = EmptySquare;
7070       if(toX > fromX) {
7071         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7072       } else {
7073         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7074       }
7075     } else if (board[fromY][fromX] == BlackKing &&
7076                board[toY][toX] == BlackRook) {
7077       board[fromY][fromX] = EmptySquare;
7078       board[toY][toX] = EmptySquare;
7079       if(toX > fromX) {
7080         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7081       } else {
7082         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7083       }
7084     /* End of code added by Tord */
7085
7086     } else if (board[fromY][fromX] == king
7087         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7088         && toY == fromY && toX > fromX+1) {
7089         board[fromY][fromX] = EmptySquare;
7090         board[toY][toX] = king;
7091         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7092         board[fromY][BOARD_RGHT-1] = EmptySquare;
7093     } else if (board[fromY][fromX] == king
7094         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7095                && toY == fromY && toX < fromX-1) {
7096         board[fromY][fromX] = EmptySquare;
7097         board[toY][toX] = king;
7098         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7099         board[fromY][BOARD_LEFT] = EmptySquare;
7100     } else if (board[fromY][fromX] == WhitePawn
7101                && toY == BOARD_HEIGHT-1
7102                && gameInfo.variant != VariantXiangqi
7103                ) {
7104         /* white pawn promotion */
7105         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7106         if (board[toY][toX] == EmptySquare) {
7107             board[toY][toX] = WhiteQueen;
7108         }
7109         if(gameInfo.variant==VariantBughouse ||
7110            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7111             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7112         board[fromY][fromX] = EmptySquare;
7113     } else if ((fromY == BOARD_HEIGHT-4)
7114                && (toX != fromX)
7115                && gameInfo.variant != VariantXiangqi
7116                && gameInfo.variant != VariantBerolina
7117                && (board[fromY][fromX] == WhitePawn)
7118                && (board[toY][toX] == EmptySquare)) {
7119         board[fromY][fromX] = EmptySquare;
7120         board[toY][toX] = WhitePawn;
7121         captured = board[toY - 1][toX];
7122         board[toY - 1][toX] = EmptySquare;
7123     } else if ((fromY == BOARD_HEIGHT-4)
7124                && (toX == fromX)
7125                && gameInfo.variant == VariantBerolina
7126                && (board[fromY][fromX] == WhitePawn)
7127                && (board[toY][toX] == EmptySquare)) {
7128         board[fromY][fromX] = EmptySquare;
7129         board[toY][toX] = WhitePawn;
7130         if(oldEP & EP_BEROLIN_A) {
7131                 captured = board[fromY][fromX-1];
7132                 board[fromY][fromX-1] = EmptySquare;
7133         }else{  captured = board[fromY][fromX+1];
7134                 board[fromY][fromX+1] = EmptySquare;
7135         }
7136     } else if (board[fromY][fromX] == king
7137         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7138                && toY == fromY && toX > fromX+1) {
7139         board[fromY][fromX] = EmptySquare;
7140         board[toY][toX] = king;
7141         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7142         board[fromY][BOARD_RGHT-1] = EmptySquare;
7143     } else if (board[fromY][fromX] == king
7144         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7145                && toY == fromY && toX < fromX-1) {
7146         board[fromY][fromX] = EmptySquare;
7147         board[toY][toX] = king;
7148         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7149         board[fromY][BOARD_LEFT] = EmptySquare;
7150     } else if (fromY == 7 && fromX == 3
7151                && board[fromY][fromX] == BlackKing
7152                && toY == 7 && toX == 5) {
7153         board[fromY][fromX] = EmptySquare;
7154         board[toY][toX] = BlackKing;
7155         board[fromY][7] = EmptySquare;
7156         board[toY][4] = BlackRook;
7157     } else if (fromY == 7 && fromX == 3
7158                && board[fromY][fromX] == BlackKing
7159                && toY == 7 && toX == 1) {
7160         board[fromY][fromX] = EmptySquare;
7161         board[toY][toX] = BlackKing;
7162         board[fromY][0] = EmptySquare;
7163         board[toY][2] = BlackRook;
7164     } else if (board[fromY][fromX] == BlackPawn
7165                && toY == 0
7166                && gameInfo.variant != VariantXiangqi
7167                ) {
7168         /* black pawn promotion */
7169         board[0][toX] = CharToPiece(ToLower(promoChar));
7170         if (board[0][toX] == EmptySquare) {
7171             board[0][toX] = BlackQueen;
7172         }
7173         if(gameInfo.variant==VariantBughouse ||
7174            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7175             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7176         board[fromY][fromX] = EmptySquare;
7177     } else if ((fromY == 3)
7178                && (toX != fromX)
7179                && gameInfo.variant != VariantXiangqi
7180                && gameInfo.variant != VariantBerolina
7181                && (board[fromY][fromX] == BlackPawn)
7182                && (board[toY][toX] == EmptySquare)) {
7183         board[fromY][fromX] = EmptySquare;
7184         board[toY][toX] = BlackPawn;
7185         captured = board[toY + 1][toX];
7186         board[toY + 1][toX] = EmptySquare;
7187     } else if ((fromY == 3)
7188                && (toX == fromX)
7189                && gameInfo.variant == VariantBerolina
7190                && (board[fromY][fromX] == BlackPawn)
7191                && (board[toY][toX] == EmptySquare)) {
7192         board[fromY][fromX] = EmptySquare;
7193         board[toY][toX] = BlackPawn;
7194         if(oldEP & EP_BEROLIN_A) {
7195                 captured = board[fromY][fromX-1];
7196                 board[fromY][fromX-1] = EmptySquare;
7197         }else{  captured = board[fromY][fromX+1];
7198                 board[fromY][fromX+1] = EmptySquare;
7199         }
7200     } else {
7201         board[toY][toX] = board[fromY][fromX];
7202         board[fromY][fromX] = EmptySquare;
7203     }
7204
7205     /* [HGM] now we promote for Shogi, if needed */
7206     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7207         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7208   }
7209
7210     if (gameInfo.holdingsWidth != 0) {
7211
7212       /* !!A lot more code needs to be written to support holdings  */
7213       /* [HGM] OK, so I have written it. Holdings are stored in the */
7214       /* penultimate board files, so they are automaticlly stored   */
7215       /* in the game history.                                       */
7216       if (fromY == DROP_RANK) {
7217         /* Delete from holdings, by decreasing count */
7218         /* and erasing image if necessary            */
7219         p = (int) fromX;
7220         if(p < (int) BlackPawn) { /* white drop */
7221              p -= (int)WhitePawn;
7222              if(p >= gameInfo.holdingsSize) p = 0;
7223              if(--board[p][BOARD_WIDTH-2] == 0)
7224                   board[p][BOARD_WIDTH-1] = EmptySquare;
7225         } else {                  /* black drop */
7226              p -= (int)BlackPawn;
7227              if(p >= gameInfo.holdingsSize) p = 0;
7228              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7229                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7230         }
7231       }
7232       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7233           && gameInfo.variant != VariantBughouse        ) {
7234         /* [HGM] holdings: Add to holdings, if holdings exist */
7235         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7236                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7237                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7238         }
7239         p = (int) captured;
7240         if (p >= (int) BlackPawn) {
7241           p -= (int)BlackPawn;
7242           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7243                   /* in Shogi restore piece to its original  first */
7244                   captured = (ChessSquare) (DEMOTED captured);
7245                   p = DEMOTED p;
7246           }
7247           p = PieceToNumber((ChessSquare)p);
7248           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7249           board[p][BOARD_WIDTH-2]++;
7250           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7251         } else {
7252           p -= (int)WhitePawn;
7253           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7254                   captured = (ChessSquare) (DEMOTED captured);
7255                   p = DEMOTED p;
7256           }
7257           p = PieceToNumber((ChessSquare)p);
7258           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7259           board[BOARD_HEIGHT-1-p][1]++;
7260           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7261         }
7262       }
7263
7264     } else if (gameInfo.variant == VariantAtomic) {
7265       if (captured != EmptySquare) {
7266         int y, x;
7267         for (y = toY-1; y <= toY+1; y++) {
7268           for (x = toX-1; x <= toX+1; x++) {
7269             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7270                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7271               board[y][x] = EmptySquare;
7272             }
7273           }
7274         }
7275         board[toY][toX] = EmptySquare;
7276       }
7277     }
7278     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7279         /* [HGM] Shogi promotions */
7280         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7281     }
7282
7283     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7284                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7285         // [HGM] superchess: take promotion piece out of holdings
7286         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7287         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7288             if(!--board[k][BOARD_WIDTH-2])
7289                 board[k][BOARD_WIDTH-1] = EmptySquare;
7290         } else {
7291             if(!--board[BOARD_HEIGHT-1-k][1])
7292                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7293         }
7294     }
7295
7296 }
7297
7298 /* Updates forwardMostMove */
7299 void
7300 MakeMove(fromX, fromY, toX, toY, promoChar)
7301      int fromX, fromY, toX, toY;
7302      int promoChar;
7303 {
7304 //    forwardMostMove++; // [HGM] bare: moved downstream
7305
7306     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7307         int timeLeft; static int lastLoadFlag=0; int king, piece;
7308         piece = boards[forwardMostMove][fromY][fromX];
7309         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7310         if(gameInfo.variant == VariantKnightmate)
7311             king += (int) WhiteUnicorn - (int) WhiteKing;
7312         if(forwardMostMove == 0) {
7313             if(blackPlaysFirst) 
7314                 fprintf(serverMoves, "%s;", second.tidy);
7315             fprintf(serverMoves, "%s;", first.tidy);
7316             if(!blackPlaysFirst) 
7317                 fprintf(serverMoves, "%s;", second.tidy);
7318         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7319         lastLoadFlag = loadFlag;
7320         // print base move
7321         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7322         // print castling suffix
7323         if( toY == fromY && piece == king ) {
7324             if(toX-fromX > 1)
7325                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7326             if(fromX-toX >1)
7327                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7328         }
7329         // e.p. suffix
7330         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7331              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7332              boards[forwardMostMove][toY][toX] == EmptySquare
7333              && fromX != toX )
7334                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7335         // promotion suffix
7336         if(promoChar != NULLCHAR)
7337                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7338         if(!loadFlag) {
7339             fprintf(serverMoves, "/%d/%d",
7340                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7341             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7342             else                      timeLeft = blackTimeRemaining/1000;
7343             fprintf(serverMoves, "/%d", timeLeft);
7344         }
7345         fflush(serverMoves);
7346     }
7347
7348     if (forwardMostMove+1 >= MAX_MOVES) {
7349       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7350                         0, 1);
7351       return;
7352     }
7353     if (commentList[forwardMostMove+1] != NULL) {
7354         free(commentList[forwardMostMove+1]);
7355         commentList[forwardMostMove+1] = NULL;
7356     }
7357     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7358     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7359     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7360                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7361     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7362     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7363     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7364     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7365     gameInfo.result = GameUnfinished;
7366     if (gameInfo.resultDetails != NULL) {
7367         free(gameInfo.resultDetails);
7368         gameInfo.resultDetails = NULL;
7369     }
7370     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7371                               moveList[forwardMostMove - 1]);
7372     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7373                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7374                              fromY, fromX, toY, toX, promoChar,
7375                              parseList[forwardMostMove - 1]);
7376     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7377                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7378                             castlingRights[forwardMostMove]) ) {
7379       case MT_NONE:
7380       case MT_STALEMATE:
7381       default:
7382         break;
7383       case MT_CHECK:
7384         if(gameInfo.variant != VariantShogi)
7385             strcat(parseList[forwardMostMove - 1], "+");
7386         break;
7387       case MT_CHECKMATE:
7388       case MT_STAINMATE:
7389         strcat(parseList[forwardMostMove - 1], "#");
7390         break;
7391     }
7392     if (appData.debugMode) {
7393         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7394     }
7395
7396 }
7397
7398 /* Updates currentMove if not pausing */
7399 void
7400 ShowMove(fromX, fromY, toX, toY)
7401 {
7402     int instant = (gameMode == PlayFromGameFile) ?
7403         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7404     if(appData.noGUI) return;
7405     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7406         if (!instant) {
7407             if (forwardMostMove == currentMove + 1) {
7408                 AnimateMove(boards[forwardMostMove - 1],
7409                             fromX, fromY, toX, toY);
7410             }
7411             if (appData.highlightLastMove) {
7412                 SetHighlights(fromX, fromY, toX, toY);
7413             }
7414         }
7415         currentMove = forwardMostMove;
7416     }
7417
7418     if (instant) return;
7419
7420     DisplayMove(currentMove - 1);
7421     DrawPosition(FALSE, boards[currentMove]);
7422     DisplayBothClocks();
7423     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7424 }
7425
7426 void SendEgtPath(ChessProgramState *cps)
7427 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7428         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7429
7430         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7431
7432         while(*p) {
7433             char c, *q = name+1, *r, *s;
7434
7435             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7436             while(*p && *p != ',') *q++ = *p++;
7437             *q++ = ':'; *q = 0;
7438             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7439                 strcmp(name, ",nalimov:") == 0 ) {
7440                 // take nalimov path from the menu-changeable option first, if it is defined
7441                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7442                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7443             } else
7444             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7445                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7446                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7447                 s = r = StrStr(s, ":") + 1; // beginning of path info
7448                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7449                 c = *r; *r = 0;             // temporarily null-terminate path info
7450                     *--q = 0;               // strip of trailig ':' from name
7451                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7452                 *r = c;
7453                 SendToProgram(buf,cps);     // send egtbpath command for this format
7454             }
7455             if(*p == ',') p++; // read away comma to position for next format name
7456         }
7457 }
7458
7459 void
7460 InitChessProgram(cps, setup)
7461      ChessProgramState *cps;
7462      int setup; /* [HGM] needed to setup FRC opening position */
7463 {
7464     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7465     if (appData.noChessProgram) return;
7466     hintRequested = FALSE;
7467     bookRequested = FALSE;
7468
7469     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7470     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7471     if(cps->memSize) { /* [HGM] memory */
7472         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7473         SendToProgram(buf, cps);
7474     }
7475     SendEgtPath(cps); /* [HGM] EGT */
7476     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7477         sprintf(buf, "cores %d\n", appData.smpCores);
7478         SendToProgram(buf, cps);
7479     }
7480
7481     SendToProgram(cps->initString, cps);
7482     if (gameInfo.variant != VariantNormal &&
7483         gameInfo.variant != VariantLoadable
7484         /* [HGM] also send variant if board size non-standard */
7485         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7486                                             ) {
7487       char *v = VariantName(gameInfo.variant);
7488       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7489         /* [HGM] in protocol 1 we have to assume all variants valid */
7490         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7491         DisplayFatalError(buf, 0, 1);
7492         return;
7493       }
7494
7495       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7496       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7497       if( gameInfo.variant == VariantXiangqi )
7498            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7499       if( gameInfo.variant == VariantShogi )
7500            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7501       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7502            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7503       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7504                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7505            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7506       if( gameInfo.variant == VariantCourier )
7507            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7508       if( gameInfo.variant == VariantSuper )
7509            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7510       if( gameInfo.variant == VariantGreat )
7511            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7512
7513       if(overruled) {
7514            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7515                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7516            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7517            if(StrStr(cps->variants, b) == NULL) { 
7518                // specific sized variant not known, check if general sizing allowed
7519                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7520                    if(StrStr(cps->variants, "boardsize") == NULL) {
7521                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7522                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7523                        DisplayFatalError(buf, 0, 1);
7524                        return;
7525                    }
7526                    /* [HGM] here we really should compare with the maximum supported board size */
7527                }
7528            }
7529       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7530       sprintf(buf, "variant %s\n", b);
7531       SendToProgram(buf, cps);
7532     }
7533     currentlyInitializedVariant = gameInfo.variant;
7534
7535     /* [HGM] send opening position in FRC to first engine */
7536     if(setup) {
7537           SendToProgram("force\n", cps);
7538           SendBoard(cps, 0);
7539           /* engine is now in force mode! Set flag to wake it up after first move. */
7540           setboardSpoiledMachineBlack = 1;
7541     }
7542
7543     if (cps->sendICS) {
7544       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7545       SendToProgram(buf, cps);
7546     }
7547     cps->maybeThinking = FALSE;
7548     cps->offeredDraw = 0;
7549     if (!appData.icsActive) {
7550         SendTimeControl(cps, movesPerSession, timeControl,
7551                         timeIncrement, appData.searchDepth,
7552                         searchTime);
7553     }
7554     if (appData.showThinking 
7555         // [HGM] thinking: four options require thinking output to be sent
7556         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7557                                 ) {
7558         SendToProgram("post\n", cps);
7559     }
7560     SendToProgram("hard\n", cps);
7561     if (!appData.ponderNextMove) {
7562         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7563            it without being sure what state we are in first.  "hard"
7564            is not a toggle, so that one is OK.
7565          */
7566         SendToProgram("easy\n", cps);
7567     }
7568     if (cps->usePing) {
7569       sprintf(buf, "ping %d\n", ++cps->lastPing);
7570       SendToProgram(buf, cps);
7571     }
7572     cps->initDone = TRUE;
7573 }   
7574
7575
7576 void
7577 StartChessProgram(cps)
7578      ChessProgramState *cps;
7579 {
7580     char buf[MSG_SIZ];
7581     int err;
7582
7583     if (appData.noChessProgram) return;
7584     cps->initDone = FALSE;
7585
7586     if (strcmp(cps->host, "localhost") == 0) {
7587         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7588     } else if (*appData.remoteShell == NULLCHAR) {
7589         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7590     } else {
7591         if (*appData.remoteUser == NULLCHAR) {
7592           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7593                     cps->program);
7594         } else {
7595           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7596                     cps->host, appData.remoteUser, cps->program);
7597         }
7598         err = StartChildProcess(buf, "", &cps->pr);
7599     }
7600     
7601     if (err != 0) {
7602         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7603         DisplayFatalError(buf, err, 1);
7604         cps->pr = NoProc;
7605         cps->isr = NULL;
7606         return;
7607     }
7608     
7609     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7610     if (cps->protocolVersion > 1) {
7611       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7612       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7613       cps->comboCnt = 0;  //                and values of combo boxes
7614       SendToProgram(buf, cps);
7615     } else {
7616       SendToProgram("xboard\n", cps);
7617     }
7618 }
7619
7620
7621 void
7622 TwoMachinesEventIfReady P((void))
7623 {
7624   if (first.lastPing != first.lastPong) {
7625     DisplayMessage("", _("Waiting for first chess program"));
7626     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7627     return;
7628   }
7629   if (second.lastPing != second.lastPong) {
7630     DisplayMessage("", _("Waiting for second chess program"));
7631     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7632     return;
7633   }
7634   ThawUI();
7635   TwoMachinesEvent();
7636 }
7637
7638 void
7639 NextMatchGame P((void))
7640 {
7641     int index; /* [HGM] autoinc: step lod index during match */
7642     Reset(FALSE, TRUE);
7643     if (*appData.loadGameFile != NULLCHAR) {
7644         index = appData.loadGameIndex;
7645         if(index < 0) { // [HGM] autoinc
7646             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7647             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7648         } 
7649         LoadGameFromFile(appData.loadGameFile,
7650                          index,
7651                          appData.loadGameFile, FALSE);
7652     } else if (*appData.loadPositionFile != NULLCHAR) {
7653         index = appData.loadPositionIndex;
7654         if(index < 0) { // [HGM] autoinc
7655             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7656             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7657         } 
7658         LoadPositionFromFile(appData.loadPositionFile,
7659                              index,
7660                              appData.loadPositionFile);
7661     }
7662     TwoMachinesEventIfReady();
7663 }
7664
7665 void UserAdjudicationEvent( int result )
7666 {
7667     ChessMove gameResult = GameIsDrawn;
7668
7669     if( result > 0 ) {
7670         gameResult = WhiteWins;
7671     }
7672     else if( result < 0 ) {
7673         gameResult = BlackWins;
7674     }
7675
7676     if( gameMode == TwoMachinesPlay ) {
7677         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7678     }
7679 }
7680
7681
7682 // [HGM] save: calculate checksum of game to make games easily identifiable
7683 int StringCheckSum(char *s)
7684 {
7685         int i = 0;
7686         if(s==NULL) return 0;
7687         while(*s) i = i*259 + *s++;
7688         return i;
7689 }
7690
7691 int GameCheckSum()
7692 {
7693         int i, sum=0;
7694         for(i=backwardMostMove; i<forwardMostMove; i++) {
7695                 sum += pvInfoList[i].depth;
7696                 sum += StringCheckSum(parseList[i]);
7697                 sum += StringCheckSum(commentList[i]);
7698                 sum *= 261;
7699         }
7700         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7701         return sum + StringCheckSum(commentList[i]);
7702 } // end of save patch
7703
7704 void
7705 GameEnds(result, resultDetails, whosays)
7706      ChessMove result;
7707      char *resultDetails;
7708      int whosays;
7709 {
7710     GameMode nextGameMode;
7711     int isIcsGame;
7712     char buf[MSG_SIZ];
7713
7714     if(endingGame) return; /* [HGM] crash: forbid recursion */
7715     endingGame = 1;
7716
7717     if (appData.debugMode) {
7718       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7719               result, resultDetails ? resultDetails : "(null)", whosays);
7720     }
7721
7722     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7723         /* If we are playing on ICS, the server decides when the
7724            game is over, but the engine can offer to draw, claim 
7725            a draw, or resign. 
7726          */
7727 #if ZIPPY
7728         if (appData.zippyPlay && first.initDone) {
7729             if (result == GameIsDrawn) {
7730                 /* In case draw still needs to be claimed */
7731                 SendToICS(ics_prefix);
7732                 SendToICS("draw\n");
7733             } else if (StrCaseStr(resultDetails, "resign")) {
7734                 SendToICS(ics_prefix);
7735                 SendToICS("resign\n");
7736             }
7737         }
7738 #endif
7739         endingGame = 0; /* [HGM] crash */
7740         return;
7741     }
7742
7743     /* If we're loading the game from a file, stop */
7744     if (whosays == GE_FILE) {
7745       (void) StopLoadGameTimer();
7746       gameFileFP = NULL;
7747     }
7748
7749     /* Cancel draw offers */
7750     first.offeredDraw = second.offeredDraw = 0;
7751
7752     /* If this is an ICS game, only ICS can really say it's done;
7753        if not, anyone can. */
7754     isIcsGame = (gameMode == IcsPlayingWhite || 
7755                  gameMode == IcsPlayingBlack || 
7756                  gameMode == IcsObserving    || 
7757                  gameMode == IcsExamining);
7758
7759     if (!isIcsGame || whosays == GE_ICS) {
7760         /* OK -- not an ICS game, or ICS said it was done */
7761         StopClocks();
7762         if (!isIcsGame && !appData.noChessProgram) 
7763           SetUserThinkingEnables();
7764     
7765         /* [HGM] if a machine claims the game end we verify this claim */
7766         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7767             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7768                 char claimer;
7769                 ChessMove trueResult = (ChessMove) -1;
7770
7771                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7772                                             first.twoMachinesColor[0] :
7773                                             second.twoMachinesColor[0] ;
7774
7775                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7776                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7777                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7778                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7779                 } else
7780                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7781                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7782                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7783                 } else
7784                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7785                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7786                 }
7787
7788                 // now verify win claims, but not in drop games, as we don't understand those yet
7789                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7790                                                  || gameInfo.variant == VariantGreat) &&
7791                     (result == WhiteWins && claimer == 'w' ||
7792                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7793                       if (appData.debugMode) {
7794                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7795                                 result, epStatus[forwardMostMove], forwardMostMove);
7796                       }
7797                       if(result != trueResult) {
7798                               sprintf(buf, "False win claim: '%s'", resultDetails);
7799                               result = claimer == 'w' ? BlackWins : WhiteWins;
7800                               resultDetails = buf;
7801                       }
7802                 } else
7803                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7804                     && (forwardMostMove <= backwardMostMove ||
7805                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7806                         (claimer=='b')==(forwardMostMove&1))
7807                                                                                   ) {
7808                       /* [HGM] verify: draws that were not flagged are false claims */
7809                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7810                       result = claimer == 'w' ? BlackWins : WhiteWins;
7811                       resultDetails = buf;
7812                 }
7813                 /* (Claiming a loss is accepted no questions asked!) */
7814             }
7815             /* [HGM] bare: don't allow bare King to win */
7816             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7817                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7818                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7819                && result != GameIsDrawn)
7820             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7821                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7822                         int p = (int)boards[forwardMostMove][i][j] - color;
7823                         if(p >= 0 && p <= (int)WhiteKing) k++;
7824                 }
7825                 if (appData.debugMode) {
7826                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7827                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7828                 }
7829                 if(k <= 1) {
7830                         result = GameIsDrawn;
7831                         sprintf(buf, "%s but bare king", resultDetails);
7832                         resultDetails = buf;
7833                 }
7834             }
7835         }
7836
7837
7838         if(serverMoves != NULL && !loadFlag) { char c = '=';
7839             if(result==WhiteWins) c = '+';
7840             if(result==BlackWins) c = '-';
7841             if(resultDetails != NULL)
7842                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7843         }
7844         if (resultDetails != NULL) {
7845             gameInfo.result = result;
7846             gameInfo.resultDetails = StrSave(resultDetails);
7847
7848             /* display last move only if game was not loaded from file */
7849             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7850                 DisplayMove(currentMove - 1);
7851     
7852             if (forwardMostMove != 0) {
7853                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7854                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7855                                                                 ) {
7856                     if (*appData.saveGameFile != NULLCHAR) {
7857                         SaveGameToFile(appData.saveGameFile, TRUE);
7858                     } else if (appData.autoSaveGames) {
7859                         AutoSaveGame();
7860                     }
7861                     if (*appData.savePositionFile != NULLCHAR) {
7862                         SavePositionToFile(appData.savePositionFile);
7863                     }
7864                 }
7865             }
7866
7867             /* Tell program how game ended in case it is learning */
7868             /* [HGM] Moved this to after saving the PGN, just in case */
7869             /* engine died and we got here through time loss. In that */
7870             /* case we will get a fatal error writing the pipe, which */
7871             /* would otherwise lose us the PGN.                       */
7872             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7873             /* output during GameEnds should never be fatal anymore   */
7874             if (gameMode == MachinePlaysWhite ||
7875                 gameMode == MachinePlaysBlack ||
7876                 gameMode == TwoMachinesPlay ||
7877                 gameMode == IcsPlayingWhite ||
7878                 gameMode == IcsPlayingBlack ||
7879                 gameMode == BeginningOfGame) {
7880                 char buf[MSG_SIZ];
7881                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7882                         resultDetails);
7883                 if (first.pr != NoProc) {
7884                     SendToProgram(buf, &first);
7885                 }
7886                 if (second.pr != NoProc &&
7887                     gameMode == TwoMachinesPlay) {
7888                     SendToProgram(buf, &second);
7889                 }
7890             }
7891         }
7892
7893         if (appData.icsActive) {
7894             if (appData.quietPlay &&
7895                 (gameMode == IcsPlayingWhite ||
7896                  gameMode == IcsPlayingBlack)) {
7897                 SendToICS(ics_prefix);
7898                 SendToICS("set shout 1\n");
7899             }
7900             nextGameMode = IcsIdle;
7901             ics_user_moved = FALSE;
7902             /* clean up premove.  It's ugly when the game has ended and the
7903              * premove highlights are still on the board.
7904              */
7905             if (gotPremove) {
7906               gotPremove = FALSE;
7907               ClearPremoveHighlights();
7908               DrawPosition(FALSE, boards[currentMove]);
7909             }
7910             if (whosays == GE_ICS) {
7911                 switch (result) {
7912                 case WhiteWins:
7913                     if (gameMode == IcsPlayingWhite)
7914                         PlayIcsWinSound();
7915                     else if(gameMode == IcsPlayingBlack)
7916                         PlayIcsLossSound();
7917                     break;
7918                 case BlackWins:
7919                     if (gameMode == IcsPlayingBlack)
7920                         PlayIcsWinSound();
7921                     else if(gameMode == IcsPlayingWhite)
7922                         PlayIcsLossSound();
7923                     break;
7924                 case GameIsDrawn:
7925                     PlayIcsDrawSound();
7926                     break;
7927                 default:
7928                     PlayIcsUnfinishedSound();
7929                 }
7930             }
7931         } else if (gameMode == EditGame ||
7932                    gameMode == PlayFromGameFile || 
7933                    gameMode == AnalyzeMode || 
7934                    gameMode == AnalyzeFile) {
7935             nextGameMode = gameMode;
7936         } else {
7937             nextGameMode = EndOfGame;
7938         }
7939         pausing = FALSE;
7940         ModeHighlight();
7941     } else {
7942         nextGameMode = gameMode;
7943     }
7944
7945     if (appData.noChessProgram) {
7946         gameMode = nextGameMode;
7947         ModeHighlight();
7948         endingGame = 0; /* [HGM] crash */
7949         return;
7950     }
7951
7952     if (first.reuse) {
7953         /* Put first chess program into idle state */
7954         if (first.pr != NoProc &&
7955             (gameMode == MachinePlaysWhite ||
7956              gameMode == MachinePlaysBlack ||
7957              gameMode == TwoMachinesPlay ||
7958              gameMode == IcsPlayingWhite ||
7959              gameMode == IcsPlayingBlack ||
7960              gameMode == BeginningOfGame)) {
7961             SendToProgram("force\n", &first);
7962             if (first.usePing) {
7963               char buf[MSG_SIZ];
7964               sprintf(buf, "ping %d\n", ++first.lastPing);
7965               SendToProgram(buf, &first);
7966             }
7967         }
7968     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7969         /* Kill off first chess program */
7970         if (first.isr != NULL)
7971           RemoveInputSource(first.isr);
7972         first.isr = NULL;
7973     
7974         if (first.pr != NoProc) {
7975             ExitAnalyzeMode();
7976             DoSleep( appData.delayBeforeQuit );
7977             SendToProgram("quit\n", &first);
7978             DoSleep( appData.delayAfterQuit );
7979             DestroyChildProcess(first.pr, first.useSigterm);
7980         }
7981         first.pr = NoProc;
7982     }
7983     if (second.reuse) {
7984         /* Put second chess program into idle state */
7985         if (second.pr != NoProc &&
7986             gameMode == TwoMachinesPlay) {
7987             SendToProgram("force\n", &second);
7988             if (second.usePing) {
7989               char buf[MSG_SIZ];
7990               sprintf(buf, "ping %d\n", ++second.lastPing);
7991               SendToProgram(buf, &second);
7992             }
7993         }
7994     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7995         /* Kill off second chess program */
7996         if (second.isr != NULL)
7997           RemoveInputSource(second.isr);
7998         second.isr = NULL;
7999     
8000         if (second.pr != NoProc) {
8001             DoSleep( appData.delayBeforeQuit );
8002             SendToProgram("quit\n", &second);
8003             DoSleep( appData.delayAfterQuit );
8004             DestroyChildProcess(second.pr, second.useSigterm);
8005         }
8006         second.pr = NoProc;
8007     }
8008
8009     if (matchMode && gameMode == TwoMachinesPlay) {
8010         switch (result) {
8011         case WhiteWins:
8012           if (first.twoMachinesColor[0] == 'w') {
8013             first.matchWins++;
8014           } else {
8015             second.matchWins++;
8016           }
8017           break;
8018         case BlackWins:
8019           if (first.twoMachinesColor[0] == 'b') {
8020             first.matchWins++;
8021           } else {
8022             second.matchWins++;
8023           }
8024           break;
8025         default:
8026           break;
8027         }
8028         if (matchGame < appData.matchGames) {
8029             char *tmp;
8030             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8031                 tmp = first.twoMachinesColor;
8032                 first.twoMachinesColor = second.twoMachinesColor;
8033                 second.twoMachinesColor = tmp;
8034             }
8035             gameMode = nextGameMode;
8036             matchGame++;
8037             if(appData.matchPause>10000 || appData.matchPause<10)
8038                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8039             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8040             endingGame = 0; /* [HGM] crash */
8041             return;
8042         } else {
8043             char buf[MSG_SIZ];
8044             gameMode = nextGameMode;
8045             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8046                     first.tidy, second.tidy,
8047                     first.matchWins, second.matchWins,
8048                     appData.matchGames - (first.matchWins + second.matchWins));
8049             DisplayFatalError(buf, 0, 0);
8050         }
8051     }
8052     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8053         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8054       ExitAnalyzeMode();
8055     gameMode = nextGameMode;
8056     ModeHighlight();
8057     endingGame = 0;  /* [HGM] crash */
8058 }
8059
8060 /* Assumes program was just initialized (initString sent).
8061    Leaves program in force mode. */
8062 void
8063 FeedMovesToProgram(cps, upto) 
8064      ChessProgramState *cps;
8065      int upto;
8066 {
8067     int i;
8068     
8069     if (appData.debugMode)
8070       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8071               startedFromSetupPosition ? "position and " : "",
8072               backwardMostMove, upto, cps->which);
8073     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8074         // [HGM] variantswitch: make engine aware of new variant
8075         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8076                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8077         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8078         SendToProgram(buf, cps);
8079         currentlyInitializedVariant = gameInfo.variant;
8080     }
8081     SendToProgram("force\n", cps);
8082     if (startedFromSetupPosition) {
8083         SendBoard(cps, backwardMostMove);
8084     if (appData.debugMode) {
8085         fprintf(debugFP, "feedMoves\n");
8086     }
8087     }
8088     for (i = backwardMostMove; i < upto; i++) {
8089         SendMoveToProgram(i, cps);
8090     }
8091 }
8092
8093
8094 void
8095 ResurrectChessProgram()
8096 {
8097      /* The chess program may have exited.
8098         If so, restart it and feed it all the moves made so far. */
8099
8100     if (appData.noChessProgram || first.pr != NoProc) return;
8101     
8102     StartChessProgram(&first);
8103     InitChessProgram(&first, FALSE);
8104     FeedMovesToProgram(&first, currentMove);
8105
8106     if (!first.sendTime) {
8107         /* can't tell gnuchess what its clock should read,
8108            so we bow to its notion. */
8109         ResetClocks();
8110         timeRemaining[0][currentMove] = whiteTimeRemaining;
8111         timeRemaining[1][currentMove] = blackTimeRemaining;
8112     }
8113
8114     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8115                 appData.icsEngineAnalyze) && first.analysisSupport) {
8116       SendToProgram("analyze\n", &first);
8117       first.analyzing = TRUE;
8118     }
8119 }
8120
8121 /*
8122  * Button procedures
8123  */
8124 void
8125 Reset(redraw, init)
8126      int redraw, init;
8127 {
8128     int i;
8129
8130     if (appData.debugMode) {
8131         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8132                 redraw, init, gameMode);
8133     }
8134     pausing = pauseExamInvalid = FALSE;
8135     startedFromSetupPosition = blackPlaysFirst = FALSE;
8136     firstMove = TRUE;
8137     whiteFlag = blackFlag = FALSE;
8138     userOfferedDraw = FALSE;
8139     hintRequested = bookRequested = FALSE;
8140     first.maybeThinking = FALSE;
8141     second.maybeThinking = FALSE;
8142     first.bookSuspend = FALSE; // [HGM] book
8143     second.bookSuspend = FALSE;
8144     thinkOutput[0] = NULLCHAR;
8145     lastHint[0] = NULLCHAR;
8146     ClearGameInfo(&gameInfo);
8147     gameInfo.variant = StringToVariant(appData.variant);
8148     ics_user_moved = ics_clock_paused = FALSE;
8149     ics_getting_history = H_FALSE;
8150     ics_gamenum = -1;
8151     white_holding[0] = black_holding[0] = NULLCHAR;
8152     ClearProgramStats();
8153     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8154     
8155     ResetFrontEnd();
8156     ClearHighlights();
8157     flipView = appData.flipView;
8158     ClearPremoveHighlights();
8159     gotPremove = FALSE;
8160     alarmSounded = FALSE;
8161
8162     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8163     if(appData.serverMovesName != NULL) {
8164         /* [HGM] prepare to make moves file for broadcasting */
8165         clock_t t = clock();
8166         if(serverMoves != NULL) fclose(serverMoves);
8167         serverMoves = fopen(appData.serverMovesName, "r");
8168         if(serverMoves != NULL) {
8169             fclose(serverMoves);
8170             /* delay 15 sec before overwriting, so all clients can see end */
8171             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8172         }
8173         serverMoves = fopen(appData.serverMovesName, "w");
8174     }
8175
8176     ExitAnalyzeMode();
8177     gameMode = BeginningOfGame;
8178     ModeHighlight();
8179     if(appData.icsActive) gameInfo.variant = VariantNormal;
8180     currentMove = forwardMostMove = backwardMostMove = 0;
8181     InitPosition(redraw);
8182     for (i = 0; i < MAX_MOVES; i++) {
8183         if (commentList[i] != NULL) {
8184             free(commentList[i]);
8185             commentList[i] = NULL;
8186         }
8187     }
8188     ResetClocks();
8189     timeRemaining[0][0] = whiteTimeRemaining;
8190     timeRemaining[1][0] = blackTimeRemaining;
8191     if (first.pr == NULL) {
8192         StartChessProgram(&first);
8193     }
8194     if (init) {
8195             InitChessProgram(&first, startedFromSetupPosition);
8196     }
8197     DisplayTitle("");
8198     DisplayMessage("", "");
8199     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8200     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8201 }
8202
8203 void
8204 AutoPlayGameLoop()
8205 {
8206     for (;;) {
8207         if (!AutoPlayOneMove())
8208           return;
8209         if (matchMode || appData.timeDelay == 0)
8210           continue;
8211         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8212           return;
8213         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8214         break;
8215     }
8216 }
8217
8218
8219 int
8220 AutoPlayOneMove()
8221 {
8222     int fromX, fromY, toX, toY;
8223
8224     if (appData.debugMode) {
8225       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8226     }
8227
8228     if (gameMode != PlayFromGameFile)
8229       return FALSE;
8230
8231     if (currentMove >= forwardMostMove) {
8232       gameMode = EditGame;
8233       ModeHighlight();
8234
8235       /* [AS] Clear current move marker at the end of a game */
8236       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8237
8238       return FALSE;
8239     }
8240     
8241     toX = moveList[currentMove][2] - AAA;
8242     toY = moveList[currentMove][3] - ONE;
8243
8244     if (moveList[currentMove][1] == '@') {
8245         if (appData.highlightLastMove) {
8246             SetHighlights(-1, -1, toX, toY);
8247         }
8248     } else {
8249         fromX = moveList[currentMove][0] - AAA;
8250         fromY = moveList[currentMove][1] - ONE;
8251
8252         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8253
8254         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8255
8256         if (appData.highlightLastMove) {
8257             SetHighlights(fromX, fromY, toX, toY);
8258         }
8259     }
8260     DisplayMove(currentMove);
8261     SendMoveToProgram(currentMove++, &first);
8262     DisplayBothClocks();
8263     DrawPosition(FALSE, boards[currentMove]);
8264     // [HGM] PV info: always display, routine tests if empty
8265     DisplayComment(currentMove - 1, commentList[currentMove]);
8266     return TRUE;
8267 }
8268
8269
8270 int
8271 LoadGameOneMove(readAhead)
8272      ChessMove readAhead;
8273 {
8274     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8275     char promoChar = NULLCHAR;
8276     ChessMove moveType;
8277     char move[MSG_SIZ];
8278     char *p, *q;
8279     
8280     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8281         gameMode != AnalyzeMode && gameMode != Training) {
8282         gameFileFP = NULL;
8283         return FALSE;
8284     }
8285     
8286     yyboardindex = forwardMostMove;
8287     if (readAhead != (ChessMove)0) {
8288       moveType = readAhead;
8289     } else {
8290       if (gameFileFP == NULL)
8291           return FALSE;
8292       moveType = (ChessMove) yylex();
8293     }
8294     
8295     done = FALSE;
8296     switch (moveType) {
8297       case Comment:
8298         if (appData.debugMode) 
8299           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8300         p = yy_text;
8301         if (*p == '{' || *p == '[' || *p == '(') {
8302             p[strlen(p) - 1] = NULLCHAR;
8303             p++;
8304         }
8305
8306         /* append the comment but don't display it */
8307         while (*p == '\n') p++;
8308         AppendComment(currentMove, p);
8309         return TRUE;
8310
8311       case WhiteCapturesEnPassant:
8312       case BlackCapturesEnPassant:
8313       case WhitePromotionChancellor:
8314       case BlackPromotionChancellor:
8315       case WhitePromotionArchbishop:
8316       case BlackPromotionArchbishop:
8317       case WhitePromotionCentaur:
8318       case BlackPromotionCentaur:
8319       case WhitePromotionQueen:
8320       case BlackPromotionQueen:
8321       case WhitePromotionRook:
8322       case BlackPromotionRook:
8323       case WhitePromotionBishop:
8324       case BlackPromotionBishop:
8325       case WhitePromotionKnight:
8326       case BlackPromotionKnight:
8327       case WhitePromotionKing:
8328       case BlackPromotionKing:
8329       case NormalMove:
8330       case WhiteKingSideCastle:
8331       case WhiteQueenSideCastle:
8332       case BlackKingSideCastle:
8333       case BlackQueenSideCastle:
8334       case WhiteKingSideCastleWild:
8335       case WhiteQueenSideCastleWild:
8336       case BlackKingSideCastleWild:
8337       case BlackQueenSideCastleWild:
8338       /* PUSH Fabien */
8339       case WhiteHSideCastleFR:
8340       case WhiteASideCastleFR:
8341       case BlackHSideCastleFR:
8342       case BlackASideCastleFR:
8343       /* POP Fabien */
8344         if (appData.debugMode)
8345           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8346         fromX = currentMoveString[0] - AAA;
8347         fromY = currentMoveString[1] - ONE;
8348         toX = currentMoveString[2] - AAA;
8349         toY = currentMoveString[3] - ONE;
8350         promoChar = currentMoveString[4];
8351         break;
8352
8353       case WhiteDrop:
8354       case BlackDrop:
8355         if (appData.debugMode)
8356           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8357         fromX = moveType == WhiteDrop ?
8358           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8359         (int) CharToPiece(ToLower(currentMoveString[0]));
8360         fromY = DROP_RANK;
8361         toX = currentMoveString[2] - AAA;
8362         toY = currentMoveString[3] - ONE;
8363         break;
8364
8365       case WhiteWins:
8366       case BlackWins:
8367       case GameIsDrawn:
8368       case GameUnfinished:
8369         if (appData.debugMode)
8370           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8371         p = strchr(yy_text, '{');
8372         if (p == NULL) p = strchr(yy_text, '(');
8373         if (p == NULL) {
8374             p = yy_text;
8375             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8376         } else {
8377             q = strchr(p, *p == '{' ? '}' : ')');
8378             if (q != NULL) *q = NULLCHAR;
8379             p++;
8380         }
8381         GameEnds(moveType, p, GE_FILE);
8382         done = TRUE;
8383         if (cmailMsgLoaded) {
8384             ClearHighlights();
8385             flipView = WhiteOnMove(currentMove);
8386             if (moveType == GameUnfinished) flipView = !flipView;
8387             if (appData.debugMode)
8388               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8389         }
8390         break;
8391
8392       case (ChessMove) 0:       /* end of file */
8393         if (appData.debugMode)
8394           fprintf(debugFP, "Parser hit end of file\n");
8395         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8396                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8397           case MT_NONE:
8398           case MT_CHECK:
8399             break;
8400           case MT_CHECKMATE:
8401           case MT_STAINMATE:
8402             if (WhiteOnMove(currentMove)) {
8403                 GameEnds(BlackWins, "Black mates", GE_FILE);
8404             } else {
8405                 GameEnds(WhiteWins, "White mates", GE_FILE);
8406             }
8407             break;
8408           case MT_STALEMATE:
8409             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8410             break;
8411         }
8412         done = TRUE;
8413         break;
8414
8415       case MoveNumberOne:
8416         if (lastLoadGameStart == GNUChessGame) {
8417             /* GNUChessGames have numbers, but they aren't move numbers */
8418             if (appData.debugMode)
8419               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8420                       yy_text, (int) moveType);
8421             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8422         }
8423         /* else fall thru */
8424
8425       case XBoardGame:
8426       case GNUChessGame:
8427       case PGNTag:
8428         /* Reached start of next game in file */
8429         if (appData.debugMode)
8430           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8431         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8432                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8433           case MT_NONE:
8434           case MT_CHECK:
8435             break;
8436           case MT_CHECKMATE:
8437           case MT_STAINMATE:
8438             if (WhiteOnMove(currentMove)) {
8439                 GameEnds(BlackWins, "Black mates", GE_FILE);
8440             } else {
8441                 GameEnds(WhiteWins, "White mates", GE_FILE);
8442             }
8443             break;
8444           case MT_STALEMATE:
8445             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8446             break;
8447         }
8448         done = TRUE;
8449         break;
8450
8451       case PositionDiagram:     /* should not happen; ignore */
8452       case ElapsedTime:         /* ignore */
8453       case NAG:                 /* ignore */
8454         if (appData.debugMode)
8455           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8456                   yy_text, (int) moveType);
8457         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8458
8459       case IllegalMove:
8460         if (appData.testLegality) {
8461             if (appData.debugMode)
8462               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8463             sprintf(move, _("Illegal move: %d.%s%s"),
8464                     (forwardMostMove / 2) + 1,
8465                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8466             DisplayError(move, 0);
8467             done = TRUE;
8468         } else {
8469             if (appData.debugMode)
8470               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8471                       yy_text, currentMoveString);
8472             fromX = currentMoveString[0] - AAA;
8473             fromY = currentMoveString[1] - ONE;
8474             toX = currentMoveString[2] - AAA;
8475             toY = currentMoveString[3] - ONE;
8476             promoChar = currentMoveString[4];
8477         }
8478         break;
8479
8480       case AmbiguousMove:
8481         if (appData.debugMode)
8482           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8483         sprintf(move, _("Ambiguous move: %d.%s%s"),
8484                 (forwardMostMove / 2) + 1,
8485                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8486         DisplayError(move, 0);
8487         done = TRUE;
8488         break;
8489
8490       default:
8491       case ImpossibleMove:
8492         if (appData.debugMode)
8493           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8494         sprintf(move, _("Illegal move: %d.%s%s"),
8495                 (forwardMostMove / 2) + 1,
8496                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8497         DisplayError(move, 0);
8498         done = TRUE;
8499         break;
8500     }
8501
8502     if (done) {
8503         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8504             DrawPosition(FALSE, boards[currentMove]);
8505             DisplayBothClocks();
8506             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8507               DisplayComment(currentMove - 1, commentList[currentMove]);
8508         }
8509         (void) StopLoadGameTimer();
8510         gameFileFP = NULL;
8511         cmailOldMove = forwardMostMove;
8512         return FALSE;
8513     } else {
8514         /* currentMoveString is set as a side-effect of yylex */
8515         strcat(currentMoveString, "\n");
8516         strcpy(moveList[forwardMostMove], currentMoveString);
8517         
8518         thinkOutput[0] = NULLCHAR;
8519         MakeMove(fromX, fromY, toX, toY, promoChar);
8520         currentMove = forwardMostMove;
8521         return TRUE;
8522     }
8523 }
8524
8525 /* Load the nth game from the given file */
8526 int
8527 LoadGameFromFile(filename, n, title, useList)
8528      char *filename;
8529      int n;
8530      char *title;
8531      /*Boolean*/ int useList;
8532 {
8533     FILE *f;
8534     char buf[MSG_SIZ];
8535
8536     if (strcmp(filename, "-") == 0) {
8537         f = stdin;
8538         title = "stdin";
8539     } else {
8540         f = fopen(filename, "rb");
8541         if (f == NULL) {
8542           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8543             DisplayError(buf, errno);
8544             return FALSE;
8545         }
8546     }
8547     if (fseek(f, 0, 0) == -1) {
8548         /* f is not seekable; probably a pipe */
8549         useList = FALSE;
8550     }
8551     if (useList && n == 0) {
8552         int error = GameListBuild(f);
8553         if (error) {
8554             DisplayError(_("Cannot build game list"), error);
8555         } else if (!ListEmpty(&gameList) &&
8556                    ((ListGame *) gameList.tailPred)->number > 1) {
8557             GameListPopUp(f, title);
8558             return TRUE;
8559         }
8560         GameListDestroy();
8561         n = 1;
8562     }
8563     if (n == 0) n = 1;
8564     return LoadGame(f, n, title, FALSE);
8565 }
8566
8567
8568 void
8569 MakeRegisteredMove()
8570 {
8571     int fromX, fromY, toX, toY;
8572     char promoChar;
8573     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8574         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8575           case CMAIL_MOVE:
8576           case CMAIL_DRAW:
8577             if (appData.debugMode)
8578               fprintf(debugFP, "Restoring %s for game %d\n",
8579                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8580     
8581             thinkOutput[0] = NULLCHAR;
8582             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8583             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8584             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8585             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8586             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8587             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8588             MakeMove(fromX, fromY, toX, toY, promoChar);
8589             ShowMove(fromX, fromY, toX, toY);
8590               
8591             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8592                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8593               case MT_NONE:
8594               case MT_CHECK:
8595                 break;
8596                 
8597               case MT_CHECKMATE:
8598               case MT_STAINMATE:
8599                 if (WhiteOnMove(currentMove)) {
8600                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8601                 } else {
8602                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8603                 }
8604                 break;
8605                 
8606               case MT_STALEMATE:
8607                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8608                 break;
8609             }
8610
8611             break;
8612             
8613           case CMAIL_RESIGN:
8614             if (WhiteOnMove(currentMove)) {
8615                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8616             } else {
8617                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8618             }
8619             break;
8620             
8621           case CMAIL_ACCEPT:
8622             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8623             break;
8624               
8625           default:
8626             break;
8627         }
8628     }
8629
8630     return;
8631 }
8632
8633 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8634 int
8635 CmailLoadGame(f, gameNumber, title, useList)
8636      FILE *f;
8637      int gameNumber;
8638      char *title;
8639      int useList;
8640 {
8641     int retVal;
8642
8643     if (gameNumber > nCmailGames) {
8644         DisplayError(_("No more games in this message"), 0);
8645         return FALSE;
8646     }
8647     if (f == lastLoadGameFP) {
8648         int offset = gameNumber - lastLoadGameNumber;
8649         if (offset == 0) {
8650             cmailMsg[0] = NULLCHAR;
8651             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8652                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8653                 nCmailMovesRegistered--;
8654             }
8655             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8656             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8657                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8658             }
8659         } else {
8660             if (! RegisterMove()) return FALSE;
8661         }
8662     }
8663
8664     retVal = LoadGame(f, gameNumber, title, useList);
8665
8666     /* Make move registered during previous look at this game, if any */
8667     MakeRegisteredMove();
8668
8669     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8670         commentList[currentMove]
8671           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8672         DisplayComment(currentMove - 1, commentList[currentMove]);
8673     }
8674
8675     return retVal;
8676 }
8677
8678 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8679 int
8680 ReloadGame(offset)
8681      int offset;
8682 {
8683     int gameNumber = lastLoadGameNumber + offset;
8684     if (lastLoadGameFP == NULL) {
8685         DisplayError(_("No game has been loaded yet"), 0);
8686         return FALSE;
8687     }
8688     if (gameNumber <= 0) {
8689         DisplayError(_("Can't back up any further"), 0);
8690         return FALSE;
8691     }
8692     if (cmailMsgLoaded) {
8693         return CmailLoadGame(lastLoadGameFP, gameNumber,
8694                              lastLoadGameTitle, lastLoadGameUseList);
8695     } else {
8696         return LoadGame(lastLoadGameFP, gameNumber,
8697                         lastLoadGameTitle, lastLoadGameUseList);
8698     }
8699 }
8700
8701
8702
8703 /* Load the nth game from open file f */
8704 int
8705 LoadGame(f, gameNumber, title, useList)
8706      FILE *f;
8707      int gameNumber;
8708      char *title;
8709      int useList;
8710 {
8711     ChessMove cm;
8712     char buf[MSG_SIZ];
8713     int gn = gameNumber;
8714     ListGame *lg = NULL;
8715     int numPGNTags = 0;
8716     int err;
8717     GameMode oldGameMode;
8718     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8719
8720     if (appData.debugMode) 
8721         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8722
8723     if (gameMode == Training )
8724         SetTrainingModeOff();
8725
8726     oldGameMode = gameMode;
8727     if (gameMode != BeginningOfGame) {
8728       Reset(FALSE, TRUE);
8729     }
8730
8731     gameFileFP = f;
8732     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8733         fclose(lastLoadGameFP);
8734     }
8735
8736     if (useList) {
8737         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8738         
8739         if (lg) {
8740             fseek(f, lg->offset, 0);
8741             GameListHighlight(gameNumber);
8742             gn = 1;
8743         }
8744         else {
8745             DisplayError(_("Game number out of range"), 0);
8746             return FALSE;
8747         }
8748     } else {
8749         GameListDestroy();
8750         if (fseek(f, 0, 0) == -1) {
8751             if (f == lastLoadGameFP ?
8752                 gameNumber == lastLoadGameNumber + 1 :
8753                 gameNumber == 1) {
8754                 gn = 1;
8755             } else {
8756                 DisplayError(_("Can't seek on game file"), 0);
8757                 return FALSE;
8758             }
8759         }
8760     }
8761     lastLoadGameFP = f;
8762     lastLoadGameNumber = gameNumber;
8763     strcpy(lastLoadGameTitle, title);
8764     lastLoadGameUseList = useList;
8765
8766     yynewfile(f);
8767
8768     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8769       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8770                 lg->gameInfo.black);
8771             DisplayTitle(buf);
8772     } else if (*title != NULLCHAR) {
8773         if (gameNumber > 1) {
8774             sprintf(buf, "%s %d", title, gameNumber);
8775             DisplayTitle(buf);
8776         } else {
8777             DisplayTitle(title);
8778         }
8779     }
8780
8781     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8782         gameMode = PlayFromGameFile;
8783         ModeHighlight();
8784     }
8785
8786     currentMove = forwardMostMove = backwardMostMove = 0;
8787     CopyBoard(boards[0], initialPosition);
8788     StopClocks();
8789
8790     /*
8791      * Skip the first gn-1 games in the file.
8792      * Also skip over anything that precedes an identifiable 
8793      * start of game marker, to avoid being confused by 
8794      * garbage at the start of the file.  Currently 
8795      * recognized start of game markers are the move number "1",
8796      * the pattern "gnuchess .* game", the pattern
8797      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8798      * A game that starts with one of the latter two patterns
8799      * will also have a move number 1, possibly
8800      * following a position diagram.
8801      * 5-4-02: Let's try being more lenient and allowing a game to
8802      * start with an unnumbered move.  Does that break anything?
8803      */
8804     cm = lastLoadGameStart = (ChessMove) 0;
8805     while (gn > 0) {
8806         yyboardindex = forwardMostMove;
8807         cm = (ChessMove) yylex();
8808         switch (cm) {
8809           case (ChessMove) 0:
8810             if (cmailMsgLoaded) {
8811                 nCmailGames = CMAIL_MAX_GAMES - gn;
8812             } else {
8813                 Reset(TRUE, TRUE);
8814                 DisplayError(_("Game not found in file"), 0);
8815             }
8816             return FALSE;
8817
8818           case GNUChessGame:
8819           case XBoardGame:
8820             gn--;
8821             lastLoadGameStart = cm;
8822             break;
8823             
8824           case MoveNumberOne:
8825             switch (lastLoadGameStart) {
8826               case GNUChessGame:
8827               case XBoardGame:
8828               case PGNTag:
8829                 break;
8830               case MoveNumberOne:
8831               case (ChessMove) 0:
8832                 gn--;           /* count this game */
8833                 lastLoadGameStart = cm;
8834                 break;
8835               default:
8836                 /* impossible */
8837                 break;
8838             }
8839             break;
8840
8841           case PGNTag:
8842             switch (lastLoadGameStart) {
8843               case GNUChessGame:
8844               case PGNTag:
8845               case MoveNumberOne:
8846               case (ChessMove) 0:
8847                 gn--;           /* count this game */
8848                 lastLoadGameStart = cm;
8849                 break;
8850               case XBoardGame:
8851                 lastLoadGameStart = cm; /* game counted already */
8852                 break;
8853               default:
8854                 /* impossible */
8855                 break;
8856             }
8857             if (gn > 0) {
8858                 do {
8859                     yyboardindex = forwardMostMove;
8860                     cm = (ChessMove) yylex();
8861                 } while (cm == PGNTag || cm == Comment);
8862             }
8863             break;
8864
8865           case WhiteWins:
8866           case BlackWins:
8867           case GameIsDrawn:
8868             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8869                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8870                     != CMAIL_OLD_RESULT) {
8871                     nCmailResults ++ ;
8872                     cmailResult[  CMAIL_MAX_GAMES
8873                                 - gn - 1] = CMAIL_OLD_RESULT;
8874                 }
8875             }
8876             break;
8877
8878           case NormalMove:
8879             /* Only a NormalMove can be at the start of a game
8880              * without a position diagram. */
8881             if (lastLoadGameStart == (ChessMove) 0) {
8882               gn--;
8883               lastLoadGameStart = MoveNumberOne;
8884             }
8885             break;
8886
8887           default:
8888             break;
8889         }
8890     }
8891     
8892     if (appData.debugMode)
8893       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8894
8895     if (cm == XBoardGame) {
8896         /* Skip any header junk before position diagram and/or move 1 */
8897         for (;;) {
8898             yyboardindex = forwardMostMove;
8899             cm = (ChessMove) yylex();
8900
8901             if (cm == (ChessMove) 0 ||
8902                 cm == GNUChessGame || cm == XBoardGame) {
8903                 /* Empty game; pretend end-of-file and handle later */
8904                 cm = (ChessMove) 0;
8905                 break;
8906             }
8907
8908             if (cm == MoveNumberOne || cm == PositionDiagram ||
8909                 cm == PGNTag || cm == Comment)
8910               break;
8911         }
8912     } else if (cm == GNUChessGame) {
8913         if (gameInfo.event != NULL) {
8914             free(gameInfo.event);
8915         }
8916         gameInfo.event = StrSave(yy_text);
8917     }   
8918
8919     startedFromSetupPosition = FALSE;
8920     while (cm == PGNTag) {
8921         if (appData.debugMode) 
8922           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8923         err = ParsePGNTag(yy_text, &gameInfo);
8924         if (!err) numPGNTags++;
8925
8926         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8927         if(gameInfo.variant != oldVariant) {
8928             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8929             InitPosition(TRUE);
8930             oldVariant = gameInfo.variant;
8931             if (appData.debugMode) 
8932               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8933         }
8934
8935
8936         if (gameInfo.fen != NULL) {
8937           Board initial_position;
8938           startedFromSetupPosition = TRUE;
8939           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8940             Reset(TRUE, TRUE);
8941             DisplayError(_("Bad FEN position in file"), 0);
8942             return FALSE;
8943           }
8944           CopyBoard(boards[0], initial_position);
8945           if (blackPlaysFirst) {
8946             currentMove = forwardMostMove = backwardMostMove = 1;
8947             CopyBoard(boards[1], initial_position);
8948             strcpy(moveList[0], "");
8949             strcpy(parseList[0], "");
8950             timeRemaining[0][1] = whiteTimeRemaining;
8951             timeRemaining[1][1] = blackTimeRemaining;
8952             if (commentList[0] != NULL) {
8953               commentList[1] = commentList[0];
8954               commentList[0] = NULL;
8955             }
8956           } else {
8957             currentMove = forwardMostMove = backwardMostMove = 0;
8958           }
8959           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8960           {   int i;
8961               initialRulePlies = FENrulePlies;
8962               epStatus[forwardMostMove] = FENepStatus;
8963               for( i=0; i< nrCastlingRights; i++ )
8964                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8965           }
8966           yyboardindex = forwardMostMove;
8967           free(gameInfo.fen);
8968           gameInfo.fen = NULL;
8969         }
8970
8971         yyboardindex = forwardMostMove;
8972         cm = (ChessMove) yylex();
8973
8974         /* Handle comments interspersed among the tags */
8975         while (cm == Comment) {
8976             char *p;
8977             if (appData.debugMode) 
8978               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8979             p = yy_text;
8980             if (*p == '{' || *p == '[' || *p == '(') {
8981                 p[strlen(p) - 1] = NULLCHAR;
8982                 p++;
8983             }
8984             while (*p == '\n') p++;
8985             AppendComment(currentMove, p);
8986             yyboardindex = forwardMostMove;
8987             cm = (ChessMove) yylex();
8988         }
8989     }
8990
8991     /* don't rely on existence of Event tag since if game was
8992      * pasted from clipboard the Event tag may not exist
8993      */
8994     if (numPGNTags > 0){
8995         char *tags;
8996         if (gameInfo.variant == VariantNormal) {
8997           gameInfo.variant = StringToVariant(gameInfo.event);
8998         }
8999         if (!matchMode) {
9000           if( appData.autoDisplayTags ) {
9001             tags = PGNTags(&gameInfo);
9002             TagsPopUp(tags, CmailMsg());
9003             free(tags);
9004           }
9005         }
9006     } else {
9007         /* Make something up, but don't display it now */
9008         SetGameInfo();
9009         TagsPopDown();
9010     }
9011
9012     if (cm == PositionDiagram) {
9013         int i, j;
9014         char *p;
9015         Board initial_position;
9016
9017         if (appData.debugMode)
9018           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9019
9020         if (!startedFromSetupPosition) {
9021             p = yy_text;
9022             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9023               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9024                 switch (*p) {
9025                   case '[':
9026                   case '-':
9027                   case ' ':
9028                   case '\t':
9029                   case '\n':
9030                   case '\r':
9031                     break;
9032                   default:
9033                     initial_position[i][j++] = CharToPiece(*p);
9034                     break;
9035                 }
9036             while (*p == ' ' || *p == '\t' ||
9037                    *p == '\n' || *p == '\r') p++;
9038         
9039             if (strncmp(p, "black", strlen("black"))==0)
9040               blackPlaysFirst = TRUE;
9041             else
9042               blackPlaysFirst = FALSE;
9043             startedFromSetupPosition = TRUE;
9044         
9045             CopyBoard(boards[0], initial_position);
9046             if (blackPlaysFirst) {
9047                 currentMove = forwardMostMove = backwardMostMove = 1;
9048                 CopyBoard(boards[1], initial_position);
9049                 strcpy(moveList[0], "");
9050                 strcpy(parseList[0], "");
9051                 timeRemaining[0][1] = whiteTimeRemaining;
9052                 timeRemaining[1][1] = blackTimeRemaining;
9053                 if (commentList[0] != NULL) {
9054                     commentList[1] = commentList[0];
9055                     commentList[0] = NULL;
9056                 }
9057             } else {
9058                 currentMove = forwardMostMove = backwardMostMove = 0;
9059             }
9060         }
9061         yyboardindex = forwardMostMove;
9062         cm = (ChessMove) yylex();
9063     }
9064
9065     if (first.pr == NoProc) {
9066         StartChessProgram(&first);
9067     }
9068     InitChessProgram(&first, FALSE);
9069     SendToProgram("force\n", &first);
9070     if (startedFromSetupPosition) {
9071         SendBoard(&first, forwardMostMove);
9072     if (appData.debugMode) {
9073         fprintf(debugFP, "Load Game\n");
9074     }
9075         DisplayBothClocks();
9076     }      
9077
9078     /* [HGM] server: flag to write setup moves in broadcast file as one */
9079     loadFlag = appData.suppressLoadMoves;
9080
9081     while (cm == Comment) {
9082         char *p;
9083         if (appData.debugMode) 
9084           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9085         p = yy_text;
9086         if (*p == '{' || *p == '[' || *p == '(') {
9087             p[strlen(p) - 1] = NULLCHAR;
9088             p++;
9089         }
9090         while (*p == '\n') p++;
9091         AppendComment(currentMove, p);
9092         yyboardindex = forwardMostMove;
9093         cm = (ChessMove) yylex();
9094     }
9095
9096     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9097         cm == WhiteWins || cm == BlackWins ||
9098         cm == GameIsDrawn || cm == GameUnfinished) {
9099         DisplayMessage("", _("No moves in game"));
9100         if (cmailMsgLoaded) {
9101             if (appData.debugMode)
9102               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9103             ClearHighlights();
9104             flipView = FALSE;
9105         }
9106         DrawPosition(FALSE, boards[currentMove]);
9107         DisplayBothClocks();
9108         gameMode = EditGame;
9109         ModeHighlight();
9110         gameFileFP = NULL;
9111         cmailOldMove = 0;
9112         return TRUE;
9113     }
9114
9115     // [HGM] PV info: routine tests if comment empty
9116     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9117         DisplayComment(currentMove - 1, commentList[currentMove]);
9118     }
9119     if (!matchMode && appData.timeDelay != 0) 
9120       DrawPosition(FALSE, boards[currentMove]);
9121
9122     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9123       programStats.ok_to_send = 1;
9124     }
9125
9126     /* if the first token after the PGN tags is a move
9127      * and not move number 1, retrieve it from the parser 
9128      */
9129     if (cm != MoveNumberOne)
9130         LoadGameOneMove(cm);
9131
9132     /* load the remaining moves from the file */
9133     while (LoadGameOneMove((ChessMove)0)) {
9134       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9135       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9136     }
9137
9138     /* rewind to the start of the game */
9139     currentMove = backwardMostMove;
9140
9141     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9142
9143     if (oldGameMode == AnalyzeFile ||
9144         oldGameMode == AnalyzeMode) {
9145       AnalyzeFileEvent();
9146     }
9147
9148     if (matchMode || appData.timeDelay == 0) {
9149       ToEndEvent();
9150       gameMode = EditGame;
9151       ModeHighlight();
9152     } else if (appData.timeDelay > 0) {
9153       AutoPlayGameLoop();
9154     }
9155
9156     if (appData.debugMode) 
9157         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9158
9159     loadFlag = 0; /* [HGM] true game starts */
9160     return TRUE;
9161 }
9162
9163 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9164 int
9165 ReloadPosition(offset)
9166      int offset;
9167 {
9168     int positionNumber = lastLoadPositionNumber + offset;
9169     if (lastLoadPositionFP == NULL) {
9170         DisplayError(_("No position has been loaded yet"), 0);
9171         return FALSE;
9172     }
9173     if (positionNumber <= 0) {
9174         DisplayError(_("Can't back up any further"), 0);
9175         return FALSE;
9176     }
9177     return LoadPosition(lastLoadPositionFP, positionNumber,
9178                         lastLoadPositionTitle);
9179 }
9180
9181 /* Load the nth position from the given file */
9182 int
9183 LoadPositionFromFile(filename, n, title)
9184      char *filename;
9185      int n;
9186      char *title;
9187 {
9188     FILE *f;
9189     char buf[MSG_SIZ];
9190
9191     if (strcmp(filename, "-") == 0) {
9192         return LoadPosition(stdin, n, "stdin");
9193     } else {
9194         f = fopen(filename, "rb");
9195         if (f == NULL) {
9196             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9197             DisplayError(buf, errno);
9198             return FALSE;
9199         } else {
9200             return LoadPosition(f, n, title);
9201         }
9202     }
9203 }
9204
9205 /* Load the nth position from the given open file, and close it */
9206 int
9207 LoadPosition(f, positionNumber, title)
9208      FILE *f;
9209      int positionNumber;
9210      char *title;
9211 {
9212     char *p, line[MSG_SIZ];
9213     Board initial_position;
9214     int i, j, fenMode, pn;
9215     
9216     if (gameMode == Training )
9217         SetTrainingModeOff();
9218
9219     if (gameMode != BeginningOfGame) {
9220         Reset(FALSE, TRUE);
9221     }
9222     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9223         fclose(lastLoadPositionFP);
9224     }
9225     if (positionNumber == 0) positionNumber = 1;
9226     lastLoadPositionFP = f;
9227     lastLoadPositionNumber = positionNumber;
9228     strcpy(lastLoadPositionTitle, title);
9229     if (first.pr == NoProc) {
9230       StartChessProgram(&first);
9231       InitChessProgram(&first, FALSE);
9232     }    
9233     pn = positionNumber;
9234     if (positionNumber < 0) {
9235         /* Negative position number means to seek to that byte offset */
9236         if (fseek(f, -positionNumber, 0) == -1) {
9237             DisplayError(_("Can't seek on position file"), 0);
9238             return FALSE;
9239         };
9240         pn = 1;
9241     } else {
9242         if (fseek(f, 0, 0) == -1) {
9243             if (f == lastLoadPositionFP ?
9244                 positionNumber == lastLoadPositionNumber + 1 :
9245                 positionNumber == 1) {
9246                 pn = 1;
9247             } else {
9248                 DisplayError(_("Can't seek on position file"), 0);
9249                 return FALSE;
9250             }
9251         }
9252     }
9253     /* See if this file is FEN or old-style xboard */
9254     if (fgets(line, MSG_SIZ, f) == NULL) {
9255         DisplayError(_("Position not found in file"), 0);
9256         return FALSE;
9257     }
9258     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9259     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9260
9261     if (pn >= 2) {
9262         if (fenMode || line[0] == '#') pn--;
9263         while (pn > 0) {
9264             /* skip positions before number pn */
9265             if (fgets(line, MSG_SIZ, f) == NULL) {
9266                 Reset(TRUE, TRUE);
9267                 DisplayError(_("Position not found in file"), 0);
9268                 return FALSE;
9269             }
9270             if (fenMode || line[0] == '#') pn--;
9271         }
9272     }
9273
9274     if (fenMode) {
9275         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9276             DisplayError(_("Bad FEN position in file"), 0);
9277             return FALSE;
9278         }
9279     } else {
9280         (void) fgets(line, MSG_SIZ, f);
9281         (void) fgets(line, MSG_SIZ, f);
9282     
9283         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9284             (void) fgets(line, MSG_SIZ, f);
9285             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9286                 if (*p == ' ')
9287                   continue;
9288                 initial_position[i][j++] = CharToPiece(*p);
9289             }
9290         }
9291     
9292         blackPlaysFirst = FALSE;
9293         if (!feof(f)) {
9294             (void) fgets(line, MSG_SIZ, f);
9295             if (strncmp(line, "black", strlen("black"))==0)
9296               blackPlaysFirst = TRUE;
9297         }
9298     }
9299     startedFromSetupPosition = TRUE;
9300     
9301     SendToProgram("force\n", &first);
9302     CopyBoard(boards[0], initial_position);
9303     if (blackPlaysFirst) {
9304         currentMove = forwardMostMove = backwardMostMove = 1;
9305         strcpy(moveList[0], "");
9306         strcpy(parseList[0], "");
9307         CopyBoard(boards[1], initial_position);
9308         DisplayMessage("", _("Black to play"));
9309     } else {
9310         currentMove = forwardMostMove = backwardMostMove = 0;
9311         DisplayMessage("", _("White to play"));
9312     }
9313           /* [HGM] copy FEN attributes as well */
9314           {   int i;
9315               initialRulePlies = FENrulePlies;
9316               epStatus[forwardMostMove] = FENepStatus;
9317               for( i=0; i< nrCastlingRights; i++ )
9318                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9319           }
9320     SendBoard(&first, forwardMostMove);
9321     if (appData.debugMode) {
9322 int i, j;
9323   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9324   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9325         fprintf(debugFP, "Load Position\n");
9326     }
9327
9328     if (positionNumber > 1) {
9329         sprintf(line, "%s %d", title, positionNumber);
9330         DisplayTitle(line);
9331     } else {
9332         DisplayTitle(title);
9333     }
9334     gameMode = EditGame;
9335     ModeHighlight();
9336     ResetClocks();
9337     timeRemaining[0][1] = whiteTimeRemaining;
9338     timeRemaining[1][1] = blackTimeRemaining;
9339     DrawPosition(FALSE, boards[currentMove]);
9340    
9341     return TRUE;
9342 }
9343
9344
9345 void
9346 CopyPlayerNameIntoFileName(dest, src)
9347      char **dest, *src;
9348 {
9349     while (*src != NULLCHAR && *src != ',') {
9350         if (*src == ' ') {
9351             *(*dest)++ = '_';
9352             src++;
9353         } else {
9354             *(*dest)++ = *src++;
9355         }
9356     }
9357 }
9358
9359 char *DefaultFileName(ext)
9360      char *ext;
9361 {
9362     static char def[MSG_SIZ];
9363     char *p;
9364
9365     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9366         p = def;
9367         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9368         *p++ = '-';
9369         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9370         *p++ = '.';
9371         strcpy(p, ext);
9372     } else {
9373         def[0] = NULLCHAR;
9374     }
9375     return def;
9376 }
9377
9378 /* Save the current game to the given file */
9379 int
9380 SaveGameToFile(filename, append)
9381      char *filename;
9382      int append;
9383 {
9384     FILE *f;
9385     char buf[MSG_SIZ];
9386
9387     if (strcmp(filename, "-") == 0) {
9388         return SaveGame(stdout, 0, NULL);
9389     } else {
9390         f = fopen(filename, append ? "a" : "w");
9391         if (f == NULL) {
9392             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9393             DisplayError(buf, errno);
9394             return FALSE;
9395         } else {
9396             return SaveGame(f, 0, NULL);
9397         }
9398     }
9399 }
9400
9401 char *
9402 SavePart(str)
9403      char *str;
9404 {
9405     static char buf[MSG_SIZ];
9406     char *p;
9407     
9408     p = strchr(str, ' ');
9409     if (p == NULL) return str;
9410     strncpy(buf, str, p - str);
9411     buf[p - str] = NULLCHAR;
9412     return buf;
9413 }
9414
9415 #define PGN_MAX_LINE 75
9416
9417 #define PGN_SIDE_WHITE  0
9418 #define PGN_SIDE_BLACK  1
9419
9420 /* [AS] */
9421 static int FindFirstMoveOutOfBook( int side )
9422 {
9423     int result = -1;
9424
9425     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9426         int index = backwardMostMove;
9427         int has_book_hit = 0;
9428
9429         if( (index % 2) != side ) {
9430             index++;
9431         }
9432
9433         while( index < forwardMostMove ) {
9434             /* Check to see if engine is in book */
9435             int depth = pvInfoList[index].depth;
9436             int score = pvInfoList[index].score;
9437             int in_book = 0;
9438
9439             if( depth <= 2 ) {
9440                 in_book = 1;
9441             }
9442             else if( score == 0 && depth == 63 ) {
9443                 in_book = 1; /* Zappa */
9444             }
9445             else if( score == 2 && depth == 99 ) {
9446                 in_book = 1; /* Abrok */
9447             }
9448
9449             has_book_hit += in_book;
9450
9451             if( ! in_book ) {
9452                 result = index;
9453
9454                 break;
9455             }
9456
9457             index += 2;
9458         }
9459     }
9460
9461     return result;
9462 }
9463
9464 /* [AS] */
9465 void GetOutOfBookInfo( char * buf )
9466 {
9467     int oob[2];
9468     int i;
9469     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9470
9471     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9472     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9473
9474     *buf = '\0';
9475
9476     if( oob[0] >= 0 || oob[1] >= 0 ) {
9477         for( i=0; i<2; i++ ) {
9478             int idx = oob[i];
9479
9480             if( idx >= 0 ) {
9481                 if( i > 0 && oob[0] >= 0 ) {
9482                     strcat( buf, "   " );
9483                 }
9484
9485                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9486                 sprintf( buf+strlen(buf), "%s%.2f", 
9487                     pvInfoList[idx].score >= 0 ? "+" : "",
9488                     pvInfoList[idx].score / 100.0 );
9489             }
9490         }
9491     }
9492 }
9493
9494 /* Save game in PGN style and close the file */
9495 int
9496 SaveGamePGN(f)
9497      FILE *f;
9498 {
9499     int i, offset, linelen, newblock;
9500     time_t tm;
9501 //    char *movetext;
9502     char numtext[32];
9503     int movelen, numlen, blank;
9504     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9505
9506     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9507     
9508     tm = time((time_t *) NULL);
9509     
9510     PrintPGNTags(f, &gameInfo);
9511     
9512     if (backwardMostMove > 0 || startedFromSetupPosition) {
9513         char *fen = PositionToFEN(backwardMostMove, NULL);
9514         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9515         fprintf(f, "\n{--------------\n");
9516         PrintPosition(f, backwardMostMove);
9517         fprintf(f, "--------------}\n");
9518         free(fen);
9519     }
9520     else {
9521         /* [AS] Out of book annotation */
9522         if( appData.saveOutOfBookInfo ) {
9523             char buf[64];
9524
9525             GetOutOfBookInfo( buf );
9526
9527             if( buf[0] != '\0' ) {
9528                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9529             }
9530         }
9531
9532         fprintf(f, "\n");
9533     }
9534
9535     i = backwardMostMove;
9536     linelen = 0;
9537     newblock = TRUE;
9538
9539     while (i < forwardMostMove) {
9540         /* Print comments preceding this move */
9541         if (commentList[i] != NULL) {
9542             if (linelen > 0) fprintf(f, "\n");
9543             fprintf(f, "{\n%s}\n", commentList[i]);
9544             linelen = 0;
9545             newblock = TRUE;
9546         }
9547
9548         /* Format move number */
9549         if ((i % 2) == 0) {
9550             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9551         } else {
9552             if (newblock) {
9553                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9554             } else {
9555                 numtext[0] = NULLCHAR;
9556             }
9557         }
9558         numlen = strlen(numtext);
9559         newblock = FALSE;
9560
9561         /* Print move number */
9562         blank = linelen > 0 && numlen > 0;
9563         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9564             fprintf(f, "\n");
9565             linelen = 0;
9566             blank = 0;
9567         }
9568         if (blank) {
9569             fprintf(f, " ");
9570             linelen++;
9571         }
9572         fprintf(f, numtext);
9573         linelen += numlen;
9574
9575         /* Get move */
9576         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9577         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9578
9579         /* Print move */
9580         blank = linelen > 0 && movelen > 0;
9581         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9582             fprintf(f, "\n");
9583             linelen = 0;
9584             blank = 0;
9585         }
9586         if (blank) {
9587             fprintf(f, " ");
9588             linelen++;
9589         }
9590         fprintf(f, move_buffer);
9591         linelen += movelen;
9592
9593         /* [AS] Add PV info if present */
9594         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9595             /* [HGM] add time */
9596             char buf[MSG_SIZ]; int seconds = 0;
9597
9598             if(i >= backwardMostMove) {
9599                 if(WhiteOnMove(i))
9600                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9601                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9602                 else
9603                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9604                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9605             }
9606             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9607
9608             if( seconds <= 0) buf[0] = 0; else
9609             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9610                 seconds = (seconds + 4)/10; // round to full seconds
9611                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9612                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9613             }
9614
9615             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9616                 pvInfoList[i].score >= 0 ? "+" : "",
9617                 pvInfoList[i].score / 100.0,
9618                 pvInfoList[i].depth,
9619                 buf );
9620
9621             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9622
9623             /* Print score/depth */
9624             blank = linelen > 0 && movelen > 0;
9625             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9626                 fprintf(f, "\n");
9627                 linelen = 0;
9628                 blank = 0;
9629             }
9630             if (blank) {
9631                 fprintf(f, " ");
9632                 linelen++;
9633             }
9634             fprintf(f, move_buffer);
9635             linelen += movelen;
9636         }
9637
9638         i++;
9639     }
9640     
9641     /* Start a new line */
9642     if (linelen > 0) fprintf(f, "\n");
9643
9644     /* Print comments after last move */
9645     if (commentList[i] != NULL) {
9646         fprintf(f, "{\n%s}\n", commentList[i]);
9647     }
9648
9649     /* Print result */
9650     if (gameInfo.resultDetails != NULL &&
9651         gameInfo.resultDetails[0] != NULLCHAR) {
9652         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9653                 PGNResult(gameInfo.result));
9654     } else {
9655         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9656     }
9657
9658     fclose(f);
9659     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9660     return TRUE;
9661 }
9662
9663 /* Save game in old style and close the file */
9664 int
9665 SaveGameOldStyle(f)
9666      FILE *f;
9667 {
9668     int i, offset;
9669     time_t tm;
9670     
9671     tm = time((time_t *) NULL);
9672     
9673     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9674     PrintOpponents(f);
9675     
9676     if (backwardMostMove > 0 || startedFromSetupPosition) {
9677         fprintf(f, "\n[--------------\n");
9678         PrintPosition(f, backwardMostMove);
9679         fprintf(f, "--------------]\n");
9680     } else {
9681         fprintf(f, "\n");
9682     }
9683
9684     i = backwardMostMove;
9685     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9686
9687     while (i < forwardMostMove) {
9688         if (commentList[i] != NULL) {
9689             fprintf(f, "[%s]\n", commentList[i]);
9690         }
9691
9692         if ((i % 2) == 1) {
9693             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9694             i++;
9695         } else {
9696             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9697             i++;
9698             if (commentList[i] != NULL) {
9699                 fprintf(f, "\n");
9700                 continue;
9701             }
9702             if (i >= forwardMostMove) {
9703                 fprintf(f, "\n");
9704                 break;
9705             }
9706             fprintf(f, "%s\n", parseList[i]);
9707             i++;
9708         }
9709     }
9710     
9711     if (commentList[i] != NULL) {
9712         fprintf(f, "[%s]\n", commentList[i]);
9713     }
9714
9715     /* This isn't really the old style, but it's close enough */
9716     if (gameInfo.resultDetails != NULL &&
9717         gameInfo.resultDetails[0] != NULLCHAR) {
9718         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9719                 gameInfo.resultDetails);
9720     } else {
9721         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9722     }
9723
9724     fclose(f);
9725     return TRUE;
9726 }
9727
9728 /* Save the current game to open file f and close the file */
9729 int
9730 SaveGame(f, dummy, dummy2)
9731      FILE *f;
9732      int dummy;
9733      char *dummy2;
9734 {
9735     if (gameMode == EditPosition) EditPositionDone();
9736     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9737     if (appData.oldSaveStyle)
9738       return SaveGameOldStyle(f);
9739     else
9740       return SaveGamePGN(f);
9741 }
9742
9743 /* Save the current position to the given file */
9744 int
9745 SavePositionToFile(filename)
9746      char *filename;
9747 {
9748     FILE *f;
9749     char buf[MSG_SIZ];
9750
9751     if (strcmp(filename, "-") == 0) {
9752         return SavePosition(stdout, 0, NULL);
9753     } else {
9754         f = fopen(filename, "a");
9755         if (f == NULL) {
9756             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9757             DisplayError(buf, errno);
9758             return FALSE;
9759         } else {
9760             SavePosition(f, 0, NULL);
9761             return TRUE;
9762         }
9763     }
9764 }
9765
9766 /* Save the current position to the given open file and close the file */
9767 int
9768 SavePosition(f, dummy, dummy2)
9769      FILE *f;
9770      int dummy;
9771      char *dummy2;
9772 {
9773     time_t tm;
9774     char *fen;
9775     
9776     if (appData.oldSaveStyle) {
9777         tm = time((time_t *) NULL);
9778     
9779         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9780         PrintOpponents(f);
9781         fprintf(f, "[--------------\n");
9782         PrintPosition(f, currentMove);
9783         fprintf(f, "--------------]\n");
9784     } else {
9785         fen = PositionToFEN(currentMove, NULL);
9786         fprintf(f, "%s\n", fen);
9787         free(fen);
9788     }
9789     fclose(f);
9790     return TRUE;
9791 }
9792
9793 void
9794 ReloadCmailMsgEvent(unregister)
9795      int unregister;
9796 {
9797 #if !WIN32
9798     static char *inFilename = NULL;
9799     static char *outFilename;
9800     int i;
9801     struct stat inbuf, outbuf;
9802     int status;
9803     
9804     /* Any registered moves are unregistered if unregister is set, */
9805     /* i.e. invoked by the signal handler */
9806     if (unregister) {
9807         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9808             cmailMoveRegistered[i] = FALSE;
9809             if (cmailCommentList[i] != NULL) {
9810                 free(cmailCommentList[i]);
9811                 cmailCommentList[i] = NULL;
9812             }
9813         }
9814         nCmailMovesRegistered = 0;
9815     }
9816
9817     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9818         cmailResult[i] = CMAIL_NOT_RESULT;
9819     }
9820     nCmailResults = 0;
9821
9822     if (inFilename == NULL) {
9823         /* Because the filenames are static they only get malloced once  */
9824         /* and they never get freed                                      */
9825         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9826         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9827
9828         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9829         sprintf(outFilename, "%s.out", appData.cmailGameName);
9830     }
9831     
9832     status = stat(outFilename, &outbuf);
9833     if (status < 0) {
9834         cmailMailedMove = FALSE;
9835     } else {
9836         status = stat(inFilename, &inbuf);
9837         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9838     }
9839     
9840     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9841        counts the games, notes how each one terminated, etc.
9842        
9843        It would be nice to remove this kludge and instead gather all
9844        the information while building the game list.  (And to keep it
9845        in the game list nodes instead of having a bunch of fixed-size
9846        parallel arrays.)  Note this will require getting each game's
9847        termination from the PGN tags, as the game list builder does
9848        not process the game moves.  --mann
9849        */
9850     cmailMsgLoaded = TRUE;
9851     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9852     
9853     /* Load first game in the file or popup game menu */
9854     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9855
9856 #endif /* !WIN32 */
9857     return;
9858 }
9859
9860 int
9861 RegisterMove()
9862 {
9863     FILE *f;
9864     char string[MSG_SIZ];
9865
9866     if (   cmailMailedMove
9867         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9868         return TRUE;            /* Allow free viewing  */
9869     }
9870
9871     /* Unregister move to ensure that we don't leave RegisterMove        */
9872     /* with the move registered when the conditions for registering no   */
9873     /* longer hold                                                       */
9874     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9875         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9876         nCmailMovesRegistered --;
9877
9878         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9879           {
9880               free(cmailCommentList[lastLoadGameNumber - 1]);
9881               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9882           }
9883     }
9884
9885     if (cmailOldMove == -1) {
9886         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9887         return FALSE;
9888     }
9889
9890     if (currentMove > cmailOldMove + 1) {
9891         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9892         return FALSE;
9893     }
9894
9895     if (currentMove < cmailOldMove) {
9896         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9897         return FALSE;
9898     }
9899
9900     if (forwardMostMove > currentMove) {
9901         /* Silently truncate extra moves */
9902         TruncateGame();
9903     }
9904
9905     if (   (currentMove == cmailOldMove + 1)
9906         || (   (currentMove == cmailOldMove)
9907             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9908                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9909         if (gameInfo.result != GameUnfinished) {
9910             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9911         }
9912
9913         if (commentList[currentMove] != NULL) {
9914             cmailCommentList[lastLoadGameNumber - 1]
9915               = StrSave(commentList[currentMove]);
9916         }
9917         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9918
9919         if (appData.debugMode)
9920           fprintf(debugFP, "Saving %s for game %d\n",
9921                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9922
9923         sprintf(string,
9924                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9925         
9926         f = fopen(string, "w");
9927         if (appData.oldSaveStyle) {
9928             SaveGameOldStyle(f); /* also closes the file */
9929             
9930             sprintf(string, "%s.pos.out", appData.cmailGameName);
9931             f = fopen(string, "w");
9932             SavePosition(f, 0, NULL); /* also closes the file */
9933         } else {
9934             fprintf(f, "{--------------\n");
9935             PrintPosition(f, currentMove);
9936             fprintf(f, "--------------}\n\n");
9937             
9938             SaveGame(f, 0, NULL); /* also closes the file*/
9939         }
9940         
9941         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9942         nCmailMovesRegistered ++;
9943     } else if (nCmailGames == 1) {
9944         DisplayError(_("You have not made a move yet"), 0);
9945         return FALSE;
9946     }
9947
9948     return TRUE;
9949 }
9950
9951 void
9952 MailMoveEvent()
9953 {
9954 #if !WIN32
9955     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9956     FILE *commandOutput;
9957     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9958     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
9959     int nBuffers;
9960     int i;
9961     int archived;
9962     char *arcDir;
9963
9964     if (! cmailMsgLoaded) {
9965         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9966         return;
9967     }
9968
9969     if (nCmailGames == nCmailResults) {
9970         DisplayError(_("No unfinished games"), 0);
9971         return;
9972     }
9973
9974 #if CMAIL_PROHIBIT_REMAIL
9975     if (cmailMailedMove) {
9976         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);
9977         DisplayError(msg, 0);
9978         return;
9979     }
9980 #endif
9981
9982     if (! (cmailMailedMove || RegisterMove())) return;
9983     
9984     if (   cmailMailedMove
9985         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
9986         sprintf(string, partCommandString,
9987                 appData.debugMode ? " -v" : "", appData.cmailGameName);
9988         commandOutput = popen(string, "r");
9989
9990         if (commandOutput == NULL) {
9991             DisplayError(_("Failed to invoke cmail"), 0);
9992         } else {
9993             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
9994                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
9995             }
9996             if (nBuffers > 1) {
9997                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
9998                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
9999                 nBytes = MSG_SIZ - 1;
10000             } else {
10001                 (void) memcpy(msg, buffer, nBytes);
10002             }
10003             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10004
10005             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10006                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10007
10008                 archived = TRUE;
10009                 for (i = 0; i < nCmailGames; i ++) {
10010                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10011                         archived = FALSE;
10012                     }
10013                 }
10014                 if (   archived
10015                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10016                         != NULL)) {
10017                     sprintf(buffer, "%s/%s.%s.archive",
10018                             arcDir,
10019                             appData.cmailGameName,
10020                             gameInfo.date);
10021                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10022                     cmailMsgLoaded = FALSE;
10023                 }
10024             }
10025
10026             DisplayInformation(msg);
10027             pclose(commandOutput);
10028         }
10029     } else {
10030         if ((*cmailMsg) != '\0') {
10031             DisplayInformation(cmailMsg);
10032         }
10033     }
10034
10035     return;
10036 #endif /* !WIN32 */
10037 }
10038
10039 char *
10040 CmailMsg()
10041 {
10042 #if WIN32
10043     return NULL;
10044 #else
10045     int  prependComma = 0;
10046     char number[5];
10047     char string[MSG_SIZ];       /* Space for game-list */
10048     int  i;
10049     
10050     if (!cmailMsgLoaded) return "";
10051
10052     if (cmailMailedMove) {
10053         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10054     } else {
10055         /* Create a list of games left */
10056         sprintf(string, "[");
10057         for (i = 0; i < nCmailGames; i ++) {
10058             if (! (   cmailMoveRegistered[i]
10059                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10060                 if (prependComma) {
10061                     sprintf(number, ",%d", i + 1);
10062                 } else {
10063                     sprintf(number, "%d", i + 1);
10064                     prependComma = 1;
10065                 }
10066                 
10067                 strcat(string, number);
10068             }
10069         }
10070         strcat(string, "]");
10071
10072         if (nCmailMovesRegistered + nCmailResults == 0) {
10073             switch (nCmailGames) {
10074               case 1:
10075                 sprintf(cmailMsg,
10076                         _("Still need to make move for game\n"));
10077                 break;
10078                 
10079               case 2:
10080                 sprintf(cmailMsg,
10081                         _("Still need to make moves for both games\n"));
10082                 break;
10083                 
10084               default:
10085                 sprintf(cmailMsg,
10086                         _("Still need to make moves for all %d games\n"),
10087                         nCmailGames);
10088                 break;
10089             }
10090         } else {
10091             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10092               case 1:
10093                 sprintf(cmailMsg,
10094                         _("Still need to make a move for game %s\n"),
10095                         string);
10096                 break;
10097                 
10098               case 0:
10099                 if (nCmailResults == nCmailGames) {
10100                     sprintf(cmailMsg, _("No unfinished games\n"));
10101                 } else {
10102                     sprintf(cmailMsg, _("Ready to send mail\n"));
10103                 }
10104                 break;
10105                 
10106               default:
10107                 sprintf(cmailMsg,
10108                         _("Still need to make moves for games %s\n"),
10109                         string);
10110             }
10111         }
10112     }
10113     return cmailMsg;
10114 #endif /* WIN32 */
10115 }
10116
10117 void
10118 ResetGameEvent()
10119 {
10120     if (gameMode == Training)
10121       SetTrainingModeOff();
10122
10123     Reset(TRUE, TRUE);
10124     cmailMsgLoaded = FALSE;
10125     if (appData.icsActive) {
10126       SendToICS(ics_prefix);
10127       SendToICS("refresh\n");
10128     }
10129 }
10130
10131 void
10132 ExitEvent(status)
10133      int status;
10134 {
10135     exiting++;
10136     if (exiting > 2) {
10137       /* Give up on clean exit */
10138       exit(status);
10139     }
10140     if (exiting > 1) {
10141       /* Keep trying for clean exit */
10142       return;
10143     }
10144
10145     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10146
10147     if (telnetISR != NULL) {
10148       RemoveInputSource(telnetISR);
10149     }
10150     if (icsPR != NoProc) {
10151       DestroyChildProcess(icsPR, TRUE);
10152     }
10153
10154     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10155     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10156
10157     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10158     /* make sure this other one finishes before killing it!                  */
10159     if(endingGame) { int count = 0;
10160         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10161         while(endingGame && count++ < 10) DoSleep(1);
10162         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10163     }
10164
10165     /* Kill off chess programs */
10166     if (first.pr != NoProc) {
10167         ExitAnalyzeMode();
10168         
10169         DoSleep( appData.delayBeforeQuit );
10170         SendToProgram("quit\n", &first);
10171         DoSleep( appData.delayAfterQuit );
10172         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10173     }
10174     if (second.pr != NoProc) {
10175         DoSleep( appData.delayBeforeQuit );
10176         SendToProgram("quit\n", &second);
10177         DoSleep( appData.delayAfterQuit );
10178         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10179     }
10180     if (first.isr != NULL) {
10181         RemoveInputSource(first.isr);
10182     }
10183     if (second.isr != NULL) {
10184         RemoveInputSource(second.isr);
10185     }
10186
10187     ShutDownFrontEnd();
10188     exit(status);
10189 }
10190
10191 void
10192 PauseEvent()
10193 {
10194     if (appData.debugMode)
10195         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10196     if (pausing) {
10197         pausing = FALSE;
10198         ModeHighlight();
10199         if (gameMode == MachinePlaysWhite ||
10200             gameMode == MachinePlaysBlack) {
10201             StartClocks();
10202         } else {
10203             DisplayBothClocks();
10204         }
10205         if (gameMode == PlayFromGameFile) {
10206             if (appData.timeDelay >= 0) 
10207                 AutoPlayGameLoop();
10208         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10209             Reset(FALSE, TRUE);
10210             SendToICS(ics_prefix);
10211             SendToICS("refresh\n");
10212         } else if (currentMove < forwardMostMove) {
10213             ForwardInner(forwardMostMove);
10214         }
10215         pauseExamInvalid = FALSE;
10216     } else {
10217         switch (gameMode) {
10218           default:
10219             return;
10220           case IcsExamining:
10221             pauseExamForwardMostMove = forwardMostMove;
10222             pauseExamInvalid = FALSE;
10223             /* fall through */
10224           case IcsObserving:
10225           case IcsPlayingWhite:
10226           case IcsPlayingBlack:
10227             pausing = TRUE;
10228             ModeHighlight();
10229             return;
10230           case PlayFromGameFile:
10231             (void) StopLoadGameTimer();
10232             pausing = TRUE;
10233             ModeHighlight();
10234             break;
10235           case BeginningOfGame:
10236             if (appData.icsActive) return;
10237             /* else fall through */
10238           case MachinePlaysWhite:
10239           case MachinePlaysBlack:
10240           case TwoMachinesPlay:
10241             if (forwardMostMove == 0)
10242               return;           /* don't pause if no one has moved */
10243             if ((gameMode == MachinePlaysWhite &&
10244                  !WhiteOnMove(forwardMostMove)) ||
10245                 (gameMode == MachinePlaysBlack &&
10246                  WhiteOnMove(forwardMostMove))) {
10247                 StopClocks();
10248             }
10249             pausing = TRUE;
10250             ModeHighlight();
10251             break;
10252         }
10253     }
10254 }
10255
10256 void
10257 EditCommentEvent()
10258 {
10259     char title[MSG_SIZ];
10260
10261     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10262         strcpy(title, _("Edit comment"));
10263     } else {
10264         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10265                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10266                 parseList[currentMove - 1]);
10267     }
10268
10269     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10270 }
10271
10272
10273 void
10274 EditTagsEvent()
10275 {
10276     char *tags = PGNTags(&gameInfo);
10277     EditTagsPopUp(tags);
10278     free(tags);
10279 }
10280
10281 void
10282 AnalyzeModeEvent()
10283 {
10284     if (appData.noChessProgram || gameMode == AnalyzeMode)
10285       return;
10286
10287     if (gameMode != AnalyzeFile) {
10288         if (!appData.icsEngineAnalyze) {
10289                EditGameEvent();
10290                if (gameMode != EditGame) return;
10291         }
10292         ResurrectChessProgram();
10293         SendToProgram("analyze\n", &first);
10294         first.analyzing = TRUE;
10295         /*first.maybeThinking = TRUE;*/
10296         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10297         AnalysisPopUp(_("Analysis"),
10298                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10299     }
10300     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10301     pausing = FALSE;
10302     ModeHighlight();
10303     SetGameInfo();
10304
10305     StartAnalysisClock();
10306     GetTimeMark(&lastNodeCountTime);
10307     lastNodeCount = 0;
10308 }
10309
10310 void
10311 AnalyzeFileEvent()
10312 {
10313     if (appData.noChessProgram || gameMode == AnalyzeFile)
10314       return;
10315
10316     if (gameMode != AnalyzeMode) {
10317         EditGameEvent();
10318         if (gameMode != EditGame) return;
10319         ResurrectChessProgram();
10320         SendToProgram("analyze\n", &first);
10321         first.analyzing = TRUE;
10322         /*first.maybeThinking = TRUE;*/
10323         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10324         AnalysisPopUp(_("Analysis"),
10325                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10326     }
10327     gameMode = AnalyzeFile;
10328     pausing = FALSE;
10329     ModeHighlight();
10330     SetGameInfo();
10331
10332     StartAnalysisClock();
10333     GetTimeMark(&lastNodeCountTime);
10334     lastNodeCount = 0;
10335 }
10336
10337 void
10338 MachineWhiteEvent()
10339 {
10340     char buf[MSG_SIZ];
10341     char *bookHit = NULL;
10342
10343     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10344       return;
10345
10346
10347     if (gameMode == PlayFromGameFile || 
10348         gameMode == TwoMachinesPlay  || 
10349         gameMode == Training         || 
10350         gameMode == AnalyzeMode      || 
10351         gameMode == EndOfGame)
10352         EditGameEvent();
10353
10354     if (gameMode == EditPosition) 
10355         EditPositionDone();
10356
10357     if (!WhiteOnMove(currentMove)) {
10358         DisplayError(_("It is not White's turn"), 0);
10359         return;
10360     }
10361   
10362     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10363       ExitAnalyzeMode();
10364
10365     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10366         gameMode == AnalyzeFile)
10367         TruncateGame();
10368
10369     ResurrectChessProgram();    /* in case it isn't running */
10370     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10371         gameMode = MachinePlaysWhite;
10372         ResetClocks();
10373     } else
10374     gameMode = MachinePlaysWhite;
10375     pausing = FALSE;
10376     ModeHighlight();
10377     SetGameInfo();
10378     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10379     DisplayTitle(buf);
10380     if (first.sendName) {
10381       sprintf(buf, "name %s\n", gameInfo.black);
10382       SendToProgram(buf, &first);
10383     }
10384     if (first.sendTime) {
10385       if (first.useColors) {
10386         SendToProgram("black\n", &first); /*gnu kludge*/
10387       }
10388       SendTimeRemaining(&first, TRUE);
10389     }
10390     if (first.useColors) {
10391       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10392     }
10393     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10394     SetMachineThinkingEnables();
10395     first.maybeThinking = TRUE;
10396     StartClocks();
10397     firstMove = FALSE;
10398
10399     if (appData.autoFlipView && !flipView) {
10400       flipView = !flipView;
10401       DrawPosition(FALSE, NULL);
10402       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10403     }
10404
10405     if(bookHit) { // [HGM] book: simulate book reply
10406         static char bookMove[MSG_SIZ]; // a bit generous?
10407
10408         programStats.nodes = programStats.depth = programStats.time = 
10409         programStats.score = programStats.got_only_move = 0;
10410         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10411
10412         strcpy(bookMove, "move ");
10413         strcat(bookMove, bookHit);
10414         HandleMachineMove(bookMove, &first);
10415     }
10416 }
10417
10418 void
10419 MachineBlackEvent()
10420 {
10421     char buf[MSG_SIZ];
10422    char *bookHit = NULL;
10423
10424     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10425         return;
10426
10427
10428     if (gameMode == PlayFromGameFile || 
10429         gameMode == TwoMachinesPlay  || 
10430         gameMode == Training         || 
10431         gameMode == AnalyzeMode      || 
10432         gameMode == EndOfGame)
10433         EditGameEvent();
10434
10435     if (gameMode == EditPosition) 
10436         EditPositionDone();
10437
10438     if (WhiteOnMove(currentMove)) {
10439         DisplayError(_("It is not Black's turn"), 0);
10440         return;
10441     }
10442     
10443     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10444       ExitAnalyzeMode();
10445
10446     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10447         gameMode == AnalyzeFile)
10448         TruncateGame();
10449
10450     ResurrectChessProgram();    /* in case it isn't running */
10451     gameMode = MachinePlaysBlack;
10452     pausing = FALSE;
10453     ModeHighlight();
10454     SetGameInfo();
10455     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10456     DisplayTitle(buf);
10457     if (first.sendName) {
10458       sprintf(buf, "name %s\n", gameInfo.white);
10459       SendToProgram(buf, &first);
10460     }
10461     if (first.sendTime) {
10462       if (first.useColors) {
10463         SendToProgram("white\n", &first); /*gnu kludge*/
10464       }
10465       SendTimeRemaining(&first, FALSE);
10466     }
10467     if (first.useColors) {
10468       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10469     }
10470     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10471     SetMachineThinkingEnables();
10472     first.maybeThinking = TRUE;
10473     StartClocks();
10474
10475     if (appData.autoFlipView && flipView) {
10476       flipView = !flipView;
10477       DrawPosition(FALSE, NULL);
10478       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10479     }
10480     if(bookHit) { // [HGM] book: simulate book reply
10481         static char bookMove[MSG_SIZ]; // a bit generous?
10482
10483         programStats.nodes = programStats.depth = programStats.time = 
10484         programStats.score = programStats.got_only_move = 0;
10485         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10486
10487         strcpy(bookMove, "move ");
10488         strcat(bookMove, bookHit);
10489         HandleMachineMove(bookMove, &first);
10490     }
10491 }
10492
10493
10494 void
10495 DisplayTwoMachinesTitle()
10496 {
10497     char buf[MSG_SIZ];
10498     if (appData.matchGames > 0) {
10499         if (first.twoMachinesColor[0] == 'w') {
10500             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10501                     gameInfo.white, gameInfo.black,
10502                     first.matchWins, second.matchWins,
10503                     matchGame - 1 - (first.matchWins + second.matchWins));
10504         } else {
10505             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10506                     gameInfo.white, gameInfo.black,
10507                     second.matchWins, first.matchWins,
10508                     matchGame - 1 - (first.matchWins + second.matchWins));
10509         }
10510     } else {
10511         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10512     }
10513     DisplayTitle(buf);
10514 }
10515
10516 void
10517 TwoMachinesEvent P((void))
10518 {
10519     int i;
10520     char buf[MSG_SIZ];
10521     ChessProgramState *onmove;
10522     char *bookHit = NULL;
10523     
10524     if (appData.noChessProgram) return;
10525
10526     switch (gameMode) {
10527       case TwoMachinesPlay:
10528         return;
10529       case MachinePlaysWhite:
10530       case MachinePlaysBlack:
10531         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10532             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10533             return;
10534         }
10535         /* fall through */
10536       case BeginningOfGame:
10537       case PlayFromGameFile:
10538       case EndOfGame:
10539         EditGameEvent();
10540         if (gameMode != EditGame) return;
10541         break;
10542       case EditPosition:
10543         EditPositionDone();
10544         break;
10545       case AnalyzeMode:
10546       case AnalyzeFile:
10547         ExitAnalyzeMode();
10548         break;
10549       case EditGame:
10550       default:
10551         break;
10552     }
10553
10554     forwardMostMove = currentMove;
10555     ResurrectChessProgram();    /* in case first program isn't running */
10556
10557     if (second.pr == NULL) {
10558         StartChessProgram(&second);
10559         if (second.protocolVersion == 1) {
10560           TwoMachinesEventIfReady();
10561         } else {
10562           /* kludge: allow timeout for initial "feature" command */
10563           FreezeUI();
10564           DisplayMessage("", _("Starting second chess program"));
10565           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10566         }
10567         return;
10568     }
10569     DisplayMessage("", "");
10570     InitChessProgram(&second, FALSE);
10571     SendToProgram("force\n", &second);
10572     if (startedFromSetupPosition) {
10573         SendBoard(&second, backwardMostMove);
10574     if (appData.debugMode) {
10575         fprintf(debugFP, "Two Machines\n");
10576     }
10577     }
10578     for (i = backwardMostMove; i < forwardMostMove; i++) {
10579         SendMoveToProgram(i, &second);
10580     }
10581
10582     gameMode = TwoMachinesPlay;
10583     pausing = FALSE;
10584     ModeHighlight();
10585     SetGameInfo();
10586     DisplayTwoMachinesTitle();
10587     firstMove = TRUE;
10588     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10589         onmove = &first;
10590     } else {
10591         onmove = &second;
10592     }
10593
10594     SendToProgram(first.computerString, &first);
10595     if (first.sendName) {
10596       sprintf(buf, "name %s\n", second.tidy);
10597       SendToProgram(buf, &first);
10598     }
10599     SendToProgram(second.computerString, &second);
10600     if (second.sendName) {
10601       sprintf(buf, "name %s\n", first.tidy);
10602       SendToProgram(buf, &second);
10603     }
10604
10605     ResetClocks();
10606     if (!first.sendTime || !second.sendTime) {
10607         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10608         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10609     }
10610     if (onmove->sendTime) {
10611       if (onmove->useColors) {
10612         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10613       }
10614       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10615     }
10616     if (onmove->useColors) {
10617       SendToProgram(onmove->twoMachinesColor, onmove);
10618     }
10619     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10620 //    SendToProgram("go\n", onmove);
10621     onmove->maybeThinking = TRUE;
10622     SetMachineThinkingEnables();
10623
10624     StartClocks();
10625
10626     if(bookHit) { // [HGM] book: simulate book reply
10627         static char bookMove[MSG_SIZ]; // a bit generous?
10628
10629         programStats.nodes = programStats.depth = programStats.time = 
10630         programStats.score = programStats.got_only_move = 0;
10631         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10632
10633         strcpy(bookMove, "move ");
10634         strcat(bookMove, bookHit);
10635         HandleMachineMove(bookMove, &first);
10636     }
10637 }
10638
10639 void
10640 TrainingEvent()
10641 {
10642     if (gameMode == Training) {
10643       SetTrainingModeOff();
10644       gameMode = PlayFromGameFile;
10645       DisplayMessage("", _("Training mode off"));
10646     } else {
10647       gameMode = Training;
10648       animateTraining = appData.animate;
10649
10650       /* make sure we are not already at the end of the game */
10651       if (currentMove < forwardMostMove) {
10652         SetTrainingModeOn();
10653         DisplayMessage("", _("Training mode on"));
10654       } else {
10655         gameMode = PlayFromGameFile;
10656         DisplayError(_("Already at end of game"), 0);
10657       }
10658     }
10659     ModeHighlight();
10660 }
10661
10662 void
10663 IcsClientEvent()
10664 {
10665     if (!appData.icsActive) return;
10666     switch (gameMode) {
10667       case IcsPlayingWhite:
10668       case IcsPlayingBlack:
10669       case IcsObserving:
10670       case IcsIdle:
10671       case BeginningOfGame:
10672       case IcsExamining:
10673         return;
10674
10675       case EditGame:
10676         break;
10677
10678       case EditPosition:
10679         EditPositionDone();
10680         break;
10681
10682       case AnalyzeMode:
10683       case AnalyzeFile:
10684         ExitAnalyzeMode();
10685         break;
10686         
10687       default:
10688         EditGameEvent();
10689         break;
10690     }
10691
10692     gameMode = IcsIdle;
10693     ModeHighlight();
10694     return;
10695 }
10696
10697
10698 void
10699 EditGameEvent()
10700 {
10701     int i;
10702
10703     switch (gameMode) {
10704       case Training:
10705         SetTrainingModeOff();
10706         break;
10707       case MachinePlaysWhite:
10708       case MachinePlaysBlack:
10709       case BeginningOfGame:
10710         SendToProgram("force\n", &first);
10711         SetUserThinkingEnables();
10712         break;
10713       case PlayFromGameFile:
10714         (void) StopLoadGameTimer();
10715         if (gameFileFP != NULL) {
10716             gameFileFP = NULL;
10717         }
10718         break;
10719       case EditPosition:
10720         EditPositionDone();
10721         break;
10722       case AnalyzeMode:
10723       case AnalyzeFile:
10724         ExitAnalyzeMode();
10725         SendToProgram("force\n", &first);
10726         break;
10727       case TwoMachinesPlay:
10728         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10729         ResurrectChessProgram();
10730         SetUserThinkingEnables();
10731         break;
10732       case EndOfGame:
10733         ResurrectChessProgram();
10734         break;
10735       case IcsPlayingBlack:
10736       case IcsPlayingWhite:
10737         DisplayError(_("Warning: You are still playing a game"), 0);
10738         break;
10739       case IcsObserving:
10740         DisplayError(_("Warning: You are still observing a game"), 0);
10741         break;
10742       case IcsExamining:
10743         DisplayError(_("Warning: You are still examining a game"), 0);
10744         break;
10745       case IcsIdle:
10746         break;
10747       case EditGame:
10748       default:
10749         return;
10750     }
10751     
10752     pausing = FALSE;
10753     StopClocks();
10754     first.offeredDraw = second.offeredDraw = 0;
10755
10756     if (gameMode == PlayFromGameFile) {
10757         whiteTimeRemaining = timeRemaining[0][currentMove];
10758         blackTimeRemaining = timeRemaining[1][currentMove];
10759         DisplayTitle("");
10760     }
10761
10762     if (gameMode == MachinePlaysWhite ||
10763         gameMode == MachinePlaysBlack ||
10764         gameMode == TwoMachinesPlay ||
10765         gameMode == EndOfGame) {
10766         i = forwardMostMove;
10767         while (i > currentMove) {
10768             SendToProgram("undo\n", &first);
10769             i--;
10770         }
10771         whiteTimeRemaining = timeRemaining[0][currentMove];
10772         blackTimeRemaining = timeRemaining[1][currentMove];
10773         DisplayBothClocks();
10774         if (whiteFlag || blackFlag) {
10775             whiteFlag = blackFlag = 0;
10776         }
10777         DisplayTitle("");
10778     }           
10779     
10780     gameMode = EditGame;
10781     ModeHighlight();
10782     SetGameInfo();
10783 }
10784
10785
10786 void
10787 EditPositionEvent()
10788 {
10789     if (gameMode == EditPosition) {
10790         EditGameEvent();
10791         return;
10792     }
10793     
10794     EditGameEvent();
10795     if (gameMode != EditGame) return;
10796     
10797     gameMode = EditPosition;
10798     ModeHighlight();
10799     SetGameInfo();
10800     if (currentMove > 0)
10801       CopyBoard(boards[0], boards[currentMove]);
10802     
10803     blackPlaysFirst = !WhiteOnMove(currentMove);
10804     ResetClocks();
10805     currentMove = forwardMostMove = backwardMostMove = 0;
10806     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10807     DisplayMove(-1);
10808 }
10809
10810 void
10811 ExitAnalyzeMode()
10812 {
10813     /* [DM] icsEngineAnalyze - possible call from other functions */
10814     if (appData.icsEngineAnalyze) {
10815         appData.icsEngineAnalyze = FALSE;
10816
10817         DisplayMessage("",_("Close ICS engine analyze..."));
10818     }
10819     if (first.analysisSupport && first.analyzing) {
10820       SendToProgram("exit\n", &first);
10821       first.analyzing = FALSE;
10822     }
10823     AnalysisPopDown();
10824     thinkOutput[0] = NULLCHAR;
10825 }
10826
10827 void
10828 EditPositionDone()
10829 {
10830     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10831
10832     startedFromSetupPosition = TRUE;
10833     InitChessProgram(&first, FALSE);
10834     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10835     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10836         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10837         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10838     } else castlingRights[0][2] = -1;
10839     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10840         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10841         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10842     } else castlingRights[0][5] = -1;
10843     SendToProgram("force\n", &first);
10844     if (blackPlaysFirst) {
10845         strcpy(moveList[0], "");
10846         strcpy(parseList[0], "");
10847         currentMove = forwardMostMove = backwardMostMove = 1;
10848         CopyBoard(boards[1], boards[0]);
10849         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10850         { int i;
10851           epStatus[1] = epStatus[0];
10852           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10853         }
10854     } else {
10855         currentMove = forwardMostMove = backwardMostMove = 0;
10856     }
10857     SendBoard(&first, forwardMostMove);
10858     if (appData.debugMode) {
10859         fprintf(debugFP, "EditPosDone\n");
10860     }
10861     DisplayTitle("");
10862     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10863     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10864     gameMode = EditGame;
10865     ModeHighlight();
10866     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10867     ClearHighlights(); /* [AS] */
10868 }
10869
10870 /* Pause for `ms' milliseconds */
10871 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10872 void
10873 TimeDelay(ms)
10874      long ms;
10875 {
10876     TimeMark m1, m2;
10877
10878     GetTimeMark(&m1);
10879     do {
10880         GetTimeMark(&m2);
10881     } while (SubtractTimeMarks(&m2, &m1) < ms);
10882 }
10883
10884 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10885 void
10886 SendMultiLineToICS(buf)
10887      char *buf;
10888 {
10889     char temp[MSG_SIZ+1], *p;
10890     int len;
10891
10892     len = strlen(buf);
10893     if (len > MSG_SIZ)
10894       len = MSG_SIZ;
10895   
10896     strncpy(temp, buf, len);
10897     temp[len] = 0;
10898
10899     p = temp;
10900     while (*p) {
10901         if (*p == '\n' || *p == '\r')
10902           *p = ' ';
10903         ++p;
10904     }
10905
10906     strcat(temp, "\n");
10907     SendToICS(temp);
10908     SendToPlayer(temp, strlen(temp));
10909 }
10910
10911 void
10912 SetWhiteToPlayEvent()
10913 {
10914     if (gameMode == EditPosition) {
10915         blackPlaysFirst = FALSE;
10916         DisplayBothClocks();    /* works because currentMove is 0 */
10917     } else if (gameMode == IcsExamining) {
10918         SendToICS(ics_prefix);
10919         SendToICS("tomove white\n");
10920     }
10921 }
10922
10923 void
10924 SetBlackToPlayEvent()
10925 {
10926     if (gameMode == EditPosition) {
10927         blackPlaysFirst = TRUE;
10928         currentMove = 1;        /* kludge */
10929         DisplayBothClocks();
10930         currentMove = 0;
10931     } else if (gameMode == IcsExamining) {
10932         SendToICS(ics_prefix);
10933         SendToICS("tomove black\n");
10934     }
10935 }
10936
10937 void
10938 EditPositionMenuEvent(selection, x, y)
10939      ChessSquare selection;
10940      int x, y;
10941 {
10942     char buf[MSG_SIZ];
10943     ChessSquare piece = boards[0][y][x];
10944
10945     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10946
10947     switch (selection) {
10948       case ClearBoard:
10949         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10950             SendToICS(ics_prefix);
10951             SendToICS("bsetup clear\n");
10952         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10953             SendToICS(ics_prefix);
10954             SendToICS("clearboard\n");
10955         } else {
10956             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10957                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10958                 for (y = 0; y < BOARD_HEIGHT; y++) {
10959                     if (gameMode == IcsExamining) {
10960                         if (boards[currentMove][y][x] != EmptySquare) {
10961                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
10962                                     AAA + x, ONE + y);
10963                             SendToICS(buf);
10964                         }
10965                     } else {
10966                         boards[0][y][x] = p;
10967                     }
10968                 }
10969             }
10970         }
10971         if (gameMode == EditPosition) {
10972             DrawPosition(FALSE, boards[0]);
10973         }
10974         break;
10975
10976       case WhitePlay:
10977         SetWhiteToPlayEvent();
10978         break;
10979
10980       case BlackPlay:
10981         SetBlackToPlayEvent();
10982         break;
10983
10984       case EmptySquare:
10985         if (gameMode == IcsExamining) {
10986             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
10987             SendToICS(buf);
10988         } else {
10989             boards[0][y][x] = EmptySquare;
10990             DrawPosition(FALSE, boards[0]);
10991         }
10992         break;
10993
10994       case PromotePiece:
10995         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
10996            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
10997             selection = (ChessSquare) (PROMOTED piece);
10998         } else if(piece == EmptySquare) selection = WhiteSilver;
10999         else selection = (ChessSquare)((int)piece - 1);
11000         goto defaultlabel;
11001
11002       case DemotePiece:
11003         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11004            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11005             selection = (ChessSquare) (DEMOTED piece);
11006         } else if(piece == EmptySquare) selection = BlackSilver;
11007         else selection = (ChessSquare)((int)piece + 1);       
11008         goto defaultlabel;
11009
11010       case WhiteQueen:
11011       case BlackQueen:
11012         if(gameInfo.variant == VariantShatranj ||
11013            gameInfo.variant == VariantXiangqi  ||
11014            gameInfo.variant == VariantCourier    )
11015             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11016         goto defaultlabel;
11017
11018       case WhiteKing:
11019       case BlackKing:
11020         if(gameInfo.variant == VariantXiangqi)
11021             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11022         if(gameInfo.variant == VariantKnightmate)
11023             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11024       default:
11025         defaultlabel:
11026         if (gameMode == IcsExamining) {
11027             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11028                     PieceToChar(selection), AAA + x, ONE + y);
11029             SendToICS(buf);
11030         } else {
11031             boards[0][y][x] = selection;
11032             DrawPosition(FALSE, boards[0]);
11033         }
11034         break;
11035     }
11036 }
11037
11038
11039 void
11040 DropMenuEvent(selection, x, y)
11041      ChessSquare selection;
11042      int x, y;
11043 {
11044     ChessMove moveType;
11045
11046     switch (gameMode) {
11047       case IcsPlayingWhite:
11048       case MachinePlaysBlack:
11049         if (!WhiteOnMove(currentMove)) {
11050             DisplayMoveError(_("It is Black's turn"));
11051             return;
11052         }
11053         moveType = WhiteDrop;
11054         break;
11055       case IcsPlayingBlack:
11056       case MachinePlaysWhite:
11057         if (WhiteOnMove(currentMove)) {
11058             DisplayMoveError(_("It is White's turn"));
11059             return;
11060         }
11061         moveType = BlackDrop;
11062         break;
11063       case EditGame:
11064         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11065         break;
11066       default:
11067         return;
11068     }
11069
11070     if (moveType == BlackDrop && selection < BlackPawn) {
11071       selection = (ChessSquare) ((int) selection
11072                                  + (int) BlackPawn - (int) WhitePawn);
11073     }
11074     if (boards[currentMove][y][x] != EmptySquare) {
11075         DisplayMoveError(_("That square is occupied"));
11076         return;
11077     }
11078
11079     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11080 }
11081
11082 void
11083 AcceptEvent()
11084 {
11085     /* Accept a pending offer of any kind from opponent */
11086     
11087     if (appData.icsActive) {
11088         SendToICS(ics_prefix);
11089         SendToICS("accept\n");
11090     } else if (cmailMsgLoaded) {
11091         if (currentMove == cmailOldMove &&
11092             commentList[cmailOldMove] != NULL &&
11093             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11094                    "Black offers a draw" : "White offers a draw")) {
11095             TruncateGame();
11096             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11097             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11098         } else {
11099             DisplayError(_("There is no pending offer on this move"), 0);
11100             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11101         }
11102     } else {
11103         /* Not used for offers from chess program */
11104     }
11105 }
11106
11107 void
11108 DeclineEvent()
11109 {
11110     /* Decline a pending offer of any kind from opponent */
11111     
11112     if (appData.icsActive) {
11113         SendToICS(ics_prefix);
11114         SendToICS("decline\n");
11115     } else if (cmailMsgLoaded) {
11116         if (currentMove == cmailOldMove &&
11117             commentList[cmailOldMove] != NULL &&
11118             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11119                    "Black offers a draw" : "White offers a draw")) {
11120 #ifdef NOTDEF
11121             AppendComment(cmailOldMove, "Draw declined");
11122             DisplayComment(cmailOldMove - 1, "Draw declined");
11123 #endif /*NOTDEF*/
11124         } else {
11125             DisplayError(_("There is no pending offer on this move"), 0);
11126         }
11127     } else {
11128         /* Not used for offers from chess program */
11129     }
11130 }
11131
11132 void
11133 RematchEvent()
11134 {
11135     /* Issue ICS rematch command */
11136     if (appData.icsActive) {
11137         SendToICS(ics_prefix);
11138         SendToICS("rematch\n");
11139     }
11140 }
11141
11142 void
11143 CallFlagEvent()
11144 {
11145     /* Call your opponent's flag (claim a win on time) */
11146     if (appData.icsActive) {
11147         SendToICS(ics_prefix);
11148         SendToICS("flag\n");
11149     } else {
11150         switch (gameMode) {
11151           default:
11152             return;
11153           case MachinePlaysWhite:
11154             if (whiteFlag) {
11155                 if (blackFlag)
11156                   GameEnds(GameIsDrawn, "Both players ran out of time",
11157                            GE_PLAYER);
11158                 else
11159                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11160             } else {
11161                 DisplayError(_("Your opponent is not out of time"), 0);
11162             }
11163             break;
11164           case MachinePlaysBlack:
11165             if (blackFlag) {
11166                 if (whiteFlag)
11167                   GameEnds(GameIsDrawn, "Both players ran out of time",
11168                            GE_PLAYER);
11169                 else
11170                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11171             } else {
11172                 DisplayError(_("Your opponent is not out of time"), 0);
11173             }
11174             break;
11175         }
11176     }
11177 }
11178
11179 void
11180 DrawEvent()
11181 {
11182     /* Offer draw or accept pending draw offer from opponent */
11183     
11184     if (appData.icsActive) {
11185         /* Note: tournament rules require draw offers to be
11186            made after you make your move but before you punch
11187            your clock.  Currently ICS doesn't let you do that;
11188            instead, you immediately punch your clock after making
11189            a move, but you can offer a draw at any time. */
11190         
11191         SendToICS(ics_prefix);
11192         SendToICS("draw\n");
11193     } else if (cmailMsgLoaded) {
11194         if (currentMove == cmailOldMove &&
11195             commentList[cmailOldMove] != NULL &&
11196             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11197                    "Black offers a draw" : "White offers a draw")) {
11198             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11199             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11200         } else if (currentMove == cmailOldMove + 1) {
11201             char *offer = WhiteOnMove(cmailOldMove) ?
11202               "White offers a draw" : "Black offers a draw";
11203             AppendComment(currentMove, offer);
11204             DisplayComment(currentMove - 1, offer);
11205             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11206         } else {
11207             DisplayError(_("You must make your move before offering a draw"), 0);
11208             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11209         }
11210     } else if (first.offeredDraw) {
11211         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11212     } else {
11213         if (first.sendDrawOffers) {
11214             SendToProgram("draw\n", &first);
11215             userOfferedDraw = TRUE;
11216         }
11217     }
11218 }
11219
11220 void
11221 AdjournEvent()
11222 {
11223     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11224     
11225     if (appData.icsActive) {
11226         SendToICS(ics_prefix);
11227         SendToICS("adjourn\n");
11228     } else {
11229         /* Currently GNU Chess doesn't offer or accept Adjourns */
11230     }
11231 }
11232
11233
11234 void
11235 AbortEvent()
11236 {
11237     /* Offer Abort or accept pending Abort offer from opponent */
11238     
11239     if (appData.icsActive) {
11240         SendToICS(ics_prefix);
11241         SendToICS("abort\n");
11242     } else {
11243         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11244     }
11245 }
11246
11247 void
11248 ResignEvent()
11249 {
11250     /* Resign.  You can do this even if it's not your turn. */
11251     
11252     if (appData.icsActive) {
11253         SendToICS(ics_prefix);
11254         SendToICS("resign\n");
11255     } else {
11256         switch (gameMode) {
11257           case MachinePlaysWhite:
11258             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11259             break;
11260           case MachinePlaysBlack:
11261             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11262             break;
11263           case EditGame:
11264             if (cmailMsgLoaded) {
11265                 TruncateGame();
11266                 if (WhiteOnMove(cmailOldMove)) {
11267                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11268                 } else {
11269                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11270                 }
11271                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11272             }
11273             break;
11274           default:
11275             break;
11276         }
11277     }
11278 }
11279
11280
11281 void
11282 StopObservingEvent()
11283 {
11284     /* Stop observing current games */
11285     SendToICS(ics_prefix);
11286     SendToICS("unobserve\n");
11287 }
11288
11289 void
11290 StopExaminingEvent()
11291 {
11292     /* Stop observing current game */
11293     SendToICS(ics_prefix);
11294     SendToICS("unexamine\n");
11295 }
11296
11297 void
11298 ForwardInner(target)
11299      int target;
11300 {
11301     int limit;
11302
11303     if (appData.debugMode)
11304         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11305                 target, currentMove, forwardMostMove);
11306
11307     if (gameMode == EditPosition)
11308       return;
11309
11310     if (gameMode == PlayFromGameFile && !pausing)
11311       PauseEvent();
11312     
11313     if (gameMode == IcsExamining && pausing)
11314       limit = pauseExamForwardMostMove;
11315     else
11316       limit = forwardMostMove;
11317     
11318     if (target > limit) target = limit;
11319
11320     if (target > 0 && moveList[target - 1][0]) {
11321         int fromX, fromY, toX, toY;
11322         toX = moveList[target - 1][2] - AAA;
11323         toY = moveList[target - 1][3] - ONE;
11324         if (moveList[target - 1][1] == '@') {
11325             if (appData.highlightLastMove) {
11326                 SetHighlights(-1, -1, toX, toY);
11327             }
11328         } else {
11329             fromX = moveList[target - 1][0] - AAA;
11330             fromY = moveList[target - 1][1] - ONE;
11331             if (target == currentMove + 1) {
11332                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11333             }
11334             if (appData.highlightLastMove) {
11335                 SetHighlights(fromX, fromY, toX, toY);
11336             }
11337         }
11338     }
11339     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11340         gameMode == Training || gameMode == PlayFromGameFile || 
11341         gameMode == AnalyzeFile) {
11342         while (currentMove < target) {
11343             SendMoveToProgram(currentMove++, &first);
11344         }
11345     } else {
11346         currentMove = target;
11347     }
11348     
11349     if (gameMode == EditGame || gameMode == EndOfGame) {
11350         whiteTimeRemaining = timeRemaining[0][currentMove];
11351         blackTimeRemaining = timeRemaining[1][currentMove];
11352     }
11353     DisplayBothClocks();
11354     DisplayMove(currentMove - 1);
11355     DrawPosition(FALSE, boards[currentMove]);
11356     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11357     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11358         DisplayComment(currentMove - 1, commentList[currentMove]);
11359     }
11360 }
11361
11362
11363 void
11364 ForwardEvent()
11365 {
11366     if (gameMode == IcsExamining && !pausing) {
11367         SendToICS(ics_prefix);
11368         SendToICS("forward\n");
11369     } else {
11370         ForwardInner(currentMove + 1);
11371     }
11372 }
11373
11374 void
11375 ToEndEvent()
11376 {
11377     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11378         /* to optimze, we temporarily turn off analysis mode while we feed
11379          * the remaining moves to the engine. Otherwise we get analysis output
11380          * after each move.
11381          */ 
11382         if (first.analysisSupport) {
11383           SendToProgram("exit\nforce\n", &first);
11384           first.analyzing = FALSE;
11385         }
11386     }
11387         
11388     if (gameMode == IcsExamining && !pausing) {
11389         SendToICS(ics_prefix);
11390         SendToICS("forward 999999\n");
11391     } else {
11392         ForwardInner(forwardMostMove);
11393     }
11394
11395     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11396         /* we have fed all the moves, so reactivate analysis mode */
11397         SendToProgram("analyze\n", &first);
11398         first.analyzing = TRUE;
11399         /*first.maybeThinking = TRUE;*/
11400         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11401     }
11402 }
11403
11404 void
11405 BackwardInner(target)
11406      int target;
11407 {
11408     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11409
11410     if (appData.debugMode)
11411         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11412                 target, currentMove, forwardMostMove);
11413
11414     if (gameMode == EditPosition) return;
11415     if (currentMove <= backwardMostMove) {
11416         ClearHighlights();
11417         DrawPosition(full_redraw, boards[currentMove]);
11418         return;
11419     }
11420     if (gameMode == PlayFromGameFile && !pausing)
11421       PauseEvent();
11422     
11423     if (moveList[target][0]) {
11424         int fromX, fromY, toX, toY;
11425         toX = moveList[target][2] - AAA;
11426         toY = moveList[target][3] - ONE;
11427         if (moveList[target][1] == '@') {
11428             if (appData.highlightLastMove) {
11429                 SetHighlights(-1, -1, toX, toY);
11430             }
11431         } else {
11432             fromX = moveList[target][0] - AAA;
11433             fromY = moveList[target][1] - ONE;
11434             if (target == currentMove - 1) {
11435                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11436             }
11437             if (appData.highlightLastMove) {
11438                 SetHighlights(fromX, fromY, toX, toY);
11439             }
11440         }
11441     }
11442     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11443         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11444         while (currentMove > target) {
11445             SendToProgram("undo\n", &first);
11446             currentMove--;
11447         }
11448     } else {
11449         currentMove = target;
11450     }
11451     
11452     if (gameMode == EditGame || gameMode == EndOfGame) {
11453         whiteTimeRemaining = timeRemaining[0][currentMove];
11454         blackTimeRemaining = timeRemaining[1][currentMove];
11455     }
11456     DisplayBothClocks();
11457     DisplayMove(currentMove - 1);
11458     DrawPosition(full_redraw, boards[currentMove]);
11459     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11460     // [HGM] PV info: routine tests if comment empty
11461     DisplayComment(currentMove - 1, commentList[currentMove]);
11462 }
11463
11464 void
11465 BackwardEvent()
11466 {
11467     if (gameMode == IcsExamining && !pausing) {
11468         SendToICS(ics_prefix);
11469         SendToICS("backward\n");
11470     } else {
11471         BackwardInner(currentMove - 1);
11472     }
11473 }
11474
11475 void
11476 ToStartEvent()
11477 {
11478     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11479         /* to optimze, we temporarily turn off analysis mode while we undo
11480          * all the moves. Otherwise we get analysis output after each undo.
11481          */ 
11482         if (first.analysisSupport) {
11483           SendToProgram("exit\nforce\n", &first);
11484           first.analyzing = FALSE;
11485         }
11486     }
11487
11488     if (gameMode == IcsExamining && !pausing) {
11489         SendToICS(ics_prefix);
11490         SendToICS("backward 999999\n");
11491     } else {
11492         BackwardInner(backwardMostMove);
11493     }
11494
11495     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11496         /* we have fed all the moves, so reactivate analysis mode */
11497         SendToProgram("analyze\n", &first);
11498         first.analyzing = TRUE;
11499         /*first.maybeThinking = TRUE;*/
11500         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11501     }
11502 }
11503
11504 void
11505 ToNrEvent(int to)
11506 {
11507   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11508   if (to >= forwardMostMove) to = forwardMostMove;
11509   if (to <= backwardMostMove) to = backwardMostMove;
11510   if (to < currentMove) {
11511     BackwardInner(to);
11512   } else {
11513     ForwardInner(to);
11514   }
11515 }
11516
11517 void
11518 RevertEvent()
11519 {
11520     if (gameMode != IcsExamining) {
11521         DisplayError(_("You are not examining a game"), 0);
11522         return;
11523     }
11524     if (pausing) {
11525         DisplayError(_("You can't revert while pausing"), 0);
11526         return;
11527     }
11528     SendToICS(ics_prefix);
11529     SendToICS("revert\n");
11530 }
11531
11532 void
11533 RetractMoveEvent()
11534 {
11535     switch (gameMode) {
11536       case MachinePlaysWhite:
11537       case MachinePlaysBlack:
11538         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11539             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11540             return;
11541         }
11542         if (forwardMostMove < 2) return;
11543         currentMove = forwardMostMove = forwardMostMove - 2;
11544         whiteTimeRemaining = timeRemaining[0][currentMove];
11545         blackTimeRemaining = timeRemaining[1][currentMove];
11546         DisplayBothClocks();
11547         DisplayMove(currentMove - 1);
11548         ClearHighlights();/*!! could figure this out*/
11549         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11550         SendToProgram("remove\n", &first);
11551         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11552         break;
11553
11554       case BeginningOfGame:
11555       default:
11556         break;
11557
11558       case IcsPlayingWhite:
11559       case IcsPlayingBlack:
11560         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11561             SendToICS(ics_prefix);
11562             SendToICS("takeback 2\n");
11563         } else {
11564             SendToICS(ics_prefix);
11565             SendToICS("takeback 1\n");
11566         }
11567         break;
11568     }
11569 }
11570
11571 void
11572 MoveNowEvent()
11573 {
11574     ChessProgramState *cps;
11575
11576     switch (gameMode) {
11577       case MachinePlaysWhite:
11578         if (!WhiteOnMove(forwardMostMove)) {
11579             DisplayError(_("It is your turn"), 0);
11580             return;
11581         }
11582         cps = &first;
11583         break;
11584       case MachinePlaysBlack:
11585         if (WhiteOnMove(forwardMostMove)) {
11586             DisplayError(_("It is your turn"), 0);
11587             return;
11588         }
11589         cps = &first;
11590         break;
11591       case TwoMachinesPlay:
11592         if (WhiteOnMove(forwardMostMove) ==
11593             (first.twoMachinesColor[0] == 'w')) {
11594             cps = &first;
11595         } else {
11596             cps = &second;
11597         }
11598         break;
11599       case BeginningOfGame:
11600       default:
11601         return;
11602     }
11603     SendToProgram("?\n", cps);
11604 }
11605
11606 void
11607 TruncateGameEvent()
11608 {
11609     EditGameEvent();
11610     if (gameMode != EditGame) return;
11611     TruncateGame();
11612 }
11613
11614 void
11615 TruncateGame()
11616 {
11617     if (forwardMostMove > currentMove) {
11618         if (gameInfo.resultDetails != NULL) {
11619             free(gameInfo.resultDetails);
11620             gameInfo.resultDetails = NULL;
11621             gameInfo.result = GameUnfinished;
11622         }
11623         forwardMostMove = currentMove;
11624         HistorySet(parseList, backwardMostMove, forwardMostMove,
11625                    currentMove-1);
11626     }
11627 }
11628
11629 void
11630 HintEvent()
11631 {
11632     if (appData.noChessProgram) return;
11633     switch (gameMode) {
11634       case MachinePlaysWhite:
11635         if (WhiteOnMove(forwardMostMove)) {
11636             DisplayError(_("Wait until your turn"), 0);
11637             return;
11638         }
11639         break;
11640       case BeginningOfGame:
11641       case MachinePlaysBlack:
11642         if (!WhiteOnMove(forwardMostMove)) {
11643             DisplayError(_("Wait until your turn"), 0);
11644             return;
11645         }
11646         break;
11647       default:
11648         DisplayError(_("No hint available"), 0);
11649         return;
11650     }
11651     SendToProgram("hint\n", &first);
11652     hintRequested = TRUE;
11653 }
11654
11655 void
11656 BookEvent()
11657 {
11658     if (appData.noChessProgram) return;
11659     switch (gameMode) {
11660       case MachinePlaysWhite:
11661         if (WhiteOnMove(forwardMostMove)) {
11662             DisplayError(_("Wait until your turn"), 0);
11663             return;
11664         }
11665         break;
11666       case BeginningOfGame:
11667       case MachinePlaysBlack:
11668         if (!WhiteOnMove(forwardMostMove)) {
11669             DisplayError(_("Wait until your turn"), 0);
11670             return;
11671         }
11672         break;
11673       case EditPosition:
11674         EditPositionDone();
11675         break;
11676       case TwoMachinesPlay:
11677         return;
11678       default:
11679         break;
11680     }
11681     SendToProgram("bk\n", &first);
11682     bookOutput[0] = NULLCHAR;
11683     bookRequested = TRUE;
11684 }
11685
11686 void
11687 AboutGameEvent()
11688 {
11689     char *tags = PGNTags(&gameInfo);
11690     TagsPopUp(tags, CmailMsg());
11691     free(tags);
11692 }
11693
11694 /* end button procedures */
11695
11696 void
11697 PrintPosition(fp, move)
11698      FILE *fp;
11699      int move;
11700 {
11701     int i, j;
11702     
11703     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11704         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11705             char c = PieceToChar(boards[move][i][j]);
11706             fputc(c == 'x' ? '.' : c, fp);
11707             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11708         }
11709     }
11710     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11711       fprintf(fp, "white to play\n");
11712     else
11713       fprintf(fp, "black to play\n");
11714 }
11715
11716 void
11717 PrintOpponents(fp)
11718      FILE *fp;
11719 {
11720     if (gameInfo.white != NULL) {
11721         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11722     } else {
11723         fprintf(fp, "\n");
11724     }
11725 }
11726
11727 /* Find last component of program's own name, using some heuristics */
11728 void
11729 TidyProgramName(prog, host, buf)
11730      char *prog, *host, buf[MSG_SIZ];
11731 {
11732     char *p, *q;
11733     int local = (strcmp(host, "localhost") == 0);
11734     while (!local && (p = strchr(prog, ';')) != NULL) {
11735         p++;
11736         while (*p == ' ') p++;
11737         prog = p;
11738     }
11739     if (*prog == '"' || *prog == '\'') {
11740         q = strchr(prog + 1, *prog);
11741     } else {
11742         q = strchr(prog, ' ');
11743     }
11744     if (q == NULL) q = prog + strlen(prog);
11745     p = q;
11746     while (p >= prog && *p != '/' && *p != '\\') p--;
11747     p++;
11748     if(p == prog && *p == '"') p++;
11749     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11750     memcpy(buf, p, q - p);
11751     buf[q - p] = NULLCHAR;
11752     if (!local) {
11753         strcat(buf, "@");
11754         strcat(buf, host);
11755     }
11756 }
11757
11758 char *
11759 TimeControlTagValue()
11760 {
11761     char buf[MSG_SIZ];
11762     if (!appData.clockMode) {
11763         strcpy(buf, "-");
11764     } else if (movesPerSession > 0) {
11765         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11766     } else if (timeIncrement == 0) {
11767         sprintf(buf, "%ld", timeControl/1000);
11768     } else {
11769         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11770     }
11771     return StrSave(buf);
11772 }
11773
11774 void
11775 SetGameInfo()
11776 {
11777     /* This routine is used only for certain modes */
11778     VariantClass v = gameInfo.variant;
11779     ClearGameInfo(&gameInfo);
11780     gameInfo.variant = v;
11781
11782     switch (gameMode) {
11783       case MachinePlaysWhite:
11784         gameInfo.event = StrSave( appData.pgnEventHeader );
11785         gameInfo.site = StrSave(HostName());
11786         gameInfo.date = PGNDate();
11787         gameInfo.round = StrSave("-");
11788         gameInfo.white = StrSave(first.tidy);
11789         gameInfo.black = StrSave(UserName());
11790         gameInfo.timeControl = TimeControlTagValue();
11791         break;
11792
11793       case MachinePlaysBlack:
11794         gameInfo.event = StrSave( appData.pgnEventHeader );
11795         gameInfo.site = StrSave(HostName());
11796         gameInfo.date = PGNDate();
11797         gameInfo.round = StrSave("-");
11798         gameInfo.white = StrSave(UserName());
11799         gameInfo.black = StrSave(first.tidy);
11800         gameInfo.timeControl = TimeControlTagValue();
11801         break;
11802
11803       case TwoMachinesPlay:
11804         gameInfo.event = StrSave( appData.pgnEventHeader );
11805         gameInfo.site = StrSave(HostName());
11806         gameInfo.date = PGNDate();
11807         if (matchGame > 0) {
11808             char buf[MSG_SIZ];
11809             sprintf(buf, "%d", matchGame);
11810             gameInfo.round = StrSave(buf);
11811         } else {
11812             gameInfo.round = StrSave("-");
11813         }
11814         if (first.twoMachinesColor[0] == 'w') {
11815             gameInfo.white = StrSave(first.tidy);
11816             gameInfo.black = StrSave(second.tidy);
11817         } else {
11818             gameInfo.white = StrSave(second.tidy);
11819             gameInfo.black = StrSave(first.tidy);
11820         }
11821         gameInfo.timeControl = TimeControlTagValue();
11822         break;
11823
11824       case EditGame:
11825         gameInfo.event = StrSave("Edited game");
11826         gameInfo.site = StrSave(HostName());
11827         gameInfo.date = PGNDate();
11828         gameInfo.round = StrSave("-");
11829         gameInfo.white = StrSave("-");
11830         gameInfo.black = StrSave("-");
11831         break;
11832
11833       case EditPosition:
11834         gameInfo.event = StrSave("Edited position");
11835         gameInfo.site = StrSave(HostName());
11836         gameInfo.date = PGNDate();
11837         gameInfo.round = StrSave("-");
11838         gameInfo.white = StrSave("-");
11839         gameInfo.black = StrSave("-");
11840         break;
11841
11842       case IcsPlayingWhite:
11843       case IcsPlayingBlack:
11844       case IcsObserving:
11845       case IcsExamining:
11846         break;
11847
11848       case PlayFromGameFile:
11849         gameInfo.event = StrSave("Game from non-PGN file");
11850         gameInfo.site = StrSave(HostName());
11851         gameInfo.date = PGNDate();
11852         gameInfo.round = StrSave("-");
11853         gameInfo.white = StrSave("?");
11854         gameInfo.black = StrSave("?");
11855         break;
11856
11857       default:
11858         break;
11859     }
11860 }
11861
11862 void
11863 ReplaceComment(index, text)
11864      int index;
11865      char *text;
11866 {
11867     int len;
11868
11869     while (*text == '\n') text++;
11870     len = strlen(text);
11871     while (len > 0 && text[len - 1] == '\n') len--;
11872
11873     if (commentList[index] != NULL)
11874       free(commentList[index]);
11875
11876     if (len == 0) {
11877         commentList[index] = NULL;
11878         return;
11879     }
11880     commentList[index] = (char *) malloc(len + 2);
11881     strncpy(commentList[index], text, len);
11882     commentList[index][len] = '\n';
11883     commentList[index][len + 1] = NULLCHAR;
11884 }
11885
11886 void
11887 CrushCRs(text)
11888      char *text;
11889 {
11890   char *p = text;
11891   char *q = text;
11892   char ch;
11893
11894   do {
11895     ch = *p++;
11896     if (ch == '\r') continue;
11897     *q++ = ch;
11898   } while (ch != '\0');
11899 }
11900
11901 void
11902 AppendComment(index, text)
11903      int index;
11904      char *text;
11905 {
11906     int oldlen, len;
11907     char *old;
11908
11909     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11910
11911     CrushCRs(text);
11912     while (*text == '\n') text++;
11913     len = strlen(text);
11914     while (len > 0 && text[len - 1] == '\n') len--;
11915
11916     if (len == 0) return;
11917
11918     if (commentList[index] != NULL) {
11919         old = commentList[index];
11920         oldlen = strlen(old);
11921         commentList[index] = (char *) malloc(oldlen + len + 2);
11922         strcpy(commentList[index], old);
11923         free(old);
11924         strncpy(&commentList[index][oldlen], text, len);
11925         commentList[index][oldlen + len] = '\n';
11926         commentList[index][oldlen + len + 1] = NULLCHAR;
11927     } else {
11928         commentList[index] = (char *) malloc(len + 2);
11929         strncpy(commentList[index], text, len);
11930         commentList[index][len] = '\n';
11931         commentList[index][len + 1] = NULLCHAR;
11932     }
11933 }
11934
11935 static char * FindStr( char * text, char * sub_text )
11936 {
11937     char * result = strstr( text, sub_text );
11938
11939     if( result != NULL ) {
11940         result += strlen( sub_text );
11941     }
11942
11943     return result;
11944 }
11945
11946 /* [AS] Try to extract PV info from PGN comment */
11947 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11948 char *GetInfoFromComment( int index, char * text )
11949 {
11950     char * sep = text;
11951
11952     if( text != NULL && index > 0 ) {
11953         int score = 0;
11954         int depth = 0;
11955         int time = -1, sec = 0, deci;
11956         char * s_eval = FindStr( text, "[%eval " );
11957         char * s_emt = FindStr( text, "[%emt " );
11958
11959         if( s_eval != NULL || s_emt != NULL ) {
11960             /* New style */
11961             char delim;
11962
11963             if( s_eval != NULL ) {
11964                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11965                     return text;
11966                 }
11967
11968                 if( delim != ']' ) {
11969                     return text;
11970                 }
11971             }
11972
11973             if( s_emt != NULL ) {
11974             }
11975         }
11976         else {
11977             /* We expect something like: [+|-]nnn.nn/dd */
11978             int score_lo = 0;
11979
11980             sep = strchr( text, '/' );
11981             if( sep == NULL || sep < (text+4) ) {
11982                 return text;
11983             }
11984
11985             time = -1; sec = -1; deci = -1;
11986             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
11987                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
11988                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
11989                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
11990                 return text;
11991             }
11992
11993             if( score_lo < 0 || score_lo >= 100 ) {
11994                 return text;
11995             }
11996
11997             if(sec >= 0) time = 600*time + 10*sec; else
11998             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
11999
12000             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12001
12002             /* [HGM] PV time: now locate end of PV info */
12003             while( *++sep >= '0' && *sep <= '9'); // strip depth
12004             if(time >= 0)
12005             while( *++sep >= '0' && *sep <= '9'); // strip time
12006             if(sec >= 0)
12007             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12008             if(deci >= 0)
12009             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12010             while(*sep == ' ') sep++;
12011         }
12012
12013         if( depth <= 0 ) {
12014             return text;
12015         }
12016
12017         if( time < 0 ) {
12018             time = -1;
12019         }
12020
12021         pvInfoList[index-1].depth = depth;
12022         pvInfoList[index-1].score = score;
12023         pvInfoList[index-1].time  = 10*time; // centi-sec
12024     }
12025     return sep;
12026 }
12027
12028 void
12029 SendToProgram(message, cps)
12030      char *message;
12031      ChessProgramState *cps;
12032 {
12033     int count, outCount, error;
12034     char buf[MSG_SIZ];
12035
12036     if (cps->pr == NULL) return;
12037     Attention(cps);
12038     
12039     if (appData.debugMode) {
12040         TimeMark now;
12041         GetTimeMark(&now);
12042         fprintf(debugFP, "%ld >%-6s: %s", 
12043                 SubtractTimeMarks(&now, &programStartTime),
12044                 cps->which, message);
12045     }
12046     
12047     count = strlen(message);
12048     outCount = OutputToProcess(cps->pr, message, count, &error);
12049     if (outCount < count && !exiting 
12050                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12051         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12052         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12053             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12054                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12055                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12056             } else {
12057                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12058             }
12059             gameInfo.resultDetails = buf;
12060         }
12061         DisplayFatalError(buf, error, 1);
12062     }
12063 }
12064
12065 void
12066 ReceiveFromProgram(isr, closure, message, count, error)
12067      InputSourceRef isr;
12068      VOIDSTAR closure;
12069      char *message;
12070      int count;
12071      int error;
12072 {
12073     char *end_str;
12074     char buf[MSG_SIZ];
12075     ChessProgramState *cps = (ChessProgramState *)closure;
12076
12077     if (isr != cps->isr) return; /* Killed intentionally */
12078     if (count <= 0) {
12079         if (count == 0) {
12080             sprintf(buf,
12081                     _("Error: %s chess program (%s) exited unexpectedly"),
12082                     cps->which, cps->program);
12083         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12084                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12085                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12086                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12087                 } else {
12088                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12089                 }
12090                 gameInfo.resultDetails = buf;
12091             }
12092             RemoveInputSource(cps->isr);
12093             DisplayFatalError(buf, 0, 1);
12094         } else {
12095             sprintf(buf,
12096                     _("Error reading from %s chess program (%s)"),
12097                     cps->which, cps->program);
12098             RemoveInputSource(cps->isr);
12099
12100             /* [AS] Program is misbehaving badly... kill it */
12101             if( count == -2 ) {
12102                 DestroyChildProcess( cps->pr, 9 );
12103                 cps->pr = NoProc;
12104             }
12105
12106             DisplayFatalError(buf, error, 1);
12107         }
12108         return;
12109     }
12110     
12111     if ((end_str = strchr(message, '\r')) != NULL)
12112       *end_str = NULLCHAR;
12113     if ((end_str = strchr(message, '\n')) != NULL)
12114       *end_str = NULLCHAR;
12115     
12116     if (appData.debugMode) {
12117         TimeMark now; int print = 1;
12118         char *quote = ""; char c; int i;
12119
12120         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12121                 char start = message[0];
12122                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12123                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12124                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12125                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12126                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12127                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12128                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12129                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12130                         { quote = "# "; print = (appData.engineComments == 2); }
12131                 message[0] = start; // restore original message
12132         }
12133         if(print) {
12134                 GetTimeMark(&now);
12135                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12136                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12137                         quote,
12138                         message);
12139         }
12140     }
12141
12142     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12143     if (appData.icsEngineAnalyze) {
12144         if (strstr(message, "whisper") != NULL ||
12145              strstr(message, "kibitz") != NULL || 
12146             strstr(message, "tellics") != NULL) return;
12147     }
12148
12149     HandleMachineMove(message, cps);
12150 }
12151
12152
12153 void
12154 SendTimeControl(cps, mps, tc, inc, sd, st)
12155      ChessProgramState *cps;
12156      int mps, inc, sd, st;
12157      long tc;
12158 {
12159     char buf[MSG_SIZ];
12160     int seconds;
12161
12162     if( timeControl_2 > 0 ) {
12163         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12164             tc = timeControl_2;
12165         }
12166     }
12167     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12168     inc /= cps->timeOdds;
12169     st  /= cps->timeOdds;
12170
12171     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12172
12173     if (st > 0) {
12174       /* Set exact time per move, normally using st command */
12175       if (cps->stKludge) {
12176         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12177         seconds = st % 60;
12178         if (seconds == 0) {
12179           sprintf(buf, "level 1 %d\n", st/60);
12180         } else {
12181           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12182         }
12183       } else {
12184         sprintf(buf, "st %d\n", st);
12185       }
12186     } else {
12187       /* Set conventional or incremental time control, using level command */
12188       if (seconds == 0) {
12189         /* Note old gnuchess bug -- minutes:seconds used to not work.
12190            Fixed in later versions, but still avoid :seconds
12191            when seconds is 0. */
12192         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12193       } else {
12194         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12195                 seconds, inc/1000);
12196       }
12197     }
12198     SendToProgram(buf, cps);
12199
12200     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12201     /* Orthogonally, limit search to given depth */
12202     if (sd > 0) {
12203       if (cps->sdKludge) {
12204         sprintf(buf, "depth\n%d\n", sd);
12205       } else {
12206         sprintf(buf, "sd %d\n", sd);
12207       }
12208       SendToProgram(buf, cps);
12209     }
12210
12211     if(cps->nps > 0) { /* [HGM] nps */
12212         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12213         else {
12214                 sprintf(buf, "nps %d\n", cps->nps);
12215               SendToProgram(buf, cps);
12216         }
12217     }
12218 }
12219
12220 ChessProgramState *WhitePlayer()
12221 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12222 {
12223     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12224        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12225         return &second;
12226     return &first;
12227 }
12228
12229 void
12230 SendTimeRemaining(cps, machineWhite)
12231      ChessProgramState *cps;
12232      int /*boolean*/ machineWhite;
12233 {
12234     char message[MSG_SIZ];
12235     long time, otime;
12236
12237     /* Note: this routine must be called when the clocks are stopped
12238        or when they have *just* been set or switched; otherwise
12239        it will be off by the time since the current tick started.
12240     */
12241     if (machineWhite) {
12242         time = whiteTimeRemaining / 10;
12243         otime = blackTimeRemaining / 10;
12244     } else {
12245         time = blackTimeRemaining / 10;
12246         otime = whiteTimeRemaining / 10;
12247     }
12248     /* [HGM] translate opponent's time by time-odds factor */
12249     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12250     if (appData.debugMode) {
12251         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12252     }
12253
12254     if (time <= 0) time = 1;
12255     if (otime <= 0) otime = 1;
12256     
12257     sprintf(message, "time %ld\n", time);
12258     SendToProgram(message, cps);
12259
12260     sprintf(message, "otim %ld\n", otime);
12261     SendToProgram(message, cps);
12262 }
12263
12264 int
12265 BoolFeature(p, name, loc, cps)
12266      char **p;
12267      char *name;
12268      int *loc;
12269      ChessProgramState *cps;
12270 {
12271   char buf[MSG_SIZ];
12272   int len = strlen(name);
12273   int val;
12274   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12275     (*p) += len + 1;
12276     sscanf(*p, "%d", &val);
12277     *loc = (val != 0);
12278     while (**p && **p != ' ') (*p)++;
12279     sprintf(buf, "accepted %s\n", name);
12280     SendToProgram(buf, cps);
12281     return TRUE;
12282   }
12283   return FALSE;
12284 }
12285
12286 int
12287 IntFeature(p, name, loc, cps)
12288      char **p;
12289      char *name;
12290      int *loc;
12291      ChessProgramState *cps;
12292 {
12293   char buf[MSG_SIZ];
12294   int len = strlen(name);
12295   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12296     (*p) += len + 1;
12297     sscanf(*p, "%d", loc);
12298     while (**p && **p != ' ') (*p)++;
12299     sprintf(buf, "accepted %s\n", name);
12300     SendToProgram(buf, cps);
12301     return TRUE;
12302   }
12303   return FALSE;
12304 }
12305
12306 int
12307 StringFeature(p, name, loc, cps)
12308      char **p;
12309      char *name;
12310      char loc[];
12311      ChessProgramState *cps;
12312 {
12313   char buf[MSG_SIZ];
12314   int len = strlen(name);
12315   if (strncmp((*p), name, len) == 0
12316       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12317     (*p) += len + 2;
12318     sscanf(*p, "%[^\"]", loc);
12319     while (**p && **p != '\"') (*p)++;
12320     if (**p == '\"') (*p)++;
12321     sprintf(buf, "accepted %s\n", name);
12322     SendToProgram(buf, cps);
12323     return TRUE;
12324   }
12325   return FALSE;
12326 }
12327
12328 int 
12329 ParseOption(Option *opt, ChessProgramState *cps)
12330 // [HGM] options: process the string that defines an engine option, and determine
12331 // name, type, default value, and allowed value range
12332 {
12333         char *p, *q, buf[MSG_SIZ];
12334         int n, min = (-1)<<31, max = 1<<31, def;
12335
12336         if(p = strstr(opt->name, " -spin ")) {
12337             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12338             if(max < min) max = min; // enforce consistency
12339             if(def < min) def = min;
12340             if(def > max) def = max;
12341             opt->value = def;
12342             opt->min = min;
12343             opt->max = max;
12344             opt->type = Spin;
12345         } else if((p = strstr(opt->name, " -slider "))) {
12346             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12347             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12348             if(max < min) max = min; // enforce consistency
12349             if(def < min) def = min;
12350             if(def > max) def = max;
12351             opt->value = def;
12352             opt->min = min;
12353             opt->max = max;
12354             opt->type = Spin; // Slider;
12355         } else if((p = strstr(opt->name, " -string "))) {
12356             opt->textValue = p+9;
12357             opt->type = TextBox;
12358         } else if((p = strstr(opt->name, " -file "))) {
12359             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12360             opt->textValue = p+7;
12361             opt->type = TextBox; // FileName;
12362         } else if((p = strstr(opt->name, " -path "))) {
12363             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12364             opt->textValue = p+7;
12365             opt->type = TextBox; // PathName;
12366         } else if(p = strstr(opt->name, " -check ")) {
12367             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12368             opt->value = (def != 0);
12369             opt->type = CheckBox;
12370         } else if(p = strstr(opt->name, " -combo ")) {
12371             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12372             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12373             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12374             opt->value = n = 0;
12375             while(q = StrStr(q, " /// ")) {
12376                 n++; *q = 0;    // count choices, and null-terminate each of them
12377                 q += 5;
12378                 if(*q == '*') { // remember default, which is marked with * prefix
12379                     q++;
12380                     opt->value = n;
12381                 }
12382                 cps->comboList[cps->comboCnt++] = q;
12383             }
12384             cps->comboList[cps->comboCnt++] = NULL;
12385             opt->max = n + 1;
12386             opt->type = ComboBox;
12387         } else if(p = strstr(opt->name, " -button")) {
12388             opt->type = Button;
12389         } else if(p = strstr(opt->name, " -save")) {
12390             opt->type = SaveButton;
12391         } else return FALSE;
12392         *p = 0; // terminate option name
12393         // now look if the command-line options define a setting for this engine option.
12394         if(cps->optionSettings && cps->optionSettings[0])
12395             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12396         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12397                 sprintf(buf, "option %s", p);
12398                 if(p = strstr(buf, ",")) *p = 0;
12399                 strcat(buf, "\n");
12400                 SendToProgram(buf, cps);
12401         }
12402         return TRUE;
12403 }
12404
12405 void
12406 FeatureDone(cps, val)
12407      ChessProgramState* cps;
12408      int val;
12409 {
12410   DelayedEventCallback cb = GetDelayedEvent();
12411   if ((cb == InitBackEnd3 && cps == &first) ||
12412       (cb == TwoMachinesEventIfReady && cps == &second)) {
12413     CancelDelayedEvent();
12414     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12415   }
12416   cps->initDone = val;
12417 }
12418
12419 /* Parse feature command from engine */
12420 void
12421 ParseFeatures(args, cps)
12422      char* args;
12423      ChessProgramState *cps;  
12424 {
12425   char *p = args;
12426   char *q;
12427   int val;
12428   char buf[MSG_SIZ];
12429
12430   for (;;) {
12431     while (*p == ' ') p++;
12432     if (*p == NULLCHAR) return;
12433
12434     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12435     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12436     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12437     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12438     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12439     if (BoolFeature(&p, "reuse", &val, cps)) {
12440       /* Engine can disable reuse, but can't enable it if user said no */
12441       if (!val) cps->reuse = FALSE;
12442       continue;
12443     }
12444     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12445     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12446       if (gameMode == TwoMachinesPlay) {
12447         DisplayTwoMachinesTitle();
12448       } else {
12449         DisplayTitle("");
12450       }
12451       continue;
12452     }
12453     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12454     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12455     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12456     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12457     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12458     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12459     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12460     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12461     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12462     if (IntFeature(&p, "done", &val, cps)) {
12463       FeatureDone(cps, val);
12464       continue;
12465     }
12466     /* Added by Tord: */
12467     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12468     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12469     /* End of additions by Tord */
12470
12471     /* [HGM] added features: */
12472     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12473     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12474     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12475     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12476     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12477     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12478     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12479         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12480             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12481             SendToProgram(buf, cps);
12482             continue;
12483         }
12484         if(cps->nrOptions >= MAX_OPTIONS) {
12485             cps->nrOptions--;
12486             sprintf(buf, "%s engine has too many options\n", cps->which);
12487             DisplayError(buf, 0);
12488         }
12489         continue;
12490     }
12491     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12492     /* End of additions by HGM */
12493
12494     /* unknown feature: complain and skip */
12495     q = p;
12496     while (*q && *q != '=') q++;
12497     sprintf(buf, "rejected %.*s\n", q-p, p);
12498     SendToProgram(buf, cps);
12499     p = q;
12500     if (*p == '=') {
12501       p++;
12502       if (*p == '\"') {
12503         p++;
12504         while (*p && *p != '\"') p++;
12505         if (*p == '\"') p++;
12506       } else {
12507         while (*p && *p != ' ') p++;
12508       }
12509     }
12510   }
12511
12512 }
12513
12514 void
12515 PeriodicUpdatesEvent(newState)
12516      int newState;
12517 {
12518     if (newState == appData.periodicUpdates)
12519       return;
12520
12521     appData.periodicUpdates=newState;
12522
12523     /* Display type changes, so update it now */
12524     DisplayAnalysis();
12525
12526     /* Get the ball rolling again... */
12527     if (newState) {
12528         AnalysisPeriodicEvent(1);
12529         StartAnalysisClock();
12530     }
12531 }
12532
12533 void
12534 PonderNextMoveEvent(newState)
12535      int newState;
12536 {
12537     if (newState == appData.ponderNextMove) return;
12538     if (gameMode == EditPosition) EditPositionDone();
12539     if (newState) {
12540         SendToProgram("hard\n", &first);
12541         if (gameMode == TwoMachinesPlay) {
12542             SendToProgram("hard\n", &second);
12543         }
12544     } else {
12545         SendToProgram("easy\n", &first);
12546         thinkOutput[0] = NULLCHAR;
12547         if (gameMode == TwoMachinesPlay) {
12548             SendToProgram("easy\n", &second);
12549         }
12550     }
12551     appData.ponderNextMove = newState;
12552 }
12553
12554 void
12555 NewSettingEvent(option, command, value)
12556      char *command;
12557      int option, value;
12558 {
12559     char buf[MSG_SIZ];
12560
12561     if (gameMode == EditPosition) EditPositionDone();
12562     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12563     SendToProgram(buf, &first);
12564     if (gameMode == TwoMachinesPlay) {
12565         SendToProgram(buf, &second);
12566     }
12567 }
12568
12569 void
12570 ShowThinkingEvent()
12571 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12572 {
12573     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12574     int newState = appData.showThinking
12575         // [HGM] thinking: other features now need thinking output as well
12576         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12577     
12578     if (oldState == newState) return;
12579     oldState = newState;
12580     if (gameMode == EditPosition) EditPositionDone();
12581     if (oldState) {
12582         SendToProgram("post\n", &first);
12583         if (gameMode == TwoMachinesPlay) {
12584             SendToProgram("post\n", &second);
12585         }
12586     } else {
12587         SendToProgram("nopost\n", &first);
12588         thinkOutput[0] = NULLCHAR;
12589         if (gameMode == TwoMachinesPlay) {
12590             SendToProgram("nopost\n", &second);
12591         }
12592     }
12593 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12594 }
12595
12596 void
12597 AskQuestionEvent(title, question, replyPrefix, which)
12598      char *title; char *question; char *replyPrefix; char *which;
12599 {
12600   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12601   if (pr == NoProc) return;
12602   AskQuestion(title, question, replyPrefix, pr);
12603 }
12604
12605 void
12606 DisplayMove(moveNumber)
12607      int moveNumber;
12608 {
12609     char message[MSG_SIZ];
12610     char res[MSG_SIZ];
12611     char cpThinkOutput[MSG_SIZ];
12612
12613     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12614     
12615     if (moveNumber == forwardMostMove - 1 || 
12616         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12617
12618         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12619
12620         if (strchr(cpThinkOutput, '\n')) {
12621             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12622         }
12623     } else {
12624         *cpThinkOutput = NULLCHAR;
12625     }
12626
12627     /* [AS] Hide thinking from human user */
12628     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12629         *cpThinkOutput = NULLCHAR;
12630         if( thinkOutput[0] != NULLCHAR ) {
12631             int i;
12632
12633             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12634                 cpThinkOutput[i] = '.';
12635             }
12636             cpThinkOutput[i] = NULLCHAR;
12637             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12638         }
12639     }
12640
12641     if (moveNumber == forwardMostMove - 1 &&
12642         gameInfo.resultDetails != NULL) {
12643         if (gameInfo.resultDetails[0] == NULLCHAR) {
12644             sprintf(res, " %s", PGNResult(gameInfo.result));
12645         } else {
12646             sprintf(res, " {%s} %s",
12647                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12648         }
12649     } else {
12650         res[0] = NULLCHAR;
12651     }
12652
12653     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12654         DisplayMessage(res, cpThinkOutput);
12655     } else {
12656         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12657                 WhiteOnMove(moveNumber) ? " " : ".. ",
12658                 parseList[moveNumber], res);
12659         DisplayMessage(message, cpThinkOutput);
12660     }
12661 }
12662
12663 void
12664 DisplayAnalysisText(text)
12665      char *text;
12666 {
12667     char buf[MSG_SIZ];
12668
12669     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12670                || appData.icsEngineAnalyze) {
12671         sprintf(buf, "Analysis (%s)", first.tidy);
12672         AnalysisPopUp(buf, text);
12673     }
12674 }
12675
12676 static int
12677 only_one_move(str)
12678      char *str;
12679 {
12680     while (*str && isspace(*str)) ++str;
12681     while (*str && !isspace(*str)) ++str;
12682     if (!*str) return 1;
12683     while (*str && isspace(*str)) ++str;
12684     if (!*str) return 1;
12685     return 0;
12686 }
12687
12688 void
12689 DisplayAnalysis()
12690 {
12691     char buf[MSG_SIZ];
12692     char lst[MSG_SIZ / 2];
12693     double nps;
12694     static char *xtra[] = { "", " (--)", " (++)" };
12695     int h, m, s, cs;
12696   
12697     if (programStats.time == 0) {
12698         programStats.time = 1;
12699     }
12700   
12701     if (programStats.got_only_move) {
12702         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12703     } else {
12704         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12705
12706         nps = (u64ToDouble(programStats.nodes) /
12707              ((double)programStats.time /100.0));
12708
12709         cs = programStats.time % 100;
12710         s = programStats.time / 100;
12711         h = (s / (60*60));
12712         s = s - h*60*60;
12713         m = (s/60);
12714         s = s - m*60;
12715
12716         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12717           if (programStats.move_name[0] != NULLCHAR) {
12718             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12719                     programStats.depth,
12720                     programStats.nr_moves-programStats.moves_left,
12721                     programStats.nr_moves, programStats.move_name,
12722                     ((float)programStats.score)/100.0, lst,
12723                     only_one_move(lst)?
12724                     xtra[programStats.got_fail] : "",
12725                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12726           } else {
12727             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12728                     programStats.depth,
12729                     programStats.nr_moves-programStats.moves_left,
12730                     programStats.nr_moves, ((float)programStats.score)/100.0,
12731                     lst,
12732                     only_one_move(lst)?
12733                     xtra[programStats.got_fail] : "",
12734                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12735           }
12736         } else {
12737             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12738                     programStats.depth,
12739                     ((float)programStats.score)/100.0,
12740                     lst,
12741                     only_one_move(lst)?
12742                     xtra[programStats.got_fail] : "",
12743                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12744         }
12745     }
12746     DisplayAnalysisText(buf);
12747 }
12748
12749 void
12750 DisplayComment(moveNumber, text)
12751      int moveNumber;
12752      char *text;
12753 {
12754     char title[MSG_SIZ];
12755     char buf[8000]; // comment can be long!
12756     int score, depth;
12757
12758     if( appData.autoDisplayComment ) {
12759         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12760             strcpy(title, "Comment");
12761         } else {
12762             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12763                     WhiteOnMove(moveNumber) ? " " : ".. ",
12764                     parseList[moveNumber]);
12765         }
12766         // [HGM] PV info: display PV info together with (or as) comment
12767         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12768             if(text == NULL) text = "";                                           
12769             score = pvInfoList[moveNumber].score;
12770             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12771                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12772             text = buf;
12773         }
12774     } else title[0] = 0;
12775
12776     if (text != NULL)
12777         CommentPopUp(title, text);
12778 }
12779
12780 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12781  * might be busy thinking or pondering.  It can be omitted if your
12782  * gnuchess is configured to stop thinking immediately on any user
12783  * input.  However, that gnuchess feature depends on the FIONREAD
12784  * ioctl, which does not work properly on some flavors of Unix.
12785  */
12786 void
12787 Attention(cps)
12788      ChessProgramState *cps;
12789 {
12790 #if ATTENTION
12791     if (!cps->useSigint) return;
12792     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12793     switch (gameMode) {
12794       case MachinePlaysWhite:
12795       case MachinePlaysBlack:
12796       case TwoMachinesPlay:
12797       case IcsPlayingWhite:
12798       case IcsPlayingBlack:
12799       case AnalyzeMode:
12800       case AnalyzeFile:
12801         /* Skip if we know it isn't thinking */
12802         if (!cps->maybeThinking) return;
12803         if (appData.debugMode)
12804           fprintf(debugFP, "Interrupting %s\n", cps->which);
12805         InterruptChildProcess(cps->pr);
12806         cps->maybeThinking = FALSE;
12807         break;
12808       default:
12809         break;
12810     }
12811 #endif /*ATTENTION*/
12812 }
12813
12814 int
12815 CheckFlags()
12816 {
12817     if (whiteTimeRemaining <= 0) {
12818         if (!whiteFlag) {
12819             whiteFlag = TRUE;
12820             if (appData.icsActive) {
12821                 if (appData.autoCallFlag &&
12822                     gameMode == IcsPlayingBlack && !blackFlag) {
12823                   SendToICS(ics_prefix);
12824                   SendToICS("flag\n");
12825                 }
12826             } else {
12827                 if (blackFlag) {
12828                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12829                 } else {
12830                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12831                     if (appData.autoCallFlag) {
12832                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12833                         return TRUE;
12834                     }
12835                 }
12836             }
12837         }
12838     }
12839     if (blackTimeRemaining <= 0) {
12840         if (!blackFlag) {
12841             blackFlag = TRUE;
12842             if (appData.icsActive) {
12843                 if (appData.autoCallFlag &&
12844                     gameMode == IcsPlayingWhite && !whiteFlag) {
12845                   SendToICS(ics_prefix);
12846                   SendToICS("flag\n");
12847                 }
12848             } else {
12849                 if (whiteFlag) {
12850                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12851                 } else {
12852                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12853                     if (appData.autoCallFlag) {
12854                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12855                         return TRUE;
12856                     }
12857                 }
12858             }
12859         }
12860     }
12861     return FALSE;
12862 }
12863
12864 void
12865 CheckTimeControl()
12866 {
12867     if (!appData.clockMode || appData.icsActive ||
12868         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12869
12870     /*
12871      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12872      */
12873     if ( !WhiteOnMove(forwardMostMove) )
12874         /* White made time control */
12875         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12876         /* [HGM] time odds: correct new time quota for time odds! */
12877                                             / WhitePlayer()->timeOdds;
12878       else
12879         /* Black made time control */
12880         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12881                                             / WhitePlayer()->other->timeOdds;
12882 }
12883
12884 void
12885 DisplayBothClocks()
12886 {
12887     int wom = gameMode == EditPosition ?
12888       !blackPlaysFirst : WhiteOnMove(currentMove);
12889     DisplayWhiteClock(whiteTimeRemaining, wom);
12890     DisplayBlackClock(blackTimeRemaining, !wom);
12891 }
12892
12893
12894 /* Timekeeping seems to be a portability nightmare.  I think everyone
12895    has ftime(), but I'm really not sure, so I'm including some ifdefs
12896    to use other calls if you don't.  Clocks will be less accurate if
12897    you have neither ftime nor gettimeofday.
12898 */
12899
12900 /* VS 2008 requires the #include outside of the function */
12901 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12902 #include <sys/timeb.h>
12903 #endif
12904
12905 /* Get the current time as a TimeMark */
12906 void
12907 GetTimeMark(tm)
12908      TimeMark *tm;
12909 {
12910 #if HAVE_GETTIMEOFDAY
12911
12912     struct timeval timeVal;
12913     struct timezone timeZone;
12914
12915     gettimeofday(&timeVal, &timeZone);
12916     tm->sec = (long) timeVal.tv_sec; 
12917     tm->ms = (int) (timeVal.tv_usec / 1000L);
12918
12919 #else /*!HAVE_GETTIMEOFDAY*/
12920 #if HAVE_FTIME
12921
12922 // include <sys/timeb.h> / moved to just above start of function
12923     struct timeb timeB;
12924
12925     ftime(&timeB);
12926     tm->sec = (long) timeB.time;
12927     tm->ms = (int) timeB.millitm;
12928
12929 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12930     tm->sec = (long) time(NULL);
12931     tm->ms = 0;
12932 #endif
12933 #endif
12934 }
12935
12936 /* Return the difference in milliseconds between two
12937    time marks.  We assume the difference will fit in a long!
12938 */
12939 long
12940 SubtractTimeMarks(tm2, tm1)
12941      TimeMark *tm2, *tm1;
12942 {
12943     return 1000L*(tm2->sec - tm1->sec) +
12944            (long) (tm2->ms - tm1->ms);
12945 }
12946
12947
12948 /*
12949  * Code to manage the game clocks.
12950  *
12951  * In tournament play, black starts the clock and then white makes a move.
12952  * We give the human user a slight advantage if he is playing white---the
12953  * clocks don't run until he makes his first move, so it takes zero time.
12954  * Also, we don't account for network lag, so we could get out of sync
12955  * with GNU Chess's clock -- but then, referees are always right.  
12956  */
12957
12958 static TimeMark tickStartTM;
12959 static long intendedTickLength;
12960
12961 long
12962 NextTickLength(timeRemaining)
12963      long timeRemaining;
12964 {
12965     long nominalTickLength, nextTickLength;
12966
12967     if (timeRemaining > 0L && timeRemaining <= 10000L)
12968       nominalTickLength = 100L;
12969     else
12970       nominalTickLength = 1000L;
12971     nextTickLength = timeRemaining % nominalTickLength;
12972     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12973
12974     return nextTickLength;
12975 }
12976
12977 /* Adjust clock one minute up or down */
12978 void
12979 AdjustClock(Boolean which, int dir)
12980 {
12981     if(which) blackTimeRemaining += 60000*dir;
12982     else      whiteTimeRemaining += 60000*dir;
12983     DisplayBothClocks();
12984 }
12985
12986 /* Stop clocks and reset to a fresh time control */
12987 void
12988 ResetClocks() 
12989 {
12990     (void) StopClockTimer();
12991     if (appData.icsActive) {
12992         whiteTimeRemaining = blackTimeRemaining = 0;
12993     } else { /* [HGM] correct new time quote for time odds */
12994         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
12995         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
12996     }
12997     if (whiteFlag || blackFlag) {
12998         DisplayTitle("");
12999         whiteFlag = blackFlag = FALSE;
13000     }
13001     DisplayBothClocks();
13002 }
13003
13004 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13005
13006 /* Decrement running clock by amount of time that has passed */
13007 void
13008 DecrementClocks()
13009 {
13010     long timeRemaining;
13011     long lastTickLength, fudge;
13012     TimeMark now;
13013
13014     if (!appData.clockMode) return;
13015     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13016         
13017     GetTimeMark(&now);
13018
13019     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13020
13021     /* Fudge if we woke up a little too soon */
13022     fudge = intendedTickLength - lastTickLength;
13023     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13024
13025     if (WhiteOnMove(forwardMostMove)) {
13026         if(whiteNPS >= 0) lastTickLength = 0;
13027         timeRemaining = whiteTimeRemaining -= lastTickLength;
13028         DisplayWhiteClock(whiteTimeRemaining - fudge,
13029                           WhiteOnMove(currentMove));
13030     } else {
13031         if(blackNPS >= 0) lastTickLength = 0;
13032         timeRemaining = blackTimeRemaining -= lastTickLength;
13033         DisplayBlackClock(blackTimeRemaining - fudge,
13034                           !WhiteOnMove(currentMove));
13035     }
13036
13037     if (CheckFlags()) return;
13038         
13039     tickStartTM = now;
13040     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13041     StartClockTimer(intendedTickLength);
13042
13043     /* if the time remaining has fallen below the alarm threshold, sound the
13044      * alarm. if the alarm has sounded and (due to a takeback or time control
13045      * with increment) the time remaining has increased to a level above the
13046      * threshold, reset the alarm so it can sound again. 
13047      */
13048     
13049     if (appData.icsActive && appData.icsAlarm) {
13050
13051         /* make sure we are dealing with the user's clock */
13052         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13053                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13054            )) return;
13055
13056         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13057             alarmSounded = FALSE;
13058         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13059             PlayAlarmSound();
13060             alarmSounded = TRUE;
13061         }
13062     }
13063 }
13064
13065
13066 /* A player has just moved, so stop the previously running
13067    clock and (if in clock mode) start the other one.
13068    We redisplay both clocks in case we're in ICS mode, because
13069    ICS gives us an update to both clocks after every move.
13070    Note that this routine is called *after* forwardMostMove
13071    is updated, so the last fractional tick must be subtracted
13072    from the color that is *not* on move now.
13073 */
13074 void
13075 SwitchClocks()
13076 {
13077     long lastTickLength;
13078     TimeMark now;
13079     int flagged = FALSE;
13080
13081     GetTimeMark(&now);
13082
13083     if (StopClockTimer() && appData.clockMode) {
13084         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13085         if (WhiteOnMove(forwardMostMove)) {
13086             if(blackNPS >= 0) lastTickLength = 0;
13087             blackTimeRemaining -= lastTickLength;
13088            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13089 //         if(pvInfoList[forwardMostMove-1].time == -1)
13090                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13091                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13092         } else {
13093            if(whiteNPS >= 0) lastTickLength = 0;
13094            whiteTimeRemaining -= lastTickLength;
13095            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13096 //         if(pvInfoList[forwardMostMove-1].time == -1)
13097                  pvInfoList[forwardMostMove-1].time = 
13098                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13099         }
13100         flagged = CheckFlags();
13101     }
13102     CheckTimeControl();
13103
13104     if (flagged || !appData.clockMode) return;
13105
13106     switch (gameMode) {
13107       case MachinePlaysBlack:
13108       case MachinePlaysWhite:
13109       case BeginningOfGame:
13110         if (pausing) return;
13111         break;
13112
13113       case EditGame:
13114       case PlayFromGameFile:
13115       case IcsExamining:
13116         return;
13117
13118       default:
13119         break;
13120     }
13121
13122     tickStartTM = now;
13123     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13124       whiteTimeRemaining : blackTimeRemaining);
13125     StartClockTimer(intendedTickLength);
13126 }
13127         
13128
13129 /* Stop both clocks */
13130 void
13131 StopClocks()
13132 {       
13133     long lastTickLength;
13134     TimeMark now;
13135
13136     if (!StopClockTimer()) return;
13137     if (!appData.clockMode) return;
13138
13139     GetTimeMark(&now);
13140
13141     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13142     if (WhiteOnMove(forwardMostMove)) {
13143         if(whiteNPS >= 0) lastTickLength = 0;
13144         whiteTimeRemaining -= lastTickLength;
13145         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13146     } else {
13147         if(blackNPS >= 0) lastTickLength = 0;
13148         blackTimeRemaining -= lastTickLength;
13149         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13150     }
13151     CheckFlags();
13152 }
13153         
13154 /* Start clock of player on move.  Time may have been reset, so
13155    if clock is already running, stop and restart it. */
13156 void
13157 StartClocks()
13158 {
13159     (void) StopClockTimer(); /* in case it was running already */
13160     DisplayBothClocks();
13161     if (CheckFlags()) return;
13162
13163     if (!appData.clockMode) return;
13164     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13165
13166     GetTimeMark(&tickStartTM);
13167     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13168       whiteTimeRemaining : blackTimeRemaining);
13169
13170    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13171     whiteNPS = blackNPS = -1; 
13172     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13173        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13174         whiteNPS = first.nps;
13175     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13176        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13177         blackNPS = first.nps;
13178     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13179         whiteNPS = second.nps;
13180     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13181         blackNPS = second.nps;
13182     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13183
13184     StartClockTimer(intendedTickLength);
13185 }
13186
13187 char *
13188 TimeString(ms)
13189      long ms;
13190 {
13191     long second, minute, hour, day;
13192     char *sign = "";
13193     static char buf[32];
13194     
13195     if (ms > 0 && ms <= 9900) {
13196       /* convert milliseconds to tenths, rounding up */
13197       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13198
13199       sprintf(buf, " %03.1f ", tenths/10.0);
13200       return buf;
13201     }
13202
13203     /* convert milliseconds to seconds, rounding up */
13204     /* use floating point to avoid strangeness of integer division
13205        with negative dividends on many machines */
13206     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13207
13208     if (second < 0) {
13209         sign = "-";
13210         second = -second;
13211     }
13212     
13213     day = second / (60 * 60 * 24);
13214     second = second % (60 * 60 * 24);
13215     hour = second / (60 * 60);
13216     second = second % (60 * 60);
13217     minute = second / 60;
13218     second = second % 60;
13219     
13220     if (day > 0)
13221       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13222               sign, day, hour, minute, second);
13223     else if (hour > 0)
13224       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13225     else
13226       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13227     
13228     return buf;
13229 }
13230
13231
13232 /*
13233  * This is necessary because some C libraries aren't ANSI C compliant yet.
13234  */
13235 char *
13236 StrStr(string, match)
13237      char *string, *match;
13238 {
13239     int i, length;
13240     
13241     length = strlen(match);
13242     
13243     for (i = strlen(string) - length; i >= 0; i--, string++)
13244       if (!strncmp(match, string, length))
13245         return string;
13246     
13247     return NULL;
13248 }
13249
13250 char *
13251 StrCaseStr(string, match)
13252      char *string, *match;
13253 {
13254     int i, j, length;
13255     
13256     length = strlen(match);
13257     
13258     for (i = strlen(string) - length; i >= 0; i--, string++) {
13259         for (j = 0; j < length; j++) {
13260             if (ToLower(match[j]) != ToLower(string[j]))
13261               break;
13262         }
13263         if (j == length) return string;
13264     }
13265
13266     return NULL;
13267 }
13268
13269 #ifndef _amigados
13270 int
13271 StrCaseCmp(s1, s2)
13272      char *s1, *s2;
13273 {
13274     char c1, c2;
13275     
13276     for (;;) {
13277         c1 = ToLower(*s1++);
13278         c2 = ToLower(*s2++);
13279         if (c1 > c2) return 1;
13280         if (c1 < c2) return -1;
13281         if (c1 == NULLCHAR) return 0;
13282     }
13283 }
13284
13285
13286 int
13287 ToLower(c)
13288      int c;
13289 {
13290     return isupper(c) ? tolower(c) : c;
13291 }
13292
13293
13294 int
13295 ToUpper(c)
13296      int c;
13297 {
13298     return islower(c) ? toupper(c) : c;
13299 }
13300 #endif /* !_amigados    */
13301
13302 char *
13303 StrSave(s)
13304      char *s;
13305 {
13306     char *ret;
13307
13308     if ((ret = (char *) malloc(strlen(s) + 1))) {
13309         strcpy(ret, s);
13310     }
13311     return ret;
13312 }
13313
13314 char *
13315 StrSavePtr(s, savePtr)
13316      char *s, **savePtr;
13317 {
13318     if (*savePtr) {
13319         free(*savePtr);
13320     }
13321     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13322         strcpy(*savePtr, s);
13323     }
13324     return(*savePtr);
13325 }
13326
13327 char *
13328 PGNDate()
13329 {
13330     time_t clock;
13331     struct tm *tm;
13332     char buf[MSG_SIZ];
13333
13334     clock = time((time_t *)NULL);
13335     tm = localtime(&clock);
13336     sprintf(buf, "%04d.%02d.%02d",
13337             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13338     return StrSave(buf);
13339 }
13340
13341
13342 char *
13343 PositionToFEN(move, overrideCastling)
13344      int move;
13345      char *overrideCastling;
13346 {
13347     int i, j, fromX, fromY, toX, toY;
13348     int whiteToPlay;
13349     char buf[128];
13350     char *p, *q;
13351     int emptycount;
13352     ChessSquare piece;
13353
13354     whiteToPlay = (gameMode == EditPosition) ?
13355       !blackPlaysFirst : (move % 2 == 0);
13356     p = buf;
13357
13358     /* Piece placement data */
13359     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13360         emptycount = 0;
13361         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13362             if (boards[move][i][j] == EmptySquare) {
13363                 emptycount++;
13364             } else { ChessSquare piece = boards[move][i][j];
13365                 if (emptycount > 0) {
13366                     if(emptycount<10) /* [HGM] can be >= 10 */
13367                         *p++ = '0' + emptycount;
13368                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13369                     emptycount = 0;
13370                 }
13371                 if(PieceToChar(piece) == '+') {
13372                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13373                     *p++ = '+';
13374                     piece = (ChessSquare)(DEMOTED piece);
13375                 } 
13376                 *p++ = PieceToChar(piece);
13377                 if(p[-1] == '~') {
13378                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13379                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13380                     *p++ = '~';
13381                 }
13382             }
13383         }
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         *p++ = '/';
13391     }
13392     *(p - 1) = ' ';
13393
13394     /* [HGM] print Crazyhouse or Shogi holdings */
13395     if( gameInfo.holdingsWidth ) {
13396         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13397         q = p;
13398         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13399             piece = boards[move][i][BOARD_WIDTH-1];
13400             if( piece != EmptySquare )
13401               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13402                   *p++ = PieceToChar(piece);
13403         }
13404         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13405             piece = boards[move][BOARD_HEIGHT-i-1][0];
13406             if( piece != EmptySquare )
13407               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13408                   *p++ = PieceToChar(piece);
13409         }
13410
13411         if( q == p ) *p++ = '-';
13412         *p++ = ']';
13413         *p++ = ' ';
13414     }
13415
13416     /* Active color */
13417     *p++ = whiteToPlay ? 'w' : 'b';
13418     *p++ = ' ';
13419
13420   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13421     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13422   } else {
13423   if(nrCastlingRights) {
13424      q = p;
13425      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13426        /* [HGM] write directly from rights */
13427            if(castlingRights[move][2] >= 0 &&
13428               castlingRights[move][0] >= 0   )
13429                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13430            if(castlingRights[move][2] >= 0 &&
13431               castlingRights[move][1] >= 0   )
13432                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13433            if(castlingRights[move][5] >= 0 &&
13434               castlingRights[move][3] >= 0   )
13435                 *p++ = castlingRights[move][3] + AAA;
13436            if(castlingRights[move][5] >= 0 &&
13437               castlingRights[move][4] >= 0   )
13438                 *p++ = castlingRights[move][4] + AAA;
13439      } else {
13440
13441         /* [HGM] write true castling rights */
13442         if( nrCastlingRights == 6 ) {
13443             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13444                castlingRights[move][2] >= 0  ) *p++ = 'K';
13445             if(castlingRights[move][1] == BOARD_LEFT &&
13446                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13447             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13448                castlingRights[move][5] >= 0  ) *p++ = 'k';
13449             if(castlingRights[move][4] == BOARD_LEFT &&
13450                castlingRights[move][5] >= 0  ) *p++ = 'q';
13451         }
13452      }
13453      if (q == p) *p++ = '-'; /* No castling rights */
13454      *p++ = ' ';
13455   }
13456
13457   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13458      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13459     /* En passant target square */
13460     if (move > backwardMostMove) {
13461         fromX = moveList[move - 1][0] - AAA;
13462         fromY = moveList[move - 1][1] - ONE;
13463         toX = moveList[move - 1][2] - AAA;
13464         toY = moveList[move - 1][3] - ONE;
13465         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13466             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13467             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13468             fromX == toX) {
13469             /* 2-square pawn move just happened */
13470             *p++ = toX + AAA;
13471             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13472         } else {
13473             *p++ = '-';
13474         }
13475     } else {
13476         *p++ = '-';
13477     }
13478     *p++ = ' ';
13479   }
13480   }
13481
13482     /* [HGM] find reversible plies */
13483     {   int i = 0, j=move;
13484
13485         if (appData.debugMode) { int k;
13486             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13487             for(k=backwardMostMove; k<=forwardMostMove; k++)
13488                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13489
13490         }
13491
13492         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13493         if( j == backwardMostMove ) i += initialRulePlies;
13494         sprintf(p, "%d ", i);
13495         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13496     }
13497     /* Fullmove number */
13498     sprintf(p, "%d", (move / 2) + 1);
13499     
13500     return StrSave(buf);
13501 }
13502
13503 Boolean
13504 ParseFEN(board, blackPlaysFirst, fen)
13505     Board board;
13506      int *blackPlaysFirst;
13507      char *fen;
13508 {
13509     int i, j;
13510     char *p;
13511     int emptycount;
13512     ChessSquare piece;
13513
13514     p = fen;
13515
13516     /* [HGM] by default clear Crazyhouse holdings, if present */
13517     if(gameInfo.holdingsWidth) {
13518        for(i=0; i<BOARD_HEIGHT; i++) {
13519            board[i][0]             = EmptySquare; /* black holdings */
13520            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13521            board[i][1]             = (ChessSquare) 0; /* black counts */
13522            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13523        }
13524     }
13525
13526     /* Piece placement data */
13527     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13528         j = 0;
13529         for (;;) {
13530             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13531                 if (*p == '/') p++;
13532                 emptycount = gameInfo.boardWidth - j;
13533                 while (emptycount--)
13534                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13535                 break;
13536 #if(BOARD_SIZE >= 10)
13537             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13538                 p++; emptycount=10;
13539                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13540                 while (emptycount--)
13541                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13542 #endif
13543             } else if (isdigit(*p)) {
13544                 emptycount = *p++ - '0';
13545                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13546                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13547                 while (emptycount--)
13548                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13549             } else if (*p == '+' || isalpha(*p)) {
13550                 if (j >= gameInfo.boardWidth) return FALSE;
13551                 if(*p=='+') {
13552                     piece = CharToPiece(*++p);
13553                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13554                     piece = (ChessSquare) (PROMOTED piece ); p++;
13555                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13556                 } else piece = CharToPiece(*p++);
13557
13558                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13559                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13560                     piece = (ChessSquare) (PROMOTED piece);
13561                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13562                     p++;
13563                 }
13564                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13565             } else {
13566                 return FALSE;
13567             }
13568         }
13569     }
13570     while (*p == '/' || *p == ' ') p++;
13571
13572     /* [HGM] look for Crazyhouse holdings here */
13573     while(*p==' ') p++;
13574     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13575         if(*p == '[') p++;
13576         if(*p == '-' ) *p++; /* empty holdings */ else {
13577             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13578             /* if we would allow FEN reading to set board size, we would   */
13579             /* have to add holdings and shift the board read so far here   */
13580             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13581                 *p++;
13582                 if((int) piece >= (int) BlackPawn ) {
13583                     i = (int)piece - (int)BlackPawn;
13584                     i = PieceToNumber((ChessSquare)i);
13585                     if( i >= gameInfo.holdingsSize ) return FALSE;
13586                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13587                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13588                 } else {
13589                     i = (int)piece - (int)WhitePawn;
13590                     i = PieceToNumber((ChessSquare)i);
13591                     if( i >= gameInfo.holdingsSize ) return FALSE;
13592                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13593                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13594                 }
13595             }
13596         }
13597         if(*p == ']') *p++;
13598     }
13599
13600     while(*p == ' ') p++;
13601
13602     /* Active color */
13603     switch (*p++) {
13604       case 'w':
13605         *blackPlaysFirst = FALSE;
13606         break;
13607       case 'b': 
13608         *blackPlaysFirst = TRUE;
13609         break;
13610       default:
13611         return FALSE;
13612     }
13613
13614     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13615     /* return the extra info in global variiables             */
13616
13617     /* set defaults in case FEN is incomplete */
13618     FENepStatus = EP_UNKNOWN;
13619     for(i=0; i<nrCastlingRights; i++ ) {
13620         FENcastlingRights[i] =
13621             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13622     }   /* assume possible unless obviously impossible */
13623     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13624     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13625     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13626     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13627     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13628     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13629     FENrulePlies = 0;
13630
13631     while(*p==' ') p++;
13632     if(nrCastlingRights) {
13633       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13634           /* castling indicator present, so default becomes no castlings */
13635           for(i=0; i<nrCastlingRights; i++ ) {
13636                  FENcastlingRights[i] = -1;
13637           }
13638       }
13639       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13640              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13641              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13642              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13643         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13644
13645         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13646             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13647             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13648         }
13649         switch(c) {
13650           case'K':
13651               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13652               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13653               FENcastlingRights[2] = whiteKingFile;
13654               break;
13655           case'Q':
13656               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13657               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13658               FENcastlingRights[2] = whiteKingFile;
13659               break;
13660           case'k':
13661               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13662               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13663               FENcastlingRights[5] = blackKingFile;
13664               break;
13665           case'q':
13666               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13667               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13668               FENcastlingRights[5] = blackKingFile;
13669           case '-':
13670               break;
13671           default: /* FRC castlings */
13672               if(c >= 'a') { /* black rights */
13673                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13674                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13675                   if(i == BOARD_RGHT) break;
13676                   FENcastlingRights[5] = i;
13677                   c -= AAA;
13678                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13679                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13680                   if(c > i)
13681                       FENcastlingRights[3] = c;
13682                   else
13683                       FENcastlingRights[4] = c;
13684               } else { /* white rights */
13685                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13686                     if(board[0][i] == WhiteKing) break;
13687                   if(i == BOARD_RGHT) break;
13688                   FENcastlingRights[2] = i;
13689                   c -= AAA - 'a' + 'A';
13690                   if(board[0][c] >= WhiteKing) break;
13691                   if(c > i)
13692                       FENcastlingRights[0] = c;
13693                   else
13694                       FENcastlingRights[1] = c;
13695               }
13696         }
13697       }
13698     if (appData.debugMode) {
13699         fprintf(debugFP, "FEN castling rights:");
13700         for(i=0; i<nrCastlingRights; i++)
13701         fprintf(debugFP, " %d", FENcastlingRights[i]);
13702         fprintf(debugFP, "\n");
13703     }
13704
13705       while(*p==' ') p++;
13706     }
13707
13708     /* read e.p. field in games that know e.p. capture */
13709     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13710        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13711       if(*p=='-') {
13712         p++; FENepStatus = EP_NONE;
13713       } else {
13714          char c = *p++ - AAA;
13715
13716          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13717          if(*p >= '0' && *p <='9') *p++;
13718          FENepStatus = c;
13719       }
13720     }
13721
13722
13723     if(sscanf(p, "%d", &i) == 1) {
13724         FENrulePlies = i; /* 50-move ply counter */
13725         /* (The move number is still ignored)    */
13726     }
13727
13728     return TRUE;
13729 }
13730       
13731 void
13732 EditPositionPasteFEN(char *fen)
13733 {
13734   if (fen != NULL) {
13735     Board initial_position;
13736
13737     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13738       DisplayError(_("Bad FEN position in clipboard"), 0);
13739       return ;
13740     } else {
13741       int savedBlackPlaysFirst = blackPlaysFirst;
13742       EditPositionEvent();
13743       blackPlaysFirst = savedBlackPlaysFirst;
13744       CopyBoard(boards[0], initial_position);
13745           /* [HGM] copy FEN attributes as well */
13746           {   int i;
13747               initialRulePlies = FENrulePlies;
13748               epStatus[0] = FENepStatus;
13749               for( i=0; i<nrCastlingRights; i++ )
13750                   castlingRights[0][i] = FENcastlingRights[i];
13751           }
13752       EditPositionDone();
13753       DisplayBothClocks();
13754       DrawPosition(FALSE, boards[currentMove]);
13755     }
13756   }
13757 }