Fixed joiner detection, allowing it to work with timeseal
[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 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                   Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((void));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom: 
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /* 
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  epStatus[MAX_MOVES];
445 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int   initialRulePlies, FENrulePlies;
450 char  FENepStatus;
451 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
452 int loadFlag = 0; 
453 int shuffleOpenings;
454 int mute; // mute all sounds
455
456 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
457     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460         BlackKing, BlackBishop, BlackKnight, BlackRook }
461 };
462
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467         BlackKing, BlackKing, BlackKnight, BlackRook }
468 };
469
470 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473     { BlackRook, BlackMan, BlackBishop, BlackQueen,
474         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
475 };
476
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481         BlackKing, BlackBishop, BlackKnight, BlackRook }
482 };
483
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
489 };
490
491
492 #if (BOARD_SIZE>=10)
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
498 };
499
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
505 };
506
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
509         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
511         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
512 };
513
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
516         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
518         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
519 };
520
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
523         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
525         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
526 };
527
528 #ifdef GOTHIC
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
531         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
533         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
534 };
535 #else // !GOTHIC
536 #define GothicArray CapablancaArray
537 #endif // !GOTHIC
538
539 #ifdef FALCON
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
542         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
544         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !FALCON
547 #define FalconArray CapablancaArray
548 #endif // !FALCON
549
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
556
557 #if (BOARD_SIZE>=12)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
563 };
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
567
568
569 Board initialPosition;
570
571
572 /* Convert str to a rating. Checks for special cases of "----",
573
574    "++++", etc. Also strips ()'s */
575 int
576 string_to_rating(str)
577   char *str;
578 {
579   while(*str && !isdigit(*str)) ++str;
580   if (!*str)
581     return 0;   /* One of the special "no rating" cases */
582   else
583     return atoi(str);
584 }
585
586 void
587 ClearProgramStats()
588 {
589     /* Init programStats */
590     programStats.movelist[0] = 0;
591     programStats.depth = 0;
592     programStats.nr_moves = 0;
593     programStats.moves_left = 0;
594     programStats.nodes = 0;
595     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
596     programStats.score = 0;
597     programStats.got_only_move = 0;
598     programStats.got_fail = 0;
599     programStats.line_is_book = 0;
600 }
601
602 void
603 InitBackEnd1()
604 {
605     int matched, min, sec;
606
607     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
608
609     GetTimeMark(&programStartTime);
610     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
611
612     ClearProgramStats();
613     programStats.ok_to_send = 1;
614     programStats.seen_stat = 0;
615
616     /*
617      * Initialize game list
618      */
619     ListNew(&gameList);
620
621
622     /*
623      * Internet chess server status
624      */
625     if (appData.icsActive) {
626         appData.matchMode = FALSE;
627         appData.matchGames = 0;
628 #if ZIPPY       
629         appData.noChessProgram = !appData.zippyPlay;
630 #else
631         appData.zippyPlay = FALSE;
632         appData.zippyTalk = FALSE;
633         appData.noChessProgram = TRUE;
634 #endif
635         if (*appData.icsHelper != NULLCHAR) {
636             appData.useTelnet = TRUE;
637             appData.telnetProgram = appData.icsHelper;
638         }
639     } else {
640         appData.zippyTalk = appData.zippyPlay = FALSE;
641     }
642
643     /* [AS] Initialize pv info list [HGM] and game state */
644     {
645         int i, j;
646
647         for( i=0; i<MAX_MOVES; i++ ) {
648             pvInfoList[i].depth = -1;
649             epStatus[i]=EP_NONE;
650             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
651         }
652     }
653
654     /*
655      * Parse timeControl resource
656      */
657     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658                           appData.movesPerSession)) {
659         char buf[MSG_SIZ];
660         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661         DisplayFatalError(buf, 0, 2);
662     }
663
664     /*
665      * Parse searchTime resource
666      */
667     if (*appData.searchTime != NULLCHAR) {
668         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
669         if (matched == 1) {
670             searchTime = min * 60;
671         } else if (matched == 2) {
672             searchTime = min * 60 + sec;
673         } else {
674             char buf[MSG_SIZ];
675             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676             DisplayFatalError(buf, 0, 2);
677         }
678     }
679
680     /* [AS] Adjudication threshold */
681     adjudicateLossThreshold = appData.adjudicateLossThreshold;
682     
683     first.which = "first";
684     second.which = "second";
685     first.maybeThinking = second.maybeThinking = FALSE;
686     first.pr = second.pr = NoProc;
687     first.isr = second.isr = NULL;
688     first.sendTime = second.sendTime = 2;
689     first.sendDrawOffers = 1;
690     if (appData.firstPlaysBlack) {
691         first.twoMachinesColor = "black\n";
692         second.twoMachinesColor = "white\n";
693     } else {
694         first.twoMachinesColor = "white\n";
695         second.twoMachinesColor = "black\n";
696     }
697     first.program = appData.firstChessProgram;
698     second.program = appData.secondChessProgram;
699     first.host = appData.firstHost;
700     second.host = appData.secondHost;
701     first.dir = appData.firstDirectory;
702     second.dir = appData.secondDirectory;
703     first.other = &second;
704     second.other = &first;
705     first.initString = appData.initString;
706     second.initString = appData.secondInitString;
707     first.computerString = appData.firstComputerString;
708     second.computerString = appData.secondComputerString;
709     first.useSigint = second.useSigint = TRUE;
710     first.useSigterm = second.useSigterm = TRUE;
711     first.reuse = appData.reuseFirst;
712     second.reuse = appData.reuseSecond;
713     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
714     second.nps = appData.secondNPS;
715     first.useSetboard = second.useSetboard = FALSE;
716     first.useSAN = second.useSAN = FALSE;
717     first.usePing = second.usePing = FALSE;
718     first.lastPing = second.lastPing = 0;
719     first.lastPong = second.lastPong = 0;
720     first.usePlayother = second.usePlayother = FALSE;
721     first.useColors = second.useColors = TRUE;
722     first.useUsermove = second.useUsermove = FALSE;
723     first.sendICS = second.sendICS = FALSE;
724     first.sendName = second.sendName = appData.icsActive;
725     first.sdKludge = second.sdKludge = FALSE;
726     first.stKludge = second.stKludge = FALSE;
727     TidyProgramName(first.program, first.host, first.tidy);
728     TidyProgramName(second.program, second.host, second.tidy);
729     first.matchWins = second.matchWins = 0;
730     strcpy(first.variants, appData.variant);
731     strcpy(second.variants, appData.variant);
732     first.analysisSupport = second.analysisSupport = 2; /* detect */
733     first.analyzing = second.analyzing = FALSE;
734     first.initDone = second.initDone = FALSE;
735
736     /* New features added by Tord: */
737     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739     /* End of new features added by Tord. */
740     first.fenOverride  = appData.fenOverride1;
741     second.fenOverride = appData.fenOverride2;
742
743     /* [HGM] time odds: set factor for each machine */
744     first.timeOdds  = appData.firstTimeOdds;
745     second.timeOdds = appData.secondTimeOdds;
746     { int norm = 1;
747         if(appData.timeOddsMode) {
748             norm = first.timeOdds;
749             if(norm > second.timeOdds) norm = second.timeOdds;
750         }
751         first.timeOdds /= norm;
752         second.timeOdds /= norm;
753     }
754
755     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756     first.accumulateTC = appData.firstAccumulateTC;
757     second.accumulateTC = appData.secondAccumulateTC;
758     first.maxNrOfSessions = second.maxNrOfSessions = 1;
759
760     /* [HGM] debug */
761     first.debug = second.debug = FALSE;
762     first.supportsNPS = second.supportsNPS = UNKNOWN;
763
764     /* [HGM] options */
765     first.optionSettings  = appData.firstOptions;
766     second.optionSettings = appData.secondOptions;
767
768     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770     first.isUCI = appData.firstIsUCI; /* [AS] */
771     second.isUCI = appData.secondIsUCI; /* [AS] */
772     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
774
775     if (appData.firstProtocolVersion > PROTOVER ||
776         appData.firstProtocolVersion < 1) {
777       char buf[MSG_SIZ];
778       sprintf(buf, _("protocol version %d not supported"),
779               appData.firstProtocolVersion);
780       DisplayFatalError(buf, 0, 2);
781     } else {
782       first.protocolVersion = appData.firstProtocolVersion;
783     }
784
785     if (appData.secondProtocolVersion > PROTOVER ||
786         appData.secondProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.secondProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       second.protocolVersion = appData.secondProtocolVersion;
793     }
794
795     if (appData.icsActive) {
796         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
797     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798         appData.clockMode = FALSE;
799         first.sendTime = second.sendTime = 0;
800     }
801     
802 #if ZIPPY
803     /* Override some settings from environment variables, for backward
804        compatibility.  Unfortunately it's not feasible to have the env
805        vars just set defaults, at least in xboard.  Ugh.
806     */
807     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
808       ZippyInit();
809     }
810 #endif
811     
812     if (appData.noChessProgram) {
813         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814         sprintf(programVersion, "%s", PACKAGE_STRING);
815     } else {
816       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
819     }
820
821     if (!appData.icsActive) {
822       char buf[MSG_SIZ];
823       /* Check for variants that are supported only in ICS mode,
824          or not at all.  Some that are accepted here nevertheless
825          have bugs; see comments below.
826       */
827       VariantClass variant = StringToVariant(appData.variant);
828       switch (variant) {
829       case VariantBughouse:     /* need four players and two boards */
830       case VariantKriegspiel:   /* need to hide pieces and move details */
831       /* case VariantFischeRandom: (Fabien: moved below) */
832         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833         DisplayFatalError(buf, 0, 2);
834         return;
835
836       case VariantUnknown:
837       case VariantLoadable:
838       case Variant29:
839       case Variant30:
840       case Variant31:
841       case Variant32:
842       case Variant33:
843       case Variant34:
844       case Variant35:
845       case Variant36:
846       default:
847         sprintf(buf, _("Unknown variant name %s"), appData.variant);
848         DisplayFatalError(buf, 0, 2);
849         return;
850
851       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
852       case VariantFairy:      /* [HGM] TestLegality definitely off! */
853       case VariantGothic:     /* [HGM] should work */
854       case VariantCapablanca: /* [HGM] should work */
855       case VariantCourier:    /* [HGM] initial forced moves not implemented */
856       case VariantShogi:      /* [HGM] drops not tested for legality */
857       case VariantKnightmate: /* [HGM] should work */
858       case VariantCylinder:   /* [HGM] untested */
859       case VariantFalcon:     /* [HGM] untested */
860       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861                                  offboard interposition not understood */
862       case VariantNormal:     /* definitely works! */
863       case VariantWildCastle: /* pieces not automatically shuffled */
864       case VariantNoCastle:   /* pieces not automatically shuffled */
865       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866       case VariantLosers:     /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantSuicide:    /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantGiveaway:   /* should work except for win condition,
871                                  and doesn't know captures are mandatory */
872       case VariantTwoKings:   /* should work */
873       case VariantAtomic:     /* should work except for win condition */
874       case Variant3Check:     /* should work except for win condition */
875       case VariantShatranj:   /* should work except for all win conditions */
876       case VariantBerolina:   /* might work if TestLegality is off */
877       case VariantCapaRandom: /* should work */
878       case VariantJanus:      /* should work */
879       case VariantSuper:      /* experimental */
880       case VariantGreat:      /* experimental, requires legality testing to be off */
881         break;
882       }
883     }
884
885     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
886     InitEngineUCI( installDir, &second );
887 }
888
889 int NextIntegerFromString( char ** str, long * value )
890 {
891     int result = -1;
892     char * s = *str;
893
894     while( *s == ' ' || *s == '\t' ) {
895         s++;
896     }
897
898     *value = 0;
899
900     if( *s >= '0' && *s <= '9' ) {
901         while( *s >= '0' && *s <= '9' ) {
902             *value = *value * 10 + (*s - '0');
903             s++;
904         }
905
906         result = 0;
907     }
908
909     *str = s;
910
911     return result;
912 }
913
914 int NextTimeControlFromString( char ** str, long * value )
915 {
916     long temp;
917     int result = NextIntegerFromString( str, &temp );
918
919     if( result == 0 ) {
920         *value = temp * 60; /* Minutes */
921         if( **str == ':' ) {
922             (*str)++;
923             result = NextIntegerFromString( str, &temp );
924             *value += temp; /* Seconds */
925         }
926     }
927
928     return result;
929 }
930
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
933     int result = -1; long temp, temp2;
934
935     if(**str != '+') return -1; // old params remain in force!
936     (*str)++;
937     if( NextTimeControlFromString( str, &temp ) ) return -1;
938
939     if(**str != '/') {
940         /* time only: incremental or sudden-death time control */
941         if(**str == '+') { /* increment follows; read it */
942             (*str)++;
943             if(result = NextIntegerFromString( str, &temp2)) return -1;
944             *inc = temp2 * 1000;
945         } else *inc = 0;
946         *moves = 0; *tc = temp * 1000; 
947         return 0;
948     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
949
950     (*str)++; /* classical time control */
951     result = NextTimeControlFromString( str, &temp2);
952     if(result == 0) {
953         *moves = temp/60;
954         *tc    = temp2 * 1000;
955         *inc   = 0;
956     }
957     return result;
958 }
959
960 int GetTimeQuota(int movenr)
961 {   /* [HGM] get time to add from the multi-session time-control string */
962     int moves=1; /* kludge to force reading of first session */
963     long time, increment;
964     char *s = fullTimeControlString;
965
966     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
967     do {
968         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970         if(movenr == -1) return time;    /* last move before new session     */
971         if(!moves) return increment;     /* current session is incremental   */
972         if(movenr >= 0) movenr -= moves; /* we already finished this session */
973     } while(movenr >= -1);               /* try again for next session       */
974
975     return 0; // no new time quota on this move
976 }
977
978 int
979 ParseTimeControl(tc, ti, mps)
980      char *tc;
981      int ti;
982      int mps;
983 {
984   long tc1;
985   long tc2;
986   char buf[MSG_SIZ];
987   
988   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
989   if(ti > 0) {
990     if(mps)
991       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992     else sprintf(buf, "+%s+%d", tc, ti);
993   } else {
994     if(mps)
995              sprintf(buf, "+%d/%s", mps, tc);
996     else sprintf(buf, "+%s", tc);
997   }
998   fullTimeControlString = StrSave(buf);
999   
1000   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1001     return FALSE;
1002   }
1003   
1004   if( *tc == '/' ) {
1005     /* Parse second time control */
1006     tc++;
1007     
1008     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1009       return FALSE;
1010     }
1011     
1012     if( tc2 == 0 ) {
1013       return FALSE;
1014     }
1015     
1016     timeControl_2 = tc2 * 1000;
1017   }
1018   else {
1019     timeControl_2 = 0;
1020   }
1021   
1022   if( tc1 == 0 ) {
1023     return FALSE;
1024   }
1025   
1026   timeControl = tc1 * 1000;
1027   
1028   if (ti >= 0) {
1029     timeIncrement = ti * 1000;  /* convert to ms */
1030     movesPerSession = 0;
1031   } else {
1032     timeIncrement = 0;
1033     movesPerSession = mps;
1034   }
1035   return TRUE;
1036 }
1037
1038 void
1039 InitBackEnd2()
1040 {
1041     if (appData.debugMode) {
1042         fprintf(debugFP, "%s\n", programVersion);
1043     }
1044
1045     if (appData.matchGames > 0) {
1046         appData.matchMode = TRUE;
1047     } else if (appData.matchMode) {
1048         appData.matchGames = 1;
1049     }
1050     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1051         appData.matchGames = appData.sameColorGames;
1052     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1053         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1054         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1055     }
1056     Reset(TRUE, FALSE);
1057     if (appData.noChessProgram || first.protocolVersion == 1) {
1058       InitBackEnd3();
1059     } else {
1060       /* kludge: allow timeout for initial "feature" commands */
1061       FreezeUI();
1062       DisplayMessage("", _("Starting chess program"));
1063       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1064     }
1065 }
1066
1067 void
1068 InitBackEnd3 P((void))
1069 {
1070     GameMode initialMode;
1071     char buf[MSG_SIZ];
1072     int err;
1073
1074     InitChessProgram(&first, startedFromSetupPosition);
1075
1076
1077     if (appData.icsActive) {
1078 #ifdef WIN32
1079         /* [DM] Make a console window if needed [HGM] merged ifs */
1080         ConsoleCreate(); 
1081 #endif
1082         err = establish();
1083         if (err != 0) {
1084             if (*appData.icsCommPort != NULLCHAR) {
1085                 sprintf(buf, _("Could not open comm port %s"),  
1086                         appData.icsCommPort);
1087             } else {
1088                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1089                         appData.icsHost, appData.icsPort);
1090             }
1091             DisplayFatalError(buf, err, 1);
1092             return;
1093         }
1094         SetICSMode();
1095         telnetISR =
1096           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1097         fromUserISR =
1098           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1099     } else if (appData.noChessProgram) {
1100         SetNCPMode();
1101     } else {
1102         SetGNUMode();
1103     }
1104
1105     if (*appData.cmailGameName != NULLCHAR) {
1106         SetCmailMode();
1107         OpenLoopback(&cmailPR);
1108         cmailISR =
1109           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1110     }
1111     
1112     ThawUI();
1113     DisplayMessage("", "");
1114     if (StrCaseCmp(appData.initialMode, "") == 0) {
1115       initialMode = BeginningOfGame;
1116     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1117       initialMode = TwoMachinesPlay;
1118     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1119       initialMode = AnalyzeFile; 
1120     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1121       initialMode = AnalyzeMode;
1122     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1123       initialMode = MachinePlaysWhite;
1124     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1125       initialMode = MachinePlaysBlack;
1126     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1127       initialMode = EditGame;
1128     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1129       initialMode = EditPosition;
1130     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1131       initialMode = Training;
1132     } else {
1133       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1134       DisplayFatalError(buf, 0, 2);
1135       return;
1136     }
1137
1138     if (appData.matchMode) {
1139         /* Set up machine vs. machine match */
1140         if (appData.noChessProgram) {
1141             DisplayFatalError(_("Can't have a match with no chess programs"),
1142                               0, 2);
1143             return;
1144         }
1145         matchMode = TRUE;
1146         matchGame = 1;
1147         if (*appData.loadGameFile != NULLCHAR) {
1148             int index = appData.loadGameIndex; // [HGM] autoinc
1149             if(index<0) lastIndex = index = 1;
1150             if (!LoadGameFromFile(appData.loadGameFile,
1151                                   index,
1152                                   appData.loadGameFile, FALSE)) {
1153                 DisplayFatalError(_("Bad game file"), 0, 1);
1154                 return;
1155             }
1156         } else if (*appData.loadPositionFile != NULLCHAR) {
1157             int index = appData.loadPositionIndex; // [HGM] autoinc
1158             if(index<0) lastIndex = index = 1;
1159             if (!LoadPositionFromFile(appData.loadPositionFile,
1160                                       index,
1161                                       appData.loadPositionFile)) {
1162                 DisplayFatalError(_("Bad position file"), 0, 1);
1163                 return;
1164             }
1165         }
1166         TwoMachinesEvent();
1167     } else if (*appData.cmailGameName != NULLCHAR) {
1168         /* Set up cmail mode */
1169         ReloadCmailMsgEvent(TRUE);
1170     } else {
1171         /* Set up other modes */
1172         if (initialMode == AnalyzeFile) {
1173           if (*appData.loadGameFile == NULLCHAR) {
1174             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1175             return;
1176           }
1177         }
1178         if (*appData.loadGameFile != NULLCHAR) {
1179             (void) LoadGameFromFile(appData.loadGameFile,
1180                                     appData.loadGameIndex,
1181                                     appData.loadGameFile, TRUE);
1182         } else if (*appData.loadPositionFile != NULLCHAR) {
1183             (void) LoadPositionFromFile(appData.loadPositionFile,
1184                                         appData.loadPositionIndex,
1185                                         appData.loadPositionFile);
1186             /* [HGM] try to make self-starting even after FEN load */
1187             /* to allow automatic setup of fairy variants with wtm */
1188             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1189                 gameMode = BeginningOfGame;
1190                 setboardSpoiledMachineBlack = 1;
1191             }
1192             /* [HGM] loadPos: make that every new game uses the setup */
1193             /* from file as long as we do not switch variant          */
1194             if(!blackPlaysFirst) { int i;
1195                 startedFromPositionFile = TRUE;
1196                 CopyBoard(filePosition, boards[0]);
1197                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1198             }
1199         }
1200         if (initialMode == AnalyzeMode) {
1201           if (appData.noChessProgram) {
1202             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1203             return;
1204           }
1205           if (appData.icsActive) {
1206             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1207             return;
1208           }
1209           AnalyzeModeEvent();
1210         } else if (initialMode == AnalyzeFile) {
1211           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1212           ShowThinkingEvent();
1213           AnalyzeFileEvent();
1214           AnalysisPeriodicEvent(1);
1215         } else if (initialMode == MachinePlaysWhite) {
1216           if (appData.noChessProgram) {
1217             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1218                               0, 2);
1219             return;
1220           }
1221           if (appData.icsActive) {
1222             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1223                               0, 2);
1224             return;
1225           }
1226           MachineWhiteEvent();
1227         } else if (initialMode == MachinePlaysBlack) {
1228           if (appData.noChessProgram) {
1229             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1230                               0, 2);
1231             return;
1232           }
1233           if (appData.icsActive) {
1234             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1235                               0, 2);
1236             return;
1237           }
1238           MachineBlackEvent();
1239         } else if (initialMode == TwoMachinesPlay) {
1240           if (appData.noChessProgram) {
1241             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1242                               0, 2);
1243             return;
1244           }
1245           if (appData.icsActive) {
1246             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1247                               0, 2);
1248             return;
1249           }
1250           TwoMachinesEvent();
1251         } else if (initialMode == EditGame) {
1252           EditGameEvent();
1253         } else if (initialMode == EditPosition) {
1254           EditPositionEvent();
1255         } else if (initialMode == Training) {
1256           if (*appData.loadGameFile == NULLCHAR) {
1257             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1258             return;
1259           }
1260           TrainingEvent();
1261         }
1262     }
1263 }
1264
1265 /*
1266  * Establish will establish a contact to a remote host.port.
1267  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1268  *  used to talk to the host.
1269  * Returns 0 if okay, error code if not.
1270  */
1271 int
1272 establish()
1273 {
1274     char buf[MSG_SIZ];
1275
1276     if (*appData.icsCommPort != NULLCHAR) {
1277         /* Talk to the host through a serial comm port */
1278         return OpenCommPort(appData.icsCommPort, &icsPR);
1279
1280     } else if (*appData.gateway != NULLCHAR) {
1281         if (*appData.remoteShell == NULLCHAR) {
1282             /* Use the rcmd protocol to run telnet program on a gateway host */
1283             snprintf(buf, sizeof(buf), "%s %s %s",
1284                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1285             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1286
1287         } else {
1288             /* Use the rsh program to run telnet program on a gateway host */
1289             if (*appData.remoteUser == NULLCHAR) {
1290                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1291                         appData.gateway, appData.telnetProgram,
1292                         appData.icsHost, appData.icsPort);
1293             } else {
1294                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1295                         appData.remoteShell, appData.gateway, 
1296                         appData.remoteUser, appData.telnetProgram,
1297                         appData.icsHost, appData.icsPort);
1298             }
1299             return StartChildProcess(buf, "", &icsPR);
1300
1301         }
1302     } else if (appData.useTelnet) {
1303         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1304
1305     } else {
1306         /* TCP socket interface differs somewhat between
1307            Unix and NT; handle details in the front end.
1308            */
1309         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1310     }
1311 }
1312
1313 void
1314 show_bytes(fp, buf, count)
1315      FILE *fp;
1316      char *buf;
1317      int count;
1318 {
1319     while (count--) {
1320         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1321             fprintf(fp, "\\%03o", *buf & 0xff);
1322         } else {
1323             putc(*buf, fp);
1324         }
1325         buf++;
1326     }
1327     fflush(fp);
1328 }
1329
1330 /* Returns an errno value */
1331 int
1332 OutputMaybeTelnet(pr, message, count, outError)
1333      ProcRef pr;
1334      char *message;
1335      int count;
1336      int *outError;
1337 {
1338     char buf[8192], *p, *q, *buflim;
1339     int left, newcount, outcount;
1340
1341     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1342         *appData.gateway != NULLCHAR) {
1343         if (appData.debugMode) {
1344             fprintf(debugFP, ">ICS: ");
1345             show_bytes(debugFP, message, count);
1346             fprintf(debugFP, "\n");
1347         }
1348         return OutputToProcess(pr, message, count, outError);
1349     }
1350
1351     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1352     p = message;
1353     q = buf;
1354     left = count;
1355     newcount = 0;
1356     while (left) {
1357         if (q >= buflim) {
1358             if (appData.debugMode) {
1359                 fprintf(debugFP, ">ICS: ");
1360                 show_bytes(debugFP, buf, newcount);
1361                 fprintf(debugFP, "\n");
1362             }
1363             outcount = OutputToProcess(pr, buf, newcount, outError);
1364             if (outcount < newcount) return -1; /* to be sure */
1365             q = buf;
1366             newcount = 0;
1367         }
1368         if (*p == '\n') {
1369             *q++ = '\r';
1370             newcount++;
1371         } else if (((unsigned char) *p) == TN_IAC) {
1372             *q++ = (char) TN_IAC;
1373             newcount ++;
1374         }
1375         *q++ = *p++;
1376         newcount++;
1377         left--;
1378     }
1379     if (appData.debugMode) {
1380         fprintf(debugFP, ">ICS: ");
1381         show_bytes(debugFP, buf, newcount);
1382         fprintf(debugFP, "\n");
1383     }
1384     outcount = OutputToProcess(pr, buf, newcount, outError);
1385     if (outcount < newcount) return -1; /* to be sure */
1386     return count;
1387 }
1388
1389 void
1390 read_from_player(isr, closure, message, count, error)
1391      InputSourceRef isr;
1392      VOIDSTAR closure;
1393      char *message;
1394      int count;
1395      int error;
1396 {
1397     int outError, outCount;
1398     static int gotEof = 0;
1399
1400     /* Pass data read from player on to ICS */
1401     if (count > 0) {
1402         gotEof = 0;
1403         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1404         if (outCount < count) {
1405             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1406         }
1407     } else if (count < 0) {
1408         RemoveInputSource(isr);
1409         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1410     } else if (gotEof++ > 0) {
1411         RemoveInputSource(isr);
1412         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1413     }
1414 }
1415
1416 void
1417 KeepAlive()
1418 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1419     SendToICS("date\n");
1420     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1421 }
1422
1423 /* added routine for printf style output to ics */
1424 void ics_printf(char *format, ...)
1425 {
1426     char buffer[MSG_SIZ];
1427     va_list args;
1428
1429     va_start(args, format);
1430     vsnprintf(buffer, sizeof(buffer), format, args);
1431     buffer[sizeof(buffer)-1] = '\0';
1432     SendToICS(buffer);
1433     va_end(args);
1434 }
1435
1436 void
1437 SendToICS(s)
1438      char *s;
1439 {
1440     int count, outCount, outError;
1441
1442     if (icsPR == NULL) return;
1443
1444     count = strlen(s);
1445     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1446     if (outCount < count) {
1447         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1448     }
1449 }
1450
1451 /* This is used for sending logon scripts to the ICS. Sending
1452    without a delay causes problems when using timestamp on ICC
1453    (at least on my machine). */
1454 void
1455 SendToICSDelayed(s,msdelay)
1456      char *s;
1457      long msdelay;
1458 {
1459     int count, outCount, outError;
1460
1461     if (icsPR == NULL) return;
1462
1463     count = strlen(s);
1464     if (appData.debugMode) {
1465         fprintf(debugFP, ">ICS: ");
1466         show_bytes(debugFP, s, count);
1467         fprintf(debugFP, "\n");
1468     }
1469     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1470                                       msdelay);
1471     if (outCount < count) {
1472         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1473     }
1474 }
1475
1476
1477 /* Remove all highlighting escape sequences in s
1478    Also deletes any suffix starting with '(' 
1479    */
1480 char *
1481 StripHighlightAndTitle(s)
1482      char *s;
1483 {
1484     static char retbuf[MSG_SIZ];
1485     char *p = retbuf;
1486
1487     while (*s != NULLCHAR) {
1488         while (*s == '\033') {
1489             while (*s != NULLCHAR && !isalpha(*s)) s++;
1490             if (*s != NULLCHAR) s++;
1491         }
1492         while (*s != NULLCHAR && *s != '\033') {
1493             if (*s == '(' || *s == '[') {
1494                 *p = NULLCHAR;
1495                 return retbuf;
1496             }
1497             *p++ = *s++;
1498         }
1499     }
1500     *p = NULLCHAR;
1501     return retbuf;
1502 }
1503
1504 /* Remove all highlighting escape sequences in s */
1505 char *
1506 StripHighlight(s)
1507      char *s;
1508 {
1509     static char retbuf[MSG_SIZ];
1510     char *p = retbuf;
1511
1512     while (*s != NULLCHAR) {
1513         while (*s == '\033') {
1514             while (*s != NULLCHAR && !isalpha(*s)) s++;
1515             if (*s != NULLCHAR) s++;
1516         }
1517         while (*s != NULLCHAR && *s != '\033') {
1518             *p++ = *s++;
1519         }
1520     }
1521     *p = NULLCHAR;
1522     return retbuf;
1523 }
1524
1525 char *variantNames[] = VARIANT_NAMES;
1526 char *
1527 VariantName(v)
1528      VariantClass v;
1529 {
1530     return variantNames[v];
1531 }
1532
1533
1534 /* Identify a variant from the strings the chess servers use or the
1535    PGN Variant tag names we use. */
1536 VariantClass
1537 StringToVariant(e)
1538      char *e;
1539 {
1540     char *p;
1541     int wnum = -1;
1542     VariantClass v = VariantNormal;
1543     int i, found = FALSE;
1544     char buf[MSG_SIZ];
1545
1546     if (!e) return v;
1547
1548     /* [HGM] skip over optional board-size prefixes */
1549     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1550         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1551         while( *e++ != '_');
1552     }
1553
1554     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1555         v = VariantNormal;
1556         found = TRUE;
1557     } else
1558     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1559       if (StrCaseStr(e, variantNames[i])) {
1560         v = (VariantClass) i;
1561         found = TRUE;
1562         break;
1563       }
1564     }
1565
1566     if (!found) {
1567       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1568           || StrCaseStr(e, "wild/fr") 
1569           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1570         v = VariantFischeRandom;
1571       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1572                  (i = 1, p = StrCaseStr(e, "w"))) {
1573         p += i;
1574         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1575         if (isdigit(*p)) {
1576           wnum = atoi(p);
1577         } else {
1578           wnum = -1;
1579         }
1580         switch (wnum) {
1581         case 0: /* FICS only, actually */
1582         case 1:
1583           /* Castling legal even if K starts on d-file */
1584           v = VariantWildCastle;
1585           break;
1586         case 2:
1587         case 3:
1588         case 4:
1589           /* Castling illegal even if K & R happen to start in
1590              normal positions. */
1591           v = VariantNoCastle;
1592           break;
1593         case 5:
1594         case 7:
1595         case 8:
1596         case 10:
1597         case 11:
1598         case 12:
1599         case 13:
1600         case 14:
1601         case 15:
1602         case 18:
1603         case 19:
1604           /* Castling legal iff K & R start in normal positions */
1605           v = VariantNormal;
1606           break;
1607         case 6:
1608         case 20:
1609         case 21:
1610           /* Special wilds for position setup; unclear what to do here */
1611           v = VariantLoadable;
1612           break;
1613         case 9:
1614           /* Bizarre ICC game */
1615           v = VariantTwoKings;
1616           break;
1617         case 16:
1618           v = VariantKriegspiel;
1619           break;
1620         case 17:
1621           v = VariantLosers;
1622           break;
1623         case 22:
1624           v = VariantFischeRandom;
1625           break;
1626         case 23:
1627           v = VariantCrazyhouse;
1628           break;
1629         case 24:
1630           v = VariantBughouse;
1631           break;
1632         case 25:
1633           v = Variant3Check;
1634           break;
1635         case 26:
1636           /* Not quite the same as FICS suicide! */
1637           v = VariantGiveaway;
1638           break;
1639         case 27:
1640           v = VariantAtomic;
1641           break;
1642         case 28:
1643           v = VariantShatranj;
1644           break;
1645
1646         /* Temporary names for future ICC types.  The name *will* change in 
1647            the next xboard/WinBoard release after ICC defines it. */
1648         case 29:
1649           v = Variant29;
1650           break;
1651         case 30:
1652           v = Variant30;
1653           break;
1654         case 31:
1655           v = Variant31;
1656           break;
1657         case 32:
1658           v = Variant32;
1659           break;
1660         case 33:
1661           v = Variant33;
1662           break;
1663         case 34:
1664           v = Variant34;
1665           break;
1666         case 35:
1667           v = Variant35;
1668           break;
1669         case 36:
1670           v = Variant36;
1671           break;
1672         case 37:
1673           v = VariantShogi;
1674           break;
1675         case 38:
1676           v = VariantXiangqi;
1677           break;
1678         case 39:
1679           v = VariantCourier;
1680           break;
1681         case 40:
1682           v = VariantGothic;
1683           break;
1684         case 41:
1685           v = VariantCapablanca;
1686           break;
1687         case 42:
1688           v = VariantKnightmate;
1689           break;
1690         case 43:
1691           v = VariantFairy;
1692           break;
1693         case 44:
1694           v = VariantCylinder;
1695           break;
1696         case 45:
1697           v = VariantFalcon;
1698           break;
1699         case 46:
1700           v = VariantCapaRandom;
1701           break;
1702         case 47:
1703           v = VariantBerolina;
1704           break;
1705         case 48:
1706           v = VariantJanus;
1707           break;
1708         case 49:
1709           v = VariantSuper;
1710           break;
1711         case 50:
1712           v = VariantGreat;
1713           break;
1714         case -1:
1715           /* Found "wild" or "w" in the string but no number;
1716              must assume it's normal chess. */
1717           v = VariantNormal;
1718           break;
1719         default:
1720           sprintf(buf, _("Unknown wild type %d"), wnum);
1721           DisplayError(buf, 0);
1722           v = VariantUnknown;
1723           break;
1724         }
1725       }
1726     }
1727     if (appData.debugMode) {
1728       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1729               e, wnum, VariantName(v));
1730     }
1731     return v;
1732 }
1733
1734 static int leftover_start = 0, leftover_len = 0;
1735 char star_match[STAR_MATCH_N][MSG_SIZ];
1736
1737 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1738    advance *index beyond it, and set leftover_start to the new value of
1739    *index; else return FALSE.  If pattern contains the character '*', it
1740    matches any sequence of characters not containing '\r', '\n', or the
1741    character following the '*' (if any), and the matched sequence(s) are
1742    copied into star_match.
1743    */
1744 int
1745 looking_at(buf, index, pattern)
1746      char *buf;
1747      int *index;
1748      char *pattern;
1749 {
1750     char *bufp = &buf[*index], *patternp = pattern;
1751     int star_count = 0;
1752     char *matchp = star_match[0];
1753     
1754     for (;;) {
1755         if (*patternp == NULLCHAR) {
1756             *index = leftover_start = bufp - buf;
1757             *matchp = NULLCHAR;
1758             return TRUE;
1759         }
1760         if (*bufp == NULLCHAR) return FALSE;
1761         if (*patternp == '*') {
1762             if (*bufp == *(patternp + 1)) {
1763                 *matchp = NULLCHAR;
1764                 matchp = star_match[++star_count];
1765                 patternp += 2;
1766                 bufp++;
1767                 continue;
1768             } else if (*bufp == '\n' || *bufp == '\r') {
1769                 patternp++;
1770                 if (*patternp == NULLCHAR)
1771                   continue;
1772                 else
1773                   return FALSE;
1774             } else {
1775                 *matchp++ = *bufp++;
1776                 continue;
1777             }
1778         }
1779         if (*patternp != *bufp) return FALSE;
1780         patternp++;
1781         bufp++;
1782     }
1783 }
1784
1785 void
1786 SendToPlayer(data, length)
1787      char *data;
1788      int length;
1789 {
1790     int error, outCount;
1791     outCount = OutputToProcess(NoProc, data, length, &error);
1792     if (outCount < length) {
1793         DisplayFatalError(_("Error writing to display"), error, 1);
1794     }
1795 }
1796
1797 void
1798 PackHolding(packed, holding)
1799      char packed[];
1800      char *holding;
1801 {
1802     char *p = holding;
1803     char *q = packed;
1804     int runlength = 0;
1805     int curr = 9999;
1806     do {
1807         if (*p == curr) {
1808             runlength++;
1809         } else {
1810             switch (runlength) {
1811               case 0:
1812                 break;
1813               case 1:
1814                 *q++ = curr;
1815                 break;
1816               case 2:
1817                 *q++ = curr;
1818                 *q++ = curr;
1819                 break;
1820               default:
1821                 sprintf(q, "%d", runlength);
1822                 while (*q) q++;
1823                 *q++ = curr;
1824                 break;
1825             }
1826             runlength = 1;
1827             curr = *p;
1828         }
1829     } while (*p++);
1830     *q = NULLCHAR;
1831 }
1832
1833 /* Telnet protocol requests from the front end */
1834 void
1835 TelnetRequest(ddww, option)
1836      unsigned char ddww, option;
1837 {
1838     unsigned char msg[3];
1839     int outCount, outError;
1840
1841     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1842
1843     if (appData.debugMode) {
1844         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1845         switch (ddww) {
1846           case TN_DO:
1847             ddwwStr = "DO";
1848             break;
1849           case TN_DONT:
1850             ddwwStr = "DONT";
1851             break;
1852           case TN_WILL:
1853             ddwwStr = "WILL";
1854             break;
1855           case TN_WONT:
1856             ddwwStr = "WONT";
1857             break;
1858           default:
1859             ddwwStr = buf1;
1860             sprintf(buf1, "%d", ddww);
1861             break;
1862         }
1863         switch (option) {
1864           case TN_ECHO:
1865             optionStr = "ECHO";
1866             break;
1867           default:
1868             optionStr = buf2;
1869             sprintf(buf2, "%d", option);
1870             break;
1871         }
1872         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1873     }
1874     msg[0] = TN_IAC;
1875     msg[1] = ddww;
1876     msg[2] = option;
1877     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1878     if (outCount < 3) {
1879         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1880     }
1881 }
1882
1883 void
1884 DoEcho()
1885 {
1886     if (!appData.icsActive) return;
1887     TelnetRequest(TN_DO, TN_ECHO);
1888 }
1889
1890 void
1891 DontEcho()
1892 {
1893     if (!appData.icsActive) return;
1894     TelnetRequest(TN_DONT, TN_ECHO);
1895 }
1896
1897 void
1898 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1899 {
1900     /* put the holdings sent to us by the server on the board holdings area */
1901     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1902     char p;
1903     ChessSquare piece;
1904
1905     if(gameInfo.holdingsWidth < 2)  return;
1906
1907     if( (int)lowestPiece >= BlackPawn ) {
1908         holdingsColumn = 0;
1909         countsColumn = 1;
1910         holdingsStartRow = BOARD_HEIGHT-1;
1911         direction = -1;
1912     } else {
1913         holdingsColumn = BOARD_WIDTH-1;
1914         countsColumn = BOARD_WIDTH-2;
1915         holdingsStartRow = 0;
1916         direction = 1;
1917     }
1918
1919     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1920         board[i][holdingsColumn] = EmptySquare;
1921         board[i][countsColumn]   = (ChessSquare) 0;
1922     }
1923     while( (p=*holdings++) != NULLCHAR ) {
1924         piece = CharToPiece( ToUpper(p) );
1925         if(piece == EmptySquare) continue;
1926         /*j = (int) piece - (int) WhitePawn;*/
1927         j = PieceToNumber(piece);
1928         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1929         if(j < 0) continue;               /* should not happen */
1930         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1931         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1932         board[holdingsStartRow+j*direction][countsColumn]++;
1933     }
1934
1935 }
1936
1937
1938 void
1939 VariantSwitch(Board board, VariantClass newVariant)
1940 {
1941    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1942
1943    startedFromPositionFile = FALSE;
1944    if(gameInfo.variant == newVariant) return;
1945
1946    /* [HGM] This routine is called each time an assignment is made to
1947     * gameInfo.variant during a game, to make sure the board sizes
1948     * are set to match the new variant. If that means adding or deleting
1949     * holdings, we shift the playing board accordingly
1950     * This kludge is needed because in ICS observe mode, we get boards
1951     * of an ongoing game without knowing the variant, and learn about the
1952     * latter only later. This can be because of the move list we requested,
1953     * in which case the game history is refilled from the beginning anyway,
1954     * but also when receiving holdings of a crazyhouse game. In the latter
1955     * case we want to add those holdings to the already received position.
1956     */
1957
1958    
1959    if (appData.debugMode) {
1960      fprintf(debugFP, "Switch board from %s to %s\n",
1961              VariantName(gameInfo.variant), VariantName(newVariant));
1962      setbuf(debugFP, NULL);
1963    }
1964    shuffleOpenings = 0;       /* [HGM] shuffle */
1965    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1966    switch(newVariant) 
1967      {
1968      case VariantShogi:
1969        newWidth = 9;  newHeight = 9;
1970        gameInfo.holdingsSize = 7;
1971      case VariantBughouse:
1972      case VariantCrazyhouse:
1973        newHoldingsWidth = 2; break;
1974      case VariantGreat:
1975        newWidth = 10;
1976      case VariantSuper:
1977        newHoldingsWidth = 2;
1978        gameInfo.holdingsSize = 8;
1979        return;
1980      case VariantGothic:
1981      case VariantCapablanca:
1982      case VariantCapaRandom:
1983        newWidth = 10;
1984      default:
1985        newHoldingsWidth = gameInfo.holdingsSize = 0;
1986      };
1987    
1988    if(newWidth  != gameInfo.boardWidth  ||
1989       newHeight != gameInfo.boardHeight ||
1990       newHoldingsWidth != gameInfo.holdingsWidth ) {
1991      
1992      /* shift position to new playing area, if needed */
1993      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1994        for(i=0; i<BOARD_HEIGHT; i++) 
1995          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1996            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1997              board[i][j];
1998        for(i=0; i<newHeight; i++) {
1999          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2000          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2001        }
2002      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2003        for(i=0; i<BOARD_HEIGHT; i++)
2004          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2005            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2006              board[i][j];
2007      }
2008      gameInfo.boardWidth  = newWidth;
2009      gameInfo.boardHeight = newHeight;
2010      gameInfo.holdingsWidth = newHoldingsWidth;
2011      gameInfo.variant = newVariant;
2012      InitDrawingSizes(-2, 0);
2013      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2014    } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2015    
2016    DrawPosition(TRUE, boards[currentMove]);
2017 }
2018
2019 static int loggedOn = FALSE;
2020
2021 /*-- Game start info cache: --*/
2022 int gs_gamenum;
2023 char gs_kind[MSG_SIZ];
2024 static char player1Name[128] = "";
2025 static char player2Name[128] = "";\r
2026 static char cont_seq[] = "\n\\   ";
2027 static int player1Rating = -1;
2028 static int player2Rating = -1;
2029 /*----------------------------*/
2030
2031 ColorClass curColor = ColorNormal;
2032 int suppressKibitz = 0;
2033
2034 void
2035 read_from_ics(isr, closure, data, count, error)
2036      InputSourceRef isr;
2037      VOIDSTAR closure;
2038      char *data;
2039      int count;
2040      int error;
2041 {
2042 #define BUF_SIZE 8192
2043 #define STARTED_NONE 0
2044 #define STARTED_MOVES 1
2045 #define STARTED_BOARD 2
2046 #define STARTED_OBSERVE 3
2047 #define STARTED_HOLDINGS 4
2048 #define STARTED_CHATTER 5
2049 #define STARTED_COMMENT 6
2050 #define STARTED_MOVES_NOHIDE 7
2051     
2052     static int started = STARTED_NONE;
2053     static char parse[20000];
2054     static int parse_pos = 0;
2055     static char buf[BUF_SIZE + 1];
2056     static int firstTime = TRUE, intfSet = FALSE;
2057     static ColorClass prevColor = ColorNormal;
2058     static int savingComment = FALSE;\r
2059         static int cmatch = 0; // continuation sequence match\r
2060         char *bp;
2061     char str[500];
2062     int i, oldi;
2063     int buf_len;
2064     int next_out;
2065     int tkind;
2066     int backup;    /* [DM] For zippy color lines */
2067     char *p;
2068     char talker[MSG_SIZ]; // [HGM] chat
2069     int channel;
2070
2071     if (appData.debugMode) {
2072       if (!error) {
2073         fprintf(debugFP, "<ICS: ");
2074         show_bytes(debugFP, data, count);
2075         fprintf(debugFP, "\n");
2076       }
2077     }
2078
2079     if (appData.debugMode) { int f = forwardMostMove;
2080         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2081                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2082     }
2083     if (count > 0) {
2084         /* If last read ended with a partial line that we couldn't parse,
2085            prepend it to the new read and try again. */
2086         if (leftover_len > 0) {
2087             for (i=0; i<leftover_len; i++)
2088               buf[i] = buf[leftover_start + i];
2089         }
2090
2091     /* copy new characters into the buffer */\r
2092     bp = buf + leftover_len;\r
2093     buf_len=leftover_len;\r
2094     for (i=0; i<count; i++)\r
2095     {\r
2096         // ignore these\r
2097         if (data[i] == '\r')\r
2098             continue;\r
2099 \r
2100         // join lines split by ICS?\r
2101         if (!appData.noJoin)\r
2102         {\r
2103             /*\r
2104                 Joining just consists of finding matches against the\r
2105                 continuation sequence, and discarding that sequence\r
2106                 if found instead of copying it.  So, until a match\r
2107                 fails, there's nothing to do since it might be the\r
2108                 complete sequence, and thus, something we don't want\r
2109                 copied.\r
2110             */\r
2111             if (data[i] == cont_seq[cmatch])\r
2112             {\r
2113                 cmatch++;\r
2114                 if (cmatch == strlen(cont_seq))\r
2115                     cmatch = 0; // complete match.  just reset the counter\r
2116                 continue;\r
2117             }\r
2118             else if (cmatch)\r
2119             {\r
2120                 /*\r
2121                     match failed, so we have to copy what matched before\r
2122                     falling through and copying this character.  In reality,\r
2123                     this will only ever be just the newline character, but\r
2124                     it doesn't hurt to be precise.\r
2125                 */\r
2126                 strncpy(bp, cont_seq, cmatch);\r
2127                 bp += cmatch;\r
2128                 buf_len += cmatch;\r
2129                 cmatch = 0;\r
2130             }\r
2131         }\r
2132 \r
2133         // copy this char\r
2134         *bp++ = data[i];\r
2135         buf_len++;\r
2136     }\r
2137 \r
2138         buf[buf_len] = NULLCHAR;
2139         next_out = leftover_len;
2140         leftover_start = 0;
2141         
2142         i = 0;
2143         while (i < buf_len) {
2144             /* Deal with part of the TELNET option negotiation
2145                protocol.  We refuse to do anything beyond the
2146                defaults, except that we allow the WILL ECHO option,
2147                which ICS uses to turn off password echoing when we are
2148                directly connected to it.  We reject this option
2149                if localLineEditing mode is on (always on in xboard)
2150                and we are talking to port 23, which might be a real
2151                telnet server that will try to keep WILL ECHO on permanently.
2152              */
2153             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2154                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2155                 unsigned char option;
2156                 oldi = i;
2157                 switch ((unsigned char) buf[++i]) {
2158                   case TN_WILL:
2159                     if (appData.debugMode)
2160                       fprintf(debugFP, "\n<WILL ");
2161                     switch (option = (unsigned char) buf[++i]) {
2162                       case TN_ECHO:
2163                         if (appData.debugMode)
2164                           fprintf(debugFP, "ECHO ");
2165                         /* Reply only if this is a change, according
2166                            to the protocol rules. */
2167                         if (remoteEchoOption) break;
2168                         if (appData.localLineEditing &&
2169                             atoi(appData.icsPort) == TN_PORT) {
2170                             TelnetRequest(TN_DONT, TN_ECHO);
2171                         } else {
2172                             EchoOff();
2173                             TelnetRequest(TN_DO, TN_ECHO);
2174                             remoteEchoOption = TRUE;
2175                         }
2176                         break;
2177                       default:
2178                         if (appData.debugMode)
2179                           fprintf(debugFP, "%d ", option);
2180                         /* Whatever this is, we don't want it. */
2181                         TelnetRequest(TN_DONT, option);
2182                         break;
2183                     }
2184                     break;
2185                   case TN_WONT:
2186                     if (appData.debugMode)
2187                       fprintf(debugFP, "\n<WONT ");
2188                     switch (option = (unsigned char) buf[++i]) {
2189                       case TN_ECHO:
2190                         if (appData.debugMode)
2191                           fprintf(debugFP, "ECHO ");
2192                         /* Reply only if this is a change, according
2193                            to the protocol rules. */
2194                         if (!remoteEchoOption) break;
2195                         EchoOn();
2196                         TelnetRequest(TN_DONT, TN_ECHO);
2197                         remoteEchoOption = FALSE;
2198                         break;
2199                       default:
2200                         if (appData.debugMode)
2201                           fprintf(debugFP, "%d ", (unsigned char) option);
2202                         /* Whatever this is, it must already be turned
2203                            off, because we never agree to turn on
2204                            anything non-default, so according to the
2205                            protocol rules, we don't reply. */
2206                         break;
2207                     }
2208                     break;
2209                   case TN_DO:
2210                     if (appData.debugMode)
2211                       fprintf(debugFP, "\n<DO ");
2212                     switch (option = (unsigned char) buf[++i]) {
2213                       default:
2214                         /* Whatever this is, we refuse to do it. */
2215                         if (appData.debugMode)
2216                           fprintf(debugFP, "%d ", option);
2217                         TelnetRequest(TN_WONT, option);
2218                         break;
2219                     }
2220                     break;
2221                   case TN_DONT:
2222                     if (appData.debugMode)
2223                       fprintf(debugFP, "\n<DONT ");
2224                     switch (option = (unsigned char) buf[++i]) {
2225                       default:
2226                         if (appData.debugMode)
2227                           fprintf(debugFP, "%d ", option);
2228                         /* Whatever this is, we are already not doing
2229                            it, because we never agree to do anything
2230                            non-default, so according to the protocol
2231                            rules, we don't reply. */
2232                         break;
2233                     }
2234                     break;
2235                   case TN_IAC:
2236                     if (appData.debugMode)
2237                       fprintf(debugFP, "\n<IAC ");
2238                     /* Doubled IAC; pass it through */
2239                     i--;
2240                     break;
2241                   default:
2242                     if (appData.debugMode)
2243                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2244                     /* Drop all other telnet commands on the floor */
2245                     break;
2246                 }
2247                 if (oldi > next_out)
2248                   SendToPlayer(&buf[next_out], oldi - next_out);
2249                 if (++i > next_out)
2250                   next_out = i;
2251                 continue;
2252             }
2253                 
2254             /* OK, this at least will *usually* work */
2255             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2256                 loggedOn = TRUE;
2257             }
2258             
2259             if (loggedOn && !intfSet) {
2260                 if (ics_type == ICS_ICC) {
2261                   sprintf(str,
2262                           "/set-quietly interface %s\n/set-quietly style 12\n",
2263                           programVersion);
2264                 } else if (ics_type == ICS_CHESSNET) {
2265                   sprintf(str, "/style 12\n");
2266                 } else {
2267                   strcpy(str, "alias $ @\n$set interface ");
2268                   strcat(str, programVersion);
2269                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2270 #ifdef WIN32
2271                   strcat(str, "$iset nohighlight 1\n");
2272 #endif
2273                   strcat(str, "$iset lock 1\n$style 12\n");
2274                 }
2275                 SendToICS(str);
2276                 NotifyFrontendLogin();
2277                 intfSet = TRUE;
2278             }
2279
2280             if (started == STARTED_COMMENT) {
2281                 /* Accumulate characters in comment */
2282                 parse[parse_pos++] = buf[i];
2283                 if (buf[i] == '\n') {
2284                     parse[parse_pos] = NULLCHAR;
2285                     if(chattingPartner>=0) {
2286                         char mess[MSG_SIZ];
2287                         sprintf(mess, "%s%s", talker, parse);
2288                         OutputChatMessage(chattingPartner, mess);
2289                         chattingPartner = -1;
2290                     } else
2291                     if(!suppressKibitz) // [HGM] kibitz
2292                         AppendComment(forwardMostMove, StripHighlight(parse));
2293                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2294                         int nrDigit = 0, nrAlph = 0, i;
2295                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2296                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2297                         parse[parse_pos] = NULLCHAR;
2298                         // try to be smart: if it does not look like search info, it should go to
2299                         // ICS interaction window after all, not to engine-output window.
2300                         for(i=0; i<parse_pos; i++) { // count letters and digits
2301                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2302                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2303                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2304                         }
2305                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2306                             int depth=0; float score;
2307                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2308                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2309                                 pvInfoList[forwardMostMove-1].depth = depth;
2310                                 pvInfoList[forwardMostMove-1].score = 100*score;
2311                             }
2312                             OutputKibitz(suppressKibitz, parse);
2313                         } else {
2314                             char tmp[MSG_SIZ];
2315                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2316                             SendToPlayer(tmp, strlen(tmp));
2317                         }
2318                     }
2319                     started = STARTED_NONE;
2320                 } else {
2321                     /* Don't match patterns against characters in chatter */
2322                     i++;
2323                     continue;
2324                 }
2325             }
2326             if (started == STARTED_CHATTER) {
2327                 if (buf[i] != '\n') {
2328                     /* Don't match patterns against characters in chatter */
2329                     i++;
2330                     continue;
2331                 }
2332                 started = STARTED_NONE;
2333             }
2334
2335             /* Kludge to deal with rcmd protocol */
2336             if (firstTime && looking_at(buf, &i, "\001*")) {
2337                 DisplayFatalError(&buf[1], 0, 1);
2338                 continue;
2339             } else {
2340                 firstTime = FALSE;
2341             }
2342
2343             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2344                 ics_type = ICS_ICC;
2345                 ics_prefix = "/";
2346                 if (appData.debugMode)
2347                   fprintf(debugFP, "ics_type %d\n", ics_type);
2348                 continue;
2349             }
2350             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2351                 ics_type = ICS_FICS;
2352                 ics_prefix = "$";
2353                 if (appData.debugMode)
2354                   fprintf(debugFP, "ics_type %d\n", ics_type);
2355                 continue;
2356             }
2357             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2358                 ics_type = ICS_CHESSNET;
2359                 ics_prefix = "/";
2360                 if (appData.debugMode)
2361                   fprintf(debugFP, "ics_type %d\n", ics_type);
2362                 continue;
2363             }
2364
2365             if (!loggedOn &&
2366                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2367                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2368                  looking_at(buf, &i, "will be \"*\""))) {
2369               strcpy(ics_handle, star_match[0]);
2370               continue;
2371             }
2372
2373             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2374               char buf[MSG_SIZ];
2375               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2376               DisplayIcsInteractionTitle(buf);
2377               have_set_title = TRUE;
2378             }
2379
2380             /* skip finger notes */
2381             if (started == STARTED_NONE &&
2382                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2383                  (buf[i] == '1' && buf[i+1] == '0')) &&
2384                 buf[i+2] == ':' && buf[i+3] == ' ') {
2385               started = STARTED_CHATTER;
2386               i += 3;
2387               continue;
2388             }
2389
2390             /* skip formula vars */
2391             if (started == STARTED_NONE &&
2392                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2393               started = STARTED_CHATTER;
2394               i += 3;
2395               continue;
2396             }
2397
2398             oldi = i;
2399             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2400             if (appData.autoKibitz && started == STARTED_NONE && 
2401                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2402                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2403                 if(looking_at(buf, &i, "* kibitzes: ") &&
2404                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2405                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2406                         suppressKibitz = TRUE;
2407                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2408                                 && (gameMode == IcsPlayingWhite)) ||
2409                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2410                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2411                             started = STARTED_CHATTER; // own kibitz we simply discard
2412                         else {
2413                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2414                             parse_pos = 0; parse[0] = NULLCHAR;
2415                             savingComment = TRUE;
2416                             suppressKibitz = gameMode != IcsObserving ? 2 :
2417                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2418                         } 
2419                         continue;
2420                 } else
2421                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2422                     started = STARTED_CHATTER;
2423                     suppressKibitz = TRUE;
2424                 }
2425             } // [HGM] kibitz: end of patch
2426
2427 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2428
2429             // [HGM] chat: intercept tells by users for which we have an open chat window
2430             channel = -1;
2431             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2432                                            looking_at(buf, &i, "* whispers:") ||
2433                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2434                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2435                 int p;
2436                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2437                 chattingPartner = -1;
2438
2439                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2440                 for(p=0; p<MAX_CHAT; p++) {
2441                     if(channel == atoi(chatPartner[p])) {
2442                     talker[0] = '['; strcat(talker, "]");
2443                     chattingPartner = p; break;
2444                     }
2445                 } else
2446                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2447                 for(p=0; p<MAX_CHAT; p++) {
2448                     if(!strcmp("WHISPER", chatPartner[p])) {
2449                         talker[0] = '['; strcat(talker, "]");
2450                         chattingPartner = p; break;
2451                     }
2452                 }
2453                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2454                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2455                     talker[0] = 0;
2456                     chattingPartner = p; break;
2457                 }
2458                 if(chattingPartner<0) i = oldi; else {
2459                     started = STARTED_COMMENT;
2460                     parse_pos = 0; parse[0] = NULLCHAR;
2461                     savingComment = TRUE;
2462                     suppressKibitz = TRUE;
2463                 }
2464             } // [HGM] chat: end of patch
2465
2466             if (appData.zippyTalk || appData.zippyPlay) {
2467                 /* [DM] Backup address for color zippy lines */
2468                 backup = i;
2469 #if ZIPPY
2470        #ifdef WIN32
2471                if (loggedOn == TRUE)
2472                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2473                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2474        #else
2475                 if (ZippyControl(buf, &i) ||
2476                     ZippyConverse(buf, &i) ||
2477                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2478                       loggedOn = TRUE;
2479                       if (!appData.colorize) continue;
2480                 }
2481        #endif
2482 #endif
2483             } // [DM] 'else { ' deleted
2484                 if (
2485                     /* Regular tells and says */
2486                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2487                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2488                     looking_at(buf, &i, "* says: ") ||
2489                     /* Don't color "message" or "messages" output */
2490                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2491                     looking_at(buf, &i, "*. * at *:*: ") ||
2492                     looking_at(buf, &i, "--* (*:*): ") ||
2493                     /* Message notifications (same color as tells) */
2494                     looking_at(buf, &i, "* has left a message ") ||
2495                     looking_at(buf, &i, "* just sent you a message:\n") ||
2496                     /* Whispers and kibitzes */
2497                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2498                     looking_at(buf, &i, "* kibitzes: ") ||
2499                     /* Channel tells */
2500                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2501
2502                   if (tkind == 1 && strchr(star_match[0], ':')) {
2503                       /* Avoid "tells you:" spoofs in channels */
2504                      tkind = 3;
2505                   }
2506                   if (star_match[0][0] == NULLCHAR ||
2507                       strchr(star_match[0], ' ') ||
2508                       (tkind == 3 && strchr(star_match[1], ' '))) {
2509                     /* Reject bogus matches */
2510                     i = oldi;
2511                   } else {
2512                     if (appData.colorize) {
2513                       if (oldi > next_out) {
2514                         SendToPlayer(&buf[next_out], oldi - next_out);
2515                         next_out = oldi;
2516                       }
2517                       switch (tkind) {
2518                       case 1:
2519                         Colorize(ColorTell, FALSE);
2520                         curColor = ColorTell;
2521                         break;
2522                       case 2:
2523                         Colorize(ColorKibitz, FALSE);
2524                         curColor = ColorKibitz;
2525                         break;
2526                       case 3:
2527                         p = strrchr(star_match[1], '(');
2528                         if (p == NULL) {
2529                           p = star_match[1];
2530                         } else {
2531                           p++;
2532                         }
2533                         if (atoi(p) == 1) {
2534                           Colorize(ColorChannel1, FALSE);
2535                           curColor = ColorChannel1;
2536                         } else {
2537                           Colorize(ColorChannel, FALSE);
2538                           curColor = ColorChannel;
2539                         }
2540                         break;
2541                       case 5:
2542                         curColor = ColorNormal;
2543                         break;
2544                       }
2545                     }
2546                     if (started == STARTED_NONE && appData.autoComment &&
2547                         (gameMode == IcsObserving ||
2548                          gameMode == IcsPlayingWhite ||
2549                          gameMode == IcsPlayingBlack)) {
2550                       parse_pos = i - oldi;
2551                       memcpy(parse, &buf[oldi], parse_pos);
2552                       parse[parse_pos] = NULLCHAR;
2553                       started = STARTED_COMMENT;
2554                       savingComment = TRUE;
2555                     } else {
2556                       started = STARTED_CHATTER;
2557                       savingComment = FALSE;
2558                     }
2559                     loggedOn = TRUE;
2560                     continue;
2561                   }
2562                 }
2563
2564                 if (looking_at(buf, &i, "* s-shouts: ") ||
2565                     looking_at(buf, &i, "* c-shouts: ")) {
2566                     if (appData.colorize) {
2567                         if (oldi > next_out) {
2568                             SendToPlayer(&buf[next_out], oldi - next_out);
2569                             next_out = oldi;
2570                         }
2571                         Colorize(ColorSShout, FALSE);
2572                         curColor = ColorSShout;
2573                     }
2574                     loggedOn = TRUE;
2575                     started = STARTED_CHATTER;
2576                     continue;
2577                 }
2578
2579                 if (looking_at(buf, &i, "--->")) {
2580                     loggedOn = TRUE;
2581                     continue;
2582                 }
2583
2584                 if (looking_at(buf, &i, "* shouts: ") ||
2585                     looking_at(buf, &i, "--> ")) {
2586                     if (appData.colorize) {
2587                         if (oldi > next_out) {
2588                             SendToPlayer(&buf[next_out], oldi - next_out);
2589                             next_out = oldi;
2590                         }
2591                         Colorize(ColorShout, FALSE);
2592                         curColor = ColorShout;
2593                     }
2594                     loggedOn = TRUE;
2595                     started = STARTED_CHATTER;
2596                     continue;
2597                 }
2598
2599                 if (looking_at( buf, &i, "Challenge:")) {
2600                     if (appData.colorize) {
2601                         if (oldi > next_out) {
2602                             SendToPlayer(&buf[next_out], oldi - next_out);
2603                             next_out = oldi;
2604                         }
2605                         Colorize(ColorChallenge, FALSE);
2606                         curColor = ColorChallenge;
2607                     }
2608                     loggedOn = TRUE;
2609                     continue;
2610                 }
2611
2612                 if (looking_at(buf, &i, "* offers you") ||
2613                     looking_at(buf, &i, "* offers to be") ||
2614                     looking_at(buf, &i, "* would like to") ||
2615                     looking_at(buf, &i, "* requests to") ||
2616                     looking_at(buf, &i, "Your opponent offers") ||
2617                     looking_at(buf, &i, "Your opponent requests")) {
2618
2619                     if (appData.colorize) {
2620                         if (oldi > next_out) {
2621                             SendToPlayer(&buf[next_out], oldi - next_out);
2622                             next_out = oldi;
2623                         }
2624                         Colorize(ColorRequest, FALSE);
2625                         curColor = ColorRequest;
2626                     }
2627                     continue;
2628                 }
2629
2630                 if (looking_at(buf, &i, "* (*) seeking")) {
2631                     if (appData.colorize) {
2632                         if (oldi > next_out) {
2633                             SendToPlayer(&buf[next_out], oldi - next_out);
2634                             next_out = oldi;
2635                         }
2636                         Colorize(ColorSeek, FALSE);
2637                         curColor = ColorSeek;
2638                     }
2639                     continue;
2640             }
2641
2642             if (looking_at(buf, &i, "\\   ")) {
2643                 if (prevColor != ColorNormal) {
2644                     if (oldi > next_out) {
2645                         SendToPlayer(&buf[next_out], oldi - next_out);
2646                         next_out = oldi;
2647                     }
2648                     Colorize(prevColor, TRUE);
2649                     curColor = prevColor;
2650                 }
2651                 if (savingComment) {
2652                     parse_pos = i - oldi;
2653                     memcpy(parse, &buf[oldi], parse_pos);
2654                     parse[parse_pos] = NULLCHAR;
2655                     started = STARTED_COMMENT;
2656                 } else {
2657                     started = STARTED_CHATTER;
2658                 }
2659                 continue;
2660             }
2661
2662             if (looking_at(buf, &i, "Black Strength :") ||
2663                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2664                 looking_at(buf, &i, "<10>") ||
2665                 looking_at(buf, &i, "#@#")) {
2666                 /* Wrong board style */
2667                 loggedOn = TRUE;
2668                 SendToICS(ics_prefix);
2669                 SendToICS("set style 12\n");
2670                 SendToICS(ics_prefix);
2671                 SendToICS("refresh\n");
2672                 continue;
2673             }
2674             
2675             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2676                 ICSInitScript();
2677                 have_sent_ICS_logon = 1;
2678                 continue;
2679             }
2680               
2681             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2682                 (looking_at(buf, &i, "\n<12> ") ||
2683                  looking_at(buf, &i, "<12> "))) {
2684                 loggedOn = TRUE;
2685                 if (oldi > next_out) {
2686                     SendToPlayer(&buf[next_out], oldi - next_out);
2687                 }
2688                 next_out = i;
2689                 started = STARTED_BOARD;
2690                 parse_pos = 0;
2691                 continue;
2692             }
2693
2694             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2695                 looking_at(buf, &i, "<b1> ")) {
2696                 if (oldi > next_out) {
2697                     SendToPlayer(&buf[next_out], oldi - next_out);
2698                 }
2699                 next_out = i;
2700                 started = STARTED_HOLDINGS;
2701                 parse_pos = 0;
2702                 continue;
2703             }
2704
2705             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2706                 loggedOn = TRUE;
2707                 /* Header for a move list -- first line */
2708
2709                 switch (ics_getting_history) {
2710                   case H_FALSE:
2711                     switch (gameMode) {
2712                       case IcsIdle:
2713                       case BeginningOfGame:
2714                         /* User typed "moves" or "oldmoves" while we
2715                            were idle.  Pretend we asked for these
2716                            moves and soak them up so user can step
2717                            through them and/or save them.
2718                            */
2719                         Reset(FALSE, TRUE);
2720                         gameMode = IcsObserving;
2721                         ModeHighlight();
2722                         ics_gamenum = -1;
2723                         ics_getting_history = H_GOT_UNREQ_HEADER;
2724                         break;
2725                       case EditGame: /*?*/
2726                       case EditPosition: /*?*/
2727                         /* Should above feature work in these modes too? */
2728                         /* For now it doesn't */
2729                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2730                         break;
2731                       default:
2732                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2733                         break;
2734                     }
2735                     break;
2736                   case H_REQUESTED:
2737                     /* Is this the right one? */
2738                     if (gameInfo.white && gameInfo.black &&
2739                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2740                         strcmp(gameInfo.black, star_match[2]) == 0) {
2741                         /* All is well */
2742                         ics_getting_history = H_GOT_REQ_HEADER;
2743                     }
2744                     break;
2745                   case H_GOT_REQ_HEADER:
2746                   case H_GOT_UNREQ_HEADER:
2747                   case H_GOT_UNWANTED_HEADER:
2748                   case H_GETTING_MOVES:
2749                     /* Should not happen */
2750                     DisplayError(_("Error gathering move list: two headers"), 0);
2751                     ics_getting_history = H_FALSE;
2752                     break;
2753                 }
2754
2755                 /* Save player ratings into gameInfo if needed */
2756                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2757                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2758                     (gameInfo.whiteRating == -1 ||
2759                      gameInfo.blackRating == -1)) {
2760
2761                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2762                     gameInfo.blackRating = string_to_rating(star_match[3]);
2763                     if (appData.debugMode)
2764                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2765                               gameInfo.whiteRating, gameInfo.blackRating);
2766                 }
2767                 continue;
2768             }
2769
2770             if (looking_at(buf, &i,
2771               "* * match, initial time: * minute*, increment: * second")) {
2772                 /* Header for a move list -- second line */
2773                 /* Initial board will follow if this is a wild game */
2774                 if (gameInfo.event != NULL) free(gameInfo.event);
2775                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2776                 gameInfo.event = StrSave(str);
2777                 /* [HGM] we switched variant. Translate boards if needed. */
2778                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2779                 continue;
2780             }
2781
2782             if (looking_at(buf, &i, "Move  ")) {
2783                 /* Beginning of a move list */
2784                 switch (ics_getting_history) {
2785                   case H_FALSE:
2786                     /* Normally should not happen */
2787                     /* Maybe user hit reset while we were parsing */
2788                     break;
2789                   case H_REQUESTED:
2790                     /* Happens if we are ignoring a move list that is not
2791                      * the one we just requested.  Common if the user
2792                      * tries to observe two games without turning off
2793                      * getMoveList */
2794                     break;
2795                   case H_GETTING_MOVES:
2796                     /* Should not happen */
2797                     DisplayError(_("Error gathering move list: nested"), 0);
2798                     ics_getting_history = H_FALSE;
2799                     break;
2800                   case H_GOT_REQ_HEADER:
2801                     ics_getting_history = H_GETTING_MOVES;
2802                     started = STARTED_MOVES;
2803                     parse_pos = 0;
2804                     if (oldi > next_out) {
2805                         SendToPlayer(&buf[next_out], oldi - next_out);
2806                     }
2807                     break;
2808                   case H_GOT_UNREQ_HEADER:
2809                     ics_getting_history = H_GETTING_MOVES;
2810                     started = STARTED_MOVES_NOHIDE;
2811                     parse_pos = 0;
2812                     break;
2813                   case H_GOT_UNWANTED_HEADER:
2814                     ics_getting_history = H_FALSE;
2815                     break;
2816                 }
2817                 continue;
2818             }                           
2819             
2820             if (looking_at(buf, &i, "% ") ||
2821                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2822                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2823                 savingComment = FALSE;
2824                 switch (started) {
2825                   case STARTED_MOVES:
2826                   case STARTED_MOVES_NOHIDE:
2827                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2828                     parse[parse_pos + i - oldi] = NULLCHAR;
2829                     ParseGameHistory(parse);
2830 #if ZIPPY
2831                     if (appData.zippyPlay && first.initDone) {
2832                         FeedMovesToProgram(&first, forwardMostMove);
2833                         if (gameMode == IcsPlayingWhite) {
2834                             if (WhiteOnMove(forwardMostMove)) {
2835                                 if (first.sendTime) {
2836                                   if (first.useColors) {
2837                                     SendToProgram("black\n", &first); 
2838                                   }
2839                                   SendTimeRemaining(&first, TRUE);
2840                                 }
2841                                 if (first.useColors) {
2842                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2843                                 }
2844                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2845                                 first.maybeThinking = TRUE;
2846                             } else {
2847                                 if (first.usePlayother) {
2848                                   if (first.sendTime) {
2849                                     SendTimeRemaining(&first, TRUE);
2850                                   }
2851                                   SendToProgram("playother\n", &first);
2852                                   firstMove = FALSE;
2853                                 } else {
2854                                   firstMove = TRUE;
2855                                 }
2856                             }
2857                         } else if (gameMode == IcsPlayingBlack) {
2858                             if (!WhiteOnMove(forwardMostMove)) {
2859                                 if (first.sendTime) {
2860                                   if (first.useColors) {
2861                                     SendToProgram("white\n", &first);
2862                                   }
2863                                   SendTimeRemaining(&first, FALSE);
2864                                 }
2865                                 if (first.useColors) {
2866                                   SendToProgram("black\n", &first);
2867                                 }
2868                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2869                                 first.maybeThinking = TRUE;
2870                             } else {
2871                                 if (first.usePlayother) {
2872                                   if (first.sendTime) {
2873                                     SendTimeRemaining(&first, FALSE);
2874                                   }
2875                                   SendToProgram("playother\n", &first);
2876                                   firstMove = FALSE;
2877                                 } else {
2878                                   firstMove = TRUE;
2879                                 }
2880                             }
2881                         }                       
2882                     }
2883 #endif
2884                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2885                         /* Moves came from oldmoves or moves command
2886                            while we weren't doing anything else.
2887                            */
2888                         currentMove = forwardMostMove;
2889                         ClearHighlights();/*!!could figure this out*/
2890                         flipView = appData.flipView;
2891                         DrawPosition(FALSE, boards[currentMove]);
2892                         DisplayBothClocks();
2893                         sprintf(str, "%s vs. %s",
2894                                 gameInfo.white, gameInfo.black);
2895                         DisplayTitle(str);
2896                         gameMode = IcsIdle;
2897                     } else {
2898                         /* Moves were history of an active game */
2899                         if (gameInfo.resultDetails != NULL) {
2900                             free(gameInfo.resultDetails);
2901                             gameInfo.resultDetails = NULL;
2902                         }
2903                     }
2904                     HistorySet(parseList, backwardMostMove,
2905                                forwardMostMove, currentMove-1);
2906                     DisplayMove(currentMove - 1);
2907                     if (started == STARTED_MOVES) next_out = i;
2908                     started = STARTED_NONE;
2909                     ics_getting_history = H_FALSE;
2910                     break;
2911
2912                   case STARTED_OBSERVE:
2913                     started = STARTED_NONE;
2914                     SendToICS(ics_prefix);
2915                     SendToICS("refresh\n");
2916                     break;
2917
2918                   default:
2919                     break;
2920                 }
2921                 if(bookHit) { // [HGM] book: simulate book reply
2922                     static char bookMove[MSG_SIZ]; // a bit generous?
2923
2924                     programStats.nodes = programStats.depth = programStats.time = 
2925                     programStats.score = programStats.got_only_move = 0;
2926                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2927
2928                     strcpy(bookMove, "move ");
2929                     strcat(bookMove, bookHit);
2930                     HandleMachineMove(bookMove, &first);
2931                 }
2932                 continue;
2933             }
2934             
2935             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2936                  started == STARTED_HOLDINGS ||
2937                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2938                 /* Accumulate characters in move list or board */
2939                 parse[parse_pos++] = buf[i];
2940             }
2941             
2942             /* Start of game messages.  Mostly we detect start of game
2943                when the first board image arrives.  On some versions
2944                of the ICS, though, we need to do a "refresh" after starting
2945                to observe in order to get the current board right away. */
2946             if (looking_at(buf, &i, "Adding game * to observation list")) {
2947                 started = STARTED_OBSERVE;
2948                 continue;
2949             }
2950
2951             /* Handle auto-observe */
2952             if (appData.autoObserve &&
2953                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2954                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2955                 char *player;
2956                 /* Choose the player that was highlighted, if any. */
2957                 if (star_match[0][0] == '\033' ||
2958                     star_match[1][0] != '\033') {
2959                     player = star_match[0];
2960                 } else {
2961                     player = star_match[2];
2962                 }
2963                 sprintf(str, "%sobserve %s\n",
2964                         ics_prefix, StripHighlightAndTitle(player));
2965                 SendToICS(str);
2966
2967                 /* Save ratings from notify string */
2968                 strcpy(player1Name, star_match[0]);
2969                 player1Rating = string_to_rating(star_match[1]);
2970                 strcpy(player2Name, star_match[2]);
2971                 player2Rating = string_to_rating(star_match[3]);
2972
2973                 if (appData.debugMode)
2974                   fprintf(debugFP, 
2975                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2976                           player1Name, player1Rating,
2977                           player2Name, player2Rating);
2978
2979                 continue;
2980             }
2981
2982             /* Deal with automatic examine mode after a game,
2983                and with IcsObserving -> IcsExamining transition */
2984             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2985                 looking_at(buf, &i, "has made you an examiner of game *")) {
2986
2987                 int gamenum = atoi(star_match[0]);
2988                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2989                     gamenum == ics_gamenum) {
2990                     /* We were already playing or observing this game;
2991                        no need to refetch history */
2992                     gameMode = IcsExamining;
2993                     if (pausing) {
2994                         pauseExamForwardMostMove = forwardMostMove;
2995                     } else if (currentMove < forwardMostMove) {
2996                         ForwardInner(forwardMostMove);
2997                     }
2998                 } else {
2999                     /* I don't think this case really can happen */
3000                     SendToICS(ics_prefix);
3001                     SendToICS("refresh\n");
3002                 }
3003                 continue;
3004             }    
3005             
3006             /* Error messages */
3007 //          if (ics_user_moved) {
3008             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3009                 if (looking_at(buf, &i, "Illegal move") ||
3010                     looking_at(buf, &i, "Not a legal move") ||
3011                     looking_at(buf, &i, "Your king is in check") ||
3012                     looking_at(buf, &i, "It isn't your turn") ||
3013                     looking_at(buf, &i, "It is not your move")) {
3014                     /* Illegal move */
3015                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3016                         currentMove = --forwardMostMove;
3017                         DisplayMove(currentMove - 1); /* before DMError */
3018                         DrawPosition(FALSE, boards[currentMove]);
3019                         SwitchClocks();
3020                         DisplayBothClocks();
3021                     }
3022                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3023                     ics_user_moved = 0;
3024                     continue;
3025                 }
3026             }
3027
3028             if (looking_at(buf, &i, "still have time") ||
3029                 looking_at(buf, &i, "not out of time") ||
3030                 looking_at(buf, &i, "either player is out of time") ||
3031                 looking_at(buf, &i, "has timeseal; checking")) {
3032                 /* We must have called his flag a little too soon */
3033                 whiteFlag = blackFlag = FALSE;
3034                 continue;
3035             }
3036
3037             if (looking_at(buf, &i, "added * seconds to") ||
3038                 looking_at(buf, &i, "seconds were added to")) {
3039                 /* Update the clocks */
3040                 SendToICS(ics_prefix);
3041                 SendToICS("refresh\n");
3042                 continue;
3043             }
3044
3045             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3046                 ics_clock_paused = TRUE;
3047                 StopClocks();
3048                 continue;
3049             }
3050
3051             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3052                 ics_clock_paused = FALSE;
3053                 StartClocks();
3054                 continue;
3055             }
3056
3057             /* Grab player ratings from the Creating: message.
3058                Note we have to check for the special case when
3059                the ICS inserts things like [white] or [black]. */
3060             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3061                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3062                 /* star_matches:
3063                    0    player 1 name (not necessarily white)
3064                    1    player 1 rating
3065                    2    empty, white, or black (IGNORED)
3066                    3    player 2 name (not necessarily black)
3067                    4    player 2 rating
3068                    
3069                    The names/ratings are sorted out when the game
3070                    actually starts (below).
3071                 */
3072                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3073                 player1Rating = string_to_rating(star_match[1]);
3074                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3075                 player2Rating = string_to_rating(star_match[4]);
3076
3077                 if (appData.debugMode)
3078                   fprintf(debugFP, 
3079                           "Ratings from 'Creating:' %s %d, %s %d\n",
3080                           player1Name, player1Rating,
3081                           player2Name, player2Rating);
3082
3083                 continue;
3084             }
3085             
3086             /* Improved generic start/end-of-game messages */
3087             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3088                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3089                 /* If tkind == 0: */
3090                 /* star_match[0] is the game number */
3091                 /*           [1] is the white player's name */
3092                 /*           [2] is the black player's name */
3093                 /* For end-of-game: */
3094                 /*           [3] is the reason for the game end */
3095                 /*           [4] is a PGN end game-token, preceded by " " */
3096                 /* For start-of-game: */
3097                 /*           [3] begins with "Creating" or "Continuing" */
3098                 /*           [4] is " *" or empty (don't care). */
3099                 int gamenum = atoi(star_match[0]);
3100                 char *whitename, *blackname, *why, *endtoken;
3101                 ChessMove endtype = (ChessMove) 0;
3102
3103                 if (tkind == 0) {
3104                   whitename = star_match[1];
3105                   blackname = star_match[2];
3106                   why = star_match[3];
3107                   endtoken = star_match[4];
3108                 } else {
3109                   whitename = star_match[1];
3110                   blackname = star_match[3];
3111                   why = star_match[5];
3112                   endtoken = star_match[6];
3113                 }
3114
3115                 /* Game start messages */
3116                 if (strncmp(why, "Creating ", 9) == 0 ||
3117                     strncmp(why, "Continuing ", 11) == 0) {
3118                     gs_gamenum = gamenum;
3119                     strcpy(gs_kind, strchr(why, ' ') + 1);
3120 #if ZIPPY
3121                     if (appData.zippyPlay) {
3122                         ZippyGameStart(whitename, blackname);
3123                     }
3124 #endif /*ZIPPY*/
3125                     continue;
3126                 }
3127
3128                 /* Game end messages */
3129                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3130                     ics_gamenum != gamenum) {
3131                     continue;
3132                 }
3133                 while (endtoken[0] == ' ') endtoken++;
3134                 switch (endtoken[0]) {
3135                   case '*':
3136                   default:
3137                     endtype = GameUnfinished;
3138                     break;
3139                   case '0':
3140                     endtype = BlackWins;
3141                     break;
3142                   case '1':
3143                     if (endtoken[1] == '/')
3144                       endtype = GameIsDrawn;
3145                     else
3146                       endtype = WhiteWins;
3147                     break;
3148                 }
3149                 GameEnds(endtype, why, GE_ICS);
3150 #if ZIPPY
3151                 if (appData.zippyPlay && first.initDone) {
3152                     ZippyGameEnd(endtype, why);
3153                     if (first.pr == NULL) {
3154                       /* Start the next process early so that we'll
3155                          be ready for the next challenge */
3156                       StartChessProgram(&first);
3157                     }
3158                     /* Send "new" early, in case this command takes
3159                        a long time to finish, so that we'll be ready
3160                        for the next challenge. */
3161                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3162                     Reset(TRUE, TRUE);
3163                 }
3164 #endif /*ZIPPY*/
3165                 continue;
3166             }
3167
3168             if (looking_at(buf, &i, "Removing game * from observation") ||
3169                 looking_at(buf, &i, "no longer observing game *") ||
3170                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3171                 if (gameMode == IcsObserving &&
3172                     atoi(star_match[0]) == ics_gamenum)
3173                   {
3174                       /* icsEngineAnalyze */
3175                       if (appData.icsEngineAnalyze) {
3176                             ExitAnalyzeMode();
3177                             ModeHighlight();
3178                       }
3179                       StopClocks();
3180                       gameMode = IcsIdle;
3181                       ics_gamenum = -1;
3182                       ics_user_moved = FALSE;
3183                   }
3184                 continue;
3185             }
3186
3187             if (looking_at(buf, &i, "no longer examining game *")) {
3188                 if (gameMode == IcsExamining &&
3189                     atoi(star_match[0]) == ics_gamenum)
3190                   {
3191                       gameMode = IcsIdle;
3192                       ics_gamenum = -1;
3193                       ics_user_moved = FALSE;
3194                   }
3195                 continue;
3196             }
3197
3198             /* Advance leftover_start past any newlines we find,
3199                so only partial lines can get reparsed */
3200             if (looking_at(buf, &i, "\n")) {
3201                 prevColor = curColor;
3202                 if (curColor != ColorNormal) {
3203                     if (oldi > next_out) {
3204                         SendToPlayer(&buf[next_out], oldi - next_out);
3205                         next_out = oldi;
3206                     }
3207                     Colorize(ColorNormal, FALSE);
3208                     curColor = ColorNormal;
3209                 }
3210                 if (started == STARTED_BOARD) {
3211                     started = STARTED_NONE;
3212                     parse[parse_pos] = NULLCHAR;
3213                     ParseBoard12(parse);
3214                     ics_user_moved = 0;
3215
3216                     /* Send premove here */
3217                     if (appData.premove) {
3218                       char str[MSG_SIZ];
3219                       if (currentMove == 0 &&
3220                           gameMode == IcsPlayingWhite &&
3221                           appData.premoveWhite) {
3222                         sprintf(str, "%s%s\n", ics_prefix,
3223                                 appData.premoveWhiteText);
3224                         if (appData.debugMode)
3225                           fprintf(debugFP, "Sending premove:\n");
3226                         SendToICS(str);
3227                       } else if (currentMove == 1 &&
3228                                  gameMode == IcsPlayingBlack &&
3229                                  appData.premoveBlack) {
3230                         sprintf(str, "%s%s\n", ics_prefix,
3231                                 appData.premoveBlackText);
3232                         if (appData.debugMode)
3233                           fprintf(debugFP, "Sending premove:\n");
3234                         SendToICS(str);
3235                       } else if (gotPremove) {
3236                         gotPremove = 0;
3237                         ClearPremoveHighlights();
3238                         if (appData.debugMode)
3239                           fprintf(debugFP, "Sending premove:\n");
3240                           UserMoveEvent(premoveFromX, premoveFromY, 
3241                                         premoveToX, premoveToY, 
3242                                         premovePromoChar);
3243                       }
3244                     }
3245
3246                     /* Usually suppress following prompt */
3247                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3248                         if (looking_at(buf, &i, "*% ")) {
3249                             savingComment = FALSE;
3250                         }
3251                     }
3252                     next_out = i;
3253                 } else if (started == STARTED_HOLDINGS) {
3254                     int gamenum;
3255                     char new_piece[MSG_SIZ];
3256                     started = STARTED_NONE;
3257                     parse[parse_pos] = NULLCHAR;
3258                     if (appData.debugMode)
3259                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3260                                                         parse, currentMove);
3261                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3262                         gamenum == ics_gamenum) {
3263                         if (gameInfo.variant == VariantNormal) {
3264                           /* [HGM] We seem to switch variant during a game!
3265                            * Presumably no holdings were displayed, so we have
3266                            * to move the position two files to the right to
3267                            * create room for them!
3268                            */
3269                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3270                           /* Get a move list just to see the header, which
3271                              will tell us whether this is really bug or zh */
3272                           if (ics_getting_history == H_FALSE) {
3273                             ics_getting_history = H_REQUESTED;
3274                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3275                             SendToICS(str);
3276                           }
3277                         }
3278                         new_piece[0] = NULLCHAR;
3279                         sscanf(parse, "game %d white [%s black [%s <- %s",
3280                                &gamenum, white_holding, black_holding,
3281                                new_piece);
3282                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3283                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3284                         /* [HGM] copy holdings to board holdings area */
3285                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3286                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3287 #if ZIPPY
3288                         if (appData.zippyPlay && first.initDone) {
3289                             ZippyHoldings(white_holding, black_holding,
3290                                           new_piece);
3291                         }
3292 #endif /*ZIPPY*/
3293                         if (tinyLayout || smallLayout) {
3294                             char wh[16], bh[16];
3295                             PackHolding(wh, white_holding);
3296                             PackHolding(bh, black_holding);
3297                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3298                                     gameInfo.white, gameInfo.black);
3299                         } else {
3300                             sprintf(str, "%s [%s] vs. %s [%s]",
3301                                     gameInfo.white, white_holding,
3302                                     gameInfo.black, black_holding);
3303                         }
3304
3305                         DrawPosition(FALSE, boards[currentMove]);
3306                         DisplayTitle(str);
3307                     }
3308                     /* Suppress following prompt */
3309                     if (looking_at(buf, &i, "*% ")) {
3310                         savingComment = FALSE;
3311                     }
3312                     next_out = i;
3313                 }
3314                 continue;
3315             }
3316
3317             i++;                /* skip unparsed character and loop back */
3318         }
3319         
3320         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3321             started != STARTED_HOLDINGS && i > next_out) {
3322             SendToPlayer(&buf[next_out], i - next_out);
3323             next_out = i;
3324         }
3325         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3326         
3327         leftover_len = buf_len - leftover_start;
3328         /* if buffer ends with something we couldn't parse,
3329            reparse it after appending the next read */
3330         
3331     } else if (count == 0) {
3332         RemoveInputSource(isr);
3333         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3334     } else {
3335         DisplayFatalError(_("Error reading from ICS"), error, 1);
3336     }
3337 }
3338
3339
3340 /* Board style 12 looks like this:
3341    
3342    <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
3343    
3344  * The "<12> " is stripped before it gets to this routine.  The two
3345  * trailing 0's (flip state and clock ticking) are later addition, and
3346  * some chess servers may not have them, or may have only the first.
3347  * Additional trailing fields may be added in the future.  
3348  */
3349
3350 #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"
3351
3352 #define RELATION_OBSERVING_PLAYED    0
3353 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3354 #define RELATION_PLAYING_MYMOVE      1
3355 #define RELATION_PLAYING_NOTMYMOVE  -1
3356 #define RELATION_EXAMINING           2
3357 #define RELATION_ISOLATED_BOARD     -3
3358 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3359
3360 void
3361 ParseBoard12(string)
3362      char *string;
3363
3364     GameMode newGameMode;
3365     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3366     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3367     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3368     char to_play, board_chars[200];
3369     char move_str[500], str[500], elapsed_time[500];
3370     char black[32], white[32];
3371     Board board;
3372     int prevMove = currentMove;
3373     int ticking = 2;
3374     ChessMove moveType;
3375     int fromX, fromY, toX, toY;
3376     char promoChar;
3377     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3378     char *bookHit = NULL; // [HGM] book
3379
3380     fromX = fromY = toX = toY = -1;
3381     
3382     newGame = FALSE;
3383
3384     if (appData.debugMode)
3385       fprintf(debugFP, _("Parsing board: %s\n"), string);
3386
3387     move_str[0] = NULLCHAR;
3388     elapsed_time[0] = NULLCHAR;
3389     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3390         int  i = 0, j;
3391         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3392             if(string[i] == ' ') { ranks++; files = 0; }
3393             else files++;
3394             i++;
3395         }
3396         for(j = 0; j <i; j++) board_chars[j] = string[j];
3397         board_chars[i] = '\0';
3398         string += i + 1;
3399     }
3400     n = sscanf(string, PATTERN, &to_play, &double_push,
3401                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3402                &gamenum, white, black, &relation, &basetime, &increment,
3403                &white_stren, &black_stren, &white_time, &black_time,
3404                &moveNum, str, elapsed_time, move_str, &ics_flip,
3405                &ticking);
3406
3407     if (n < 21) {
3408         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3409         DisplayError(str, 0);
3410         return;
3411     }
3412
3413     /* Convert the move number to internal form */
3414     moveNum = (moveNum - 1) * 2;
3415     if (to_play == 'B') moveNum++;
3416     if (moveNum >= MAX_MOVES) {
3417       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3418                         0, 1);
3419       return;
3420     }
3421     
3422     switch (relation) {
3423       case RELATION_OBSERVING_PLAYED:
3424       case RELATION_OBSERVING_STATIC:
3425         if (gamenum == -1) {
3426             /* Old ICC buglet */
3427             relation = RELATION_OBSERVING_STATIC;
3428         }
3429         newGameMode = IcsObserving;
3430         break;
3431       case RELATION_PLAYING_MYMOVE:
3432       case RELATION_PLAYING_NOTMYMOVE:
3433         newGameMode =
3434           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3435             IcsPlayingWhite : IcsPlayingBlack;
3436         break;
3437       case RELATION_EXAMINING:
3438         newGameMode = IcsExamining;
3439         break;
3440       case RELATION_ISOLATED_BOARD:
3441       default:
3442         /* Just display this board.  If user was doing something else,
3443            we will forget about it until the next board comes. */ 
3444         newGameMode = IcsIdle;
3445         break;
3446       case RELATION_STARTING_POSITION:
3447         newGameMode = gameMode;
3448         break;
3449     }
3450     
3451     /* Modify behavior for initial board display on move listing
3452        of wild games.
3453        */
3454     switch (ics_getting_history) {
3455       case H_FALSE:
3456       case H_REQUESTED:
3457         break;
3458       case H_GOT_REQ_HEADER:
3459       case H_GOT_UNREQ_HEADER:
3460         /* This is the initial position of the current game */
3461         gamenum = ics_gamenum;
3462         moveNum = 0;            /* old ICS bug workaround */
3463         if (to_play == 'B') {
3464           startedFromSetupPosition = TRUE;
3465           blackPlaysFirst = TRUE;
3466           moveNum = 1;
3467           if (forwardMostMove == 0) forwardMostMove = 1;
3468           if (backwardMostMove == 0) backwardMostMove = 1;
3469           if (currentMove == 0) currentMove = 1;
3470         }
3471         newGameMode = gameMode;
3472         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3473         break;
3474       case H_GOT_UNWANTED_HEADER:
3475         /* This is an initial board that we don't want */
3476         return;
3477       case H_GETTING_MOVES:
3478         /* Should not happen */
3479         DisplayError(_("Error gathering move list: extra board"), 0);
3480         ics_getting_history = H_FALSE;
3481         return;
3482     }
3483     
3484     /* Take action if this is the first board of a new game, or of a
3485        different game than is currently being displayed.  */
3486     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3487         relation == RELATION_ISOLATED_BOARD) {
3488         
3489         /* Forget the old game and get the history (if any) of the new one */
3490         if (gameMode != BeginningOfGame) {
3491           Reset(FALSE, TRUE);
3492         }
3493         newGame = TRUE;
3494         if (appData.autoRaiseBoard) BoardToTop();
3495         prevMove = -3;
3496         if (gamenum == -1) {
3497             newGameMode = IcsIdle;
3498         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3499                    appData.getMoveList) {
3500             /* Need to get game history */
3501             ics_getting_history = H_REQUESTED;
3502             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3503             SendToICS(str);
3504         }
3505         
3506         /* Initially flip the board to have black on the bottom if playing
3507            black or if the ICS flip flag is set, but let the user change
3508            it with the Flip View button. */
3509         flipView = appData.autoFlipView ? 
3510           (newGameMode == IcsPlayingBlack) || ics_flip :
3511           appData.flipView;
3512         
3513         /* Done with values from previous mode; copy in new ones */
3514         gameMode = newGameMode;
3515         ModeHighlight();
3516         ics_gamenum = gamenum;
3517         if (gamenum == gs_gamenum) {
3518             int klen = strlen(gs_kind);
3519             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3520             sprintf(str, "ICS %s", gs_kind);
3521             gameInfo.event = StrSave(str);
3522         } else {
3523             gameInfo.event = StrSave("ICS game");
3524         }
3525         gameInfo.site = StrSave(appData.icsHost);
3526         gameInfo.date = PGNDate();
3527         gameInfo.round = StrSave("-");
3528         gameInfo.white = StrSave(white);
3529         gameInfo.black = StrSave(black);
3530         timeControl = basetime * 60 * 1000;
3531         timeControl_2 = 0;
3532         timeIncrement = increment * 1000;
3533         movesPerSession = 0;
3534         gameInfo.timeControl = TimeControlTagValue();
3535         VariantSwitch(board, StringToVariant(gameInfo.event) );
3536   if (appData.debugMode) {
3537     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3538     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3539     setbuf(debugFP, NULL);
3540   }
3541
3542         gameInfo.outOfBook = NULL;
3543         
3544         /* Do we have the ratings? */
3545         if (strcmp(player1Name, white) == 0 &&
3546             strcmp(player2Name, black) == 0) {
3547             if (appData.debugMode)
3548               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3549                       player1Rating, player2Rating);
3550             gameInfo.whiteRating = player1Rating;
3551             gameInfo.blackRating = player2Rating;
3552         } else if (strcmp(player2Name, white) == 0 &&
3553                    strcmp(player1Name, black) == 0) {
3554             if (appData.debugMode)
3555               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3556                       player2Rating, player1Rating);
3557             gameInfo.whiteRating = player2Rating;
3558             gameInfo.blackRating = player1Rating;
3559         }
3560         player1Name[0] = player2Name[0] = NULLCHAR;
3561
3562         /* Silence shouts if requested */
3563         if (appData.quietPlay &&
3564             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3565             SendToICS(ics_prefix);
3566             SendToICS("set shout 0\n");
3567         }
3568     }
3569     
3570     /* Deal with midgame name changes */
3571     if (!newGame) {
3572         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3573             if (gameInfo.white) free(gameInfo.white);
3574             gameInfo.white = StrSave(white);
3575         }
3576         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3577             if (gameInfo.black) free(gameInfo.black);
3578             gameInfo.black = StrSave(black);
3579         }
3580     }
3581     
3582     /* Throw away game result if anything actually changes in examine mode */
3583     if (gameMode == IcsExamining && !newGame) {
3584         gameInfo.result = GameUnfinished;
3585         if (gameInfo.resultDetails != NULL) {
3586             free(gameInfo.resultDetails);
3587             gameInfo.resultDetails = NULL;
3588         }
3589     }
3590     
3591     /* In pausing && IcsExamining mode, we ignore boards coming
3592        in if they are in a different variation than we are. */
3593     if (pauseExamInvalid) return;
3594     if (pausing && gameMode == IcsExamining) {
3595         if (moveNum <= pauseExamForwardMostMove) {
3596             pauseExamInvalid = TRUE;
3597             forwardMostMove = pauseExamForwardMostMove;
3598             return;
3599         }
3600     }
3601     
3602   if (appData.debugMode) {
3603     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3604   }
3605     /* Parse the board */
3606     for (k = 0; k < ranks; k++) {
3607       for (j = 0; j < files; j++)
3608         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3609       if(gameInfo.holdingsWidth > 1) {
3610            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3611            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3612       }
3613     }
3614     CopyBoard(boards[moveNum], board);
3615     if (moveNum == 0) {
3616         startedFromSetupPosition =
3617           !CompareBoards(board, initialPosition);
3618         if(startedFromSetupPosition)
3619             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3620     }
3621
3622     /* [HGM] Set castling rights. Take the outermost Rooks,
3623        to make it also work for FRC opening positions. Note that board12
3624        is really defective for later FRC positions, as it has no way to
3625        indicate which Rook can castle if they are on the same side of King.
3626        For the initial position we grant rights to the outermost Rooks,
3627        and remember thos rights, and we then copy them on positions
3628        later in an FRC game. This means WB might not recognize castlings with
3629        Rooks that have moved back to their original position as illegal,
3630        but in ICS mode that is not its job anyway.
3631     */
3632     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3633     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3634
3635         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3636             if(board[0][i] == WhiteRook) j = i;
3637         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3638         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3639             if(board[0][i] == WhiteRook) j = i;
3640         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3641         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3642             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3643         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3644         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3645             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3646         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3647
3648         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3649         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3650             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3651         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3652             if(board[BOARD_HEIGHT-1][k] == bKing)
3653                 initialRights[5] = castlingRights[moveNum][5] = k;
3654     } else { int r;
3655         r = castlingRights[moveNum][0] = initialRights[0];
3656         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3657         r = castlingRights[moveNum][1] = initialRights[1];
3658         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3659         r = castlingRights[moveNum][3] = initialRights[3];
3660         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3661         r = castlingRights[moveNum][4] = initialRights[4];
3662         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3663         /* wildcastle kludge: always assume King has rights */
3664         r = castlingRights[moveNum][2] = initialRights[2];
3665         r = castlingRights[moveNum][5] = initialRights[5];
3666     }
3667     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3668     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3669
3670     
3671     if (ics_getting_history == H_GOT_REQ_HEADER ||
3672         ics_getting_history == H_GOT_UNREQ_HEADER) {
3673         /* This was an initial position from a move list, not
3674            the current position */
3675         return;
3676     }
3677     
3678     /* Update currentMove and known move number limits */
3679     newMove = newGame || moveNum > forwardMostMove;
3680
3681     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3682     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3683         takeback = forwardMostMove - moveNum;
3684         for (i = 0; i < takeback; i++) {
3685              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3686              SendToProgram("undo\n", &first);
3687         }
3688     }
3689
3690     if (newGame) {
3691         forwardMostMove = backwardMostMove = currentMove = moveNum;
3692         if (gameMode == IcsExamining && moveNum == 0) {
3693           /* Workaround for ICS limitation: we are not told the wild
3694              type when starting to examine a game.  But if we ask for
3695              the move list, the move list header will tell us */
3696             ics_getting_history = H_REQUESTED;
3697             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3698             SendToICS(str);
3699         }
3700     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3701                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3702         forwardMostMove = moveNum;
3703         if (!pausing || currentMove > forwardMostMove)
3704           currentMove = forwardMostMove;
3705     } else {
3706         /* New part of history that is not contiguous with old part */ 
3707         if (pausing && gameMode == IcsExamining) {
3708             pauseExamInvalid = TRUE;
3709             forwardMostMove = pauseExamForwardMostMove;
3710             return;
3711         }
3712         forwardMostMove = backwardMostMove = currentMove = moveNum;
3713         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3714             ics_getting_history = H_REQUESTED;
3715             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3716             SendToICS(str);
3717         }
3718     }
3719     
3720     /* Update the clocks */
3721     if (strchr(elapsed_time, '.')) {
3722       /* Time is in ms */
3723       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3724       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3725     } else {
3726       /* Time is in seconds */
3727       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3728       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3729     }
3730       
3731
3732 #if ZIPPY
3733     if (appData.zippyPlay && newGame &&
3734         gameMode != IcsObserving && gameMode != IcsIdle &&
3735         gameMode != IcsExamining)
3736       ZippyFirstBoard(moveNum, basetime, increment);
3737 #endif
3738     
3739     /* Put the move on the move list, first converting
3740        to canonical algebraic form. */
3741     if (moveNum > 0) {
3742   if (appData.debugMode) {
3743     if (appData.debugMode) { int f = forwardMostMove;
3744         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3745                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3746     }
3747     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3748     fprintf(debugFP, "moveNum = %d\n", moveNum);
3749     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3750     setbuf(debugFP, NULL);
3751   }
3752         if (moveNum <= backwardMostMove) {
3753             /* We don't know what the board looked like before
3754                this move.  Punt. */
3755             strcpy(parseList[moveNum - 1], move_str);
3756             strcat(parseList[moveNum - 1], " ");
3757             strcat(parseList[moveNum - 1], elapsed_time);
3758             moveList[moveNum - 1][0] = NULLCHAR;
3759         } else if (strcmp(move_str, "none") == 0) {
3760             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3761             /* Again, we don't know what the board looked like;
3762                this is really the start of the game. */
3763             parseList[moveNum - 1][0] = NULLCHAR;
3764             moveList[moveNum - 1][0] = NULLCHAR;
3765             backwardMostMove = moveNum;
3766             startedFromSetupPosition = TRUE;
3767             fromX = fromY = toX = toY = -1;
3768         } else {
3769           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3770           //                 So we parse the long-algebraic move string in stead of the SAN move
3771           int valid; char buf[MSG_SIZ], *prom;
3772
3773           // str looks something like "Q/a1-a2"; kill the slash
3774           if(str[1] == '/') 
3775                 sprintf(buf, "%c%s", str[0], str+2);
3776           else  strcpy(buf, str); // might be castling
3777           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3778                 strcat(buf, prom); // long move lacks promo specification!
3779           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3780                 if(appData.debugMode) 
3781                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3782                 strcpy(move_str, buf);
3783           }
3784           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3785                                 &fromX, &fromY, &toX, &toY, &promoChar)
3786                || ParseOneMove(buf, moveNum - 1, &moveType,
3787                                 &fromX, &fromY, &toX, &toY, &promoChar);
3788           // end of long SAN patch
3789           if (valid) {
3790             (void) CoordsToAlgebraic(boards[moveNum - 1],
3791                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3792                                      fromY, fromX, toY, toX, promoChar,
3793                                      parseList[moveNum-1]);
3794             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3795                              castlingRights[moveNum]) ) {
3796               case MT_NONE:
3797               case MT_STALEMATE:
3798               default:
3799                 break;
3800               case MT_CHECK:
3801                 if(gameInfo.variant != VariantShogi)
3802                     strcat(parseList[moveNum - 1], "+");
3803                 break;
3804               case MT_CHECKMATE:
3805               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3806                 strcat(parseList[moveNum - 1], "#");
3807                 break;
3808             }
3809             strcat(parseList[moveNum - 1], " ");
3810             strcat(parseList[moveNum - 1], elapsed_time);
3811             /* currentMoveString is set as a side-effect of ParseOneMove */
3812             strcpy(moveList[moveNum - 1], currentMoveString);
3813             strcat(moveList[moveNum - 1], "\n");
3814           } else {
3815             /* Move from ICS was illegal!?  Punt. */
3816   if (appData.debugMode) {
3817     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3818     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3819   }
3820             strcpy(parseList[moveNum - 1], move_str);
3821             strcat(parseList[moveNum - 1], " ");
3822             strcat(parseList[moveNum - 1], elapsed_time);
3823             moveList[moveNum - 1][0] = NULLCHAR;
3824             fromX = fromY = toX = toY = -1;
3825           }
3826         }
3827   if (appData.debugMode) {
3828     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3829     setbuf(debugFP, NULL);
3830   }
3831
3832 #if ZIPPY
3833         /* Send move to chess program (BEFORE animating it). */
3834         if (appData.zippyPlay && !newGame && newMove && 
3835            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3836
3837             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3838                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3839                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3840                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3841                             move_str);
3842                     DisplayError(str, 0);
3843                 } else {
3844                     if (first.sendTime) {
3845                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3846                     }
3847                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3848                     if (firstMove && !bookHit) {
3849                         firstMove = FALSE;
3850                         if (first.useColors) {
3851                           SendToProgram(gameMode == IcsPlayingWhite ?
3852                                         "white\ngo\n" :
3853                                         "black\ngo\n", &first);
3854                         } else {
3855                           SendToProgram("go\n", &first);
3856                         }
3857                         first.maybeThinking = TRUE;
3858                     }
3859                 }
3860             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3861               if (moveList[moveNum - 1][0] == NULLCHAR) {
3862                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3863                 DisplayError(str, 0);
3864               } else {
3865                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3866                 SendMoveToProgram(moveNum - 1, &first);
3867               }
3868             }
3869         }
3870 #endif
3871     }
3872
3873     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3874         /* If move comes from a remote source, animate it.  If it
3875            isn't remote, it will have already been animated. */
3876         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3877             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3878         }
3879         if (!pausing && appData.highlightLastMove) {
3880             SetHighlights(fromX, fromY, toX, toY);
3881         }
3882     }
3883     
3884     /* Start the clocks */
3885     whiteFlag = blackFlag = FALSE;
3886     appData.clockMode = !(basetime == 0 && increment == 0);
3887     if (ticking == 0) {
3888       ics_clock_paused = TRUE;
3889       StopClocks();
3890     } else if (ticking == 1) {
3891       ics_clock_paused = FALSE;
3892     }
3893     if (gameMode == IcsIdle ||
3894         relation == RELATION_OBSERVING_STATIC ||
3895         relation == RELATION_EXAMINING ||
3896         ics_clock_paused)
3897       DisplayBothClocks();
3898     else
3899       StartClocks();
3900     
3901     /* Display opponents and material strengths */
3902     if (gameInfo.variant != VariantBughouse &&
3903         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3904         if (tinyLayout || smallLayout) {
3905             if(gameInfo.variant == VariantNormal)
3906                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3907                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3908                     basetime, increment);
3909             else
3910                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3911                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3912                     basetime, increment, (int) gameInfo.variant);
3913         } else {
3914             if(gameInfo.variant == VariantNormal)
3915                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3916                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3917                     basetime, increment);
3918             else
3919                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3920                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3921                     basetime, increment, VariantName(gameInfo.variant));
3922         }
3923         DisplayTitle(str);
3924   if (appData.debugMode) {
3925     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3926   }
3927     }
3928
3929    
3930     /* Display the board */
3931     if (!pausing && !appData.noGUI) {
3932       
3933       if (appData.premove)
3934           if (!gotPremove || 
3935              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3936              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3937               ClearPremoveHighlights();
3938
3939       DrawPosition(FALSE, boards[currentMove]);
3940       DisplayMove(moveNum - 1);
3941       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3942             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3943               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3944         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3945       }
3946     }
3947
3948     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3949 #if ZIPPY
3950     if(bookHit) { // [HGM] book: simulate book reply
3951         static char bookMove[MSG_SIZ]; // a bit generous?
3952
3953         programStats.nodes = programStats.depth = programStats.time = 
3954         programStats.score = programStats.got_only_move = 0;
3955         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3956
3957         strcpy(bookMove, "move ");
3958         strcat(bookMove, bookHit);
3959         HandleMachineMove(bookMove, &first);
3960     }
3961 #endif
3962 }
3963
3964 void
3965 GetMoveListEvent()
3966 {
3967     char buf[MSG_SIZ];
3968     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3969         ics_getting_history = H_REQUESTED;
3970         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3971         SendToICS(buf);
3972     }
3973 }
3974
3975 void
3976 AnalysisPeriodicEvent(force)
3977      int force;
3978 {
3979     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3980          && !force) || !appData.periodicUpdates)
3981       return;
3982
3983     /* Send . command to Crafty to collect stats */
3984     SendToProgram(".\n", &first);
3985
3986     /* Don't send another until we get a response (this makes
3987        us stop sending to old Crafty's which don't understand
3988        the "." command (sending illegal cmds resets node count & time,
3989        which looks bad)) */
3990     programStats.ok_to_send = 0;
3991 }
3992
3993 void ics_update_width(new_width)
3994         int new_width;
3995 {
3996         ics_printf("set width %d\n", new_width);
3997 }
3998
3999 void
4000 SendMoveToProgram(moveNum, cps)
4001      int moveNum;
4002      ChessProgramState *cps;
4003 {
4004     char buf[MSG_SIZ];
4005
4006     if (cps->useUsermove) {
4007       SendToProgram("usermove ", cps);
4008     }
4009     if (cps->useSAN) {
4010       char *space;
4011       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4012         int len = space - parseList[moveNum];
4013         memcpy(buf, parseList[moveNum], len);
4014         buf[len++] = '\n';
4015         buf[len] = NULLCHAR;
4016       } else {
4017         sprintf(buf, "%s\n", parseList[moveNum]);
4018       }
4019       SendToProgram(buf, cps);
4020     } else {
4021       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4022         AlphaRank(moveList[moveNum], 4);
4023         SendToProgram(moveList[moveNum], cps);
4024         AlphaRank(moveList[moveNum], 4); // and back
4025       } else
4026       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4027        * the engine. It would be nice to have a better way to identify castle 
4028        * moves here. */
4029       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4030                                                                          && cps->useOOCastle) {
4031         int fromX = moveList[moveNum][0] - AAA; 
4032         int fromY = moveList[moveNum][1] - ONE;
4033         int toX = moveList[moveNum][2] - AAA; 
4034         int toY = moveList[moveNum][3] - ONE;
4035         if((boards[moveNum][fromY][fromX] == WhiteKing 
4036             && boards[moveNum][toY][toX] == WhiteRook)
4037            || (boards[moveNum][fromY][fromX] == BlackKing 
4038                && boards[moveNum][toY][toX] == BlackRook)) {
4039           if(toX > fromX) SendToProgram("O-O\n", cps);
4040           else SendToProgram("O-O-O\n", cps);
4041         }
4042         else SendToProgram(moveList[moveNum], cps);
4043       }
4044       else SendToProgram(moveList[moveNum], cps);
4045       /* End of additions by Tord */
4046     }
4047
4048     /* [HGM] setting up the opening has brought engine in force mode! */
4049     /*       Send 'go' if we are in a mode where machine should play. */
4050     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4051         (gameMode == TwoMachinesPlay   ||
4052 #ifdef ZIPPY
4053          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4054 #endif
4055          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4056         SendToProgram("go\n", cps);
4057   if (appData.debugMode) {
4058     fprintf(debugFP, "(extra)\n");
4059   }
4060     }
4061     setboardSpoiledMachineBlack = 0;
4062 }
4063
4064 void
4065 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4066      ChessMove moveType;
4067      int fromX, fromY, toX, toY;
4068 {
4069     char user_move[MSG_SIZ];
4070
4071     switch (moveType) {
4072       default:
4073         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4074                 (int)moveType, fromX, fromY, toX, toY);
4075         DisplayError(user_move + strlen("say "), 0);
4076         break;
4077       case WhiteKingSideCastle:
4078       case BlackKingSideCastle:
4079       case WhiteQueenSideCastleWild:
4080       case BlackQueenSideCastleWild:
4081       /* PUSH Fabien */
4082       case WhiteHSideCastleFR:
4083       case BlackHSideCastleFR:
4084       /* POP Fabien */
4085         sprintf(user_move, "o-o\n");
4086         break;
4087       case WhiteQueenSideCastle:
4088       case BlackQueenSideCastle:
4089       case WhiteKingSideCastleWild:
4090       case BlackKingSideCastleWild:
4091       /* PUSH Fabien */
4092       case WhiteASideCastleFR:
4093       case BlackASideCastleFR:
4094       /* POP Fabien */
4095         sprintf(user_move, "o-o-o\n");
4096         break;
4097       case WhitePromotionQueen:
4098       case BlackPromotionQueen:
4099       case WhitePromotionRook:
4100       case BlackPromotionRook:
4101       case WhitePromotionBishop:
4102       case BlackPromotionBishop:
4103       case WhitePromotionKnight:
4104       case BlackPromotionKnight:
4105       case WhitePromotionKing:
4106       case BlackPromotionKing:
4107       case WhitePromotionChancellor:
4108       case BlackPromotionChancellor:
4109       case WhitePromotionArchbishop:
4110       case BlackPromotionArchbishop:
4111         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4112             sprintf(user_move, "%c%c%c%c=%c\n",
4113                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4114                 PieceToChar(WhiteFerz));
4115         else if(gameInfo.variant == VariantGreat)
4116             sprintf(user_move, "%c%c%c%c=%c\n",
4117                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4118                 PieceToChar(WhiteMan));
4119         else
4120             sprintf(user_move, "%c%c%c%c=%c\n",
4121                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4122                 PieceToChar(PromoPiece(moveType)));
4123         break;
4124       case WhiteDrop:
4125       case BlackDrop:
4126         sprintf(user_move, "%c@%c%c\n",
4127                 ToUpper(PieceToChar((ChessSquare) fromX)),
4128                 AAA + toX, ONE + toY);
4129         break;
4130       case NormalMove:
4131       case WhiteCapturesEnPassant:
4132       case BlackCapturesEnPassant:
4133       case IllegalMove:  /* could be a variant we don't quite understand */
4134         sprintf(user_move, "%c%c%c%c\n",
4135                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4136         break;
4137     }
4138     SendToICS(user_move);
4139     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4140         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4141 }
4142
4143 void
4144 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4145      int rf, ff, rt, ft;
4146      char promoChar;
4147      char move[7];
4148 {
4149     if (rf == DROP_RANK) {
4150         sprintf(move, "%c@%c%c\n",
4151                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4152     } else {
4153         if (promoChar == 'x' || promoChar == NULLCHAR) {
4154             sprintf(move, "%c%c%c%c\n",
4155                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4156         } else {
4157             sprintf(move, "%c%c%c%c%c\n",
4158                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4159         }
4160     }
4161 }
4162
4163 void
4164 ProcessICSInitScript(f)
4165      FILE *f;
4166 {
4167     char buf[MSG_SIZ];
4168
4169     while (fgets(buf, MSG_SIZ, f)) {
4170         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4171     }
4172
4173     fclose(f);
4174 }
4175
4176
4177 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4178 void
4179 AlphaRank(char *move, int n)
4180 {
4181 //    char *p = move, c; int x, y;
4182
4183     if (appData.debugMode) {
4184         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4185     }
4186
4187     if(move[1]=='*' && 
4188        move[2]>='0' && move[2]<='9' &&
4189        move[3]>='a' && move[3]<='x'    ) {
4190         move[1] = '@';
4191         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4192         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4193     } else
4194     if(move[0]>='0' && move[0]<='9' &&
4195        move[1]>='a' && move[1]<='x' &&
4196        move[2]>='0' && move[2]<='9' &&
4197        move[3]>='a' && move[3]<='x'    ) {
4198         /* input move, Shogi -> normal */
4199         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4200         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4201         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4202         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4203     } else
4204     if(move[1]=='@' &&
4205        move[3]>='0' && move[3]<='9' &&
4206        move[2]>='a' && move[2]<='x'    ) {
4207         move[1] = '*';
4208         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4209         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4210     } else
4211     if(
4212        move[0]>='a' && move[0]<='x' &&
4213        move[3]>='0' && move[3]<='9' &&
4214        move[2]>='a' && move[2]<='x'    ) {
4215          /* output move, normal -> Shogi */
4216         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4217         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4218         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4219         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4220         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4221     }
4222     if (appData.debugMode) {
4223         fprintf(debugFP, "   out = '%s'\n", move);
4224     }
4225 }
4226
4227 /* Parser for moves from gnuchess, ICS, or user typein box */
4228 Boolean
4229 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4230      char *move;
4231      int moveNum;
4232      ChessMove *moveType;
4233      int *fromX, *fromY, *toX, *toY;
4234      char *promoChar;
4235 {       
4236     if (appData.debugMode) {
4237         fprintf(debugFP, "move to parse: %s\n", move);
4238     }
4239     *moveType = yylexstr(moveNum, move);
4240
4241     switch (*moveType) {
4242       case WhitePromotionChancellor:
4243       case BlackPromotionChancellor:
4244       case WhitePromotionArchbishop:
4245       case BlackPromotionArchbishop:
4246       case WhitePromotionQueen:
4247       case BlackPromotionQueen:
4248       case WhitePromotionRook:
4249       case BlackPromotionRook:
4250       case WhitePromotionBishop:
4251       case BlackPromotionBishop:
4252       case WhitePromotionKnight:
4253       case BlackPromotionKnight:
4254       case WhitePromotionKing:
4255       case BlackPromotionKing:
4256       case NormalMove:
4257       case WhiteCapturesEnPassant:
4258       case BlackCapturesEnPassant:
4259       case WhiteKingSideCastle:
4260       case WhiteQueenSideCastle:
4261       case BlackKingSideCastle:
4262       case BlackQueenSideCastle:
4263       case WhiteKingSideCastleWild:
4264       case WhiteQueenSideCastleWild:
4265       case BlackKingSideCastleWild:
4266       case BlackQueenSideCastleWild:
4267       /* Code added by Tord: */
4268       case WhiteHSideCastleFR:
4269       case WhiteASideCastleFR:
4270       case BlackHSideCastleFR:
4271       case BlackASideCastleFR:
4272       /* End of code added by Tord */
4273       case IllegalMove:         /* bug or odd chess variant */
4274         *fromX = currentMoveString[0] - AAA;
4275         *fromY = currentMoveString[1] - ONE;
4276         *toX = currentMoveString[2] - AAA;
4277         *toY = currentMoveString[3] - ONE;
4278         *promoChar = currentMoveString[4];
4279         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4280             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4281     if (appData.debugMode) {
4282         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4283     }
4284             *fromX = *fromY = *toX = *toY = 0;
4285             return FALSE;
4286         }
4287         if (appData.testLegality) {
4288           return (*moveType != IllegalMove);
4289         } else {
4290           return !(fromX == fromY && toX == toY);
4291         }
4292
4293       case WhiteDrop:
4294       case BlackDrop:
4295         *fromX = *moveType == WhiteDrop ?
4296           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4297           (int) CharToPiece(ToLower(currentMoveString[0]));
4298         *fromY = DROP_RANK;
4299         *toX = currentMoveString[2] - AAA;
4300         *toY = currentMoveString[3] - ONE;
4301         *promoChar = NULLCHAR;
4302         return TRUE;
4303
4304       case AmbiguousMove:
4305       case ImpossibleMove:
4306       case (ChessMove) 0:       /* end of file */
4307       case ElapsedTime:
4308       case Comment:
4309       case PGNTag:
4310       case NAG:
4311       case WhiteWins:
4312       case BlackWins:
4313       case GameIsDrawn:
4314       default:
4315     if (appData.debugMode) {
4316         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4317     }
4318         /* bug? */
4319         *fromX = *fromY = *toX = *toY = 0;
4320         *promoChar = NULLCHAR;
4321         return FALSE;
4322     }
4323 }
4324
4325 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4326 // All positions will have equal probability, but the current method will not provide a unique
4327 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4328 #define DARK 1
4329 #define LITE 2
4330 #define ANY 3
4331
4332 int squaresLeft[4];
4333 int piecesLeft[(int)BlackPawn];
4334 int seed, nrOfShuffles;
4335
4336 void GetPositionNumber()
4337 {       // sets global variable seed
4338         int i;
4339
4340         seed = appData.defaultFrcPosition;
4341         if(seed < 0) { // randomize based on time for negative FRC position numbers
4342                 for(i=0; i<50; i++) seed += random();
4343                 seed = random() ^ random() >> 8 ^ random() << 8;
4344                 if(seed<0) seed = -seed;
4345         }
4346 }
4347
4348 int put(Board board, int pieceType, int rank, int n, int shade)
4349 // put the piece on the (n-1)-th empty squares of the given shade
4350 {
4351         int i;
4352
4353         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4354                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4355                         board[rank][i] = (ChessSquare) pieceType;
4356                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4357                         squaresLeft[ANY]--;
4358                         piecesLeft[pieceType]--; 
4359                         return i;
4360                 }
4361         }
4362         return -1;
4363 }
4364
4365
4366 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4367 // calculate where the next piece goes, (any empty square), and put it there
4368 {
4369         int i;
4370
4371         i = seed % squaresLeft[shade];
4372         nrOfShuffles *= squaresLeft[shade];
4373         seed /= squaresLeft[shade];
4374         put(board, pieceType, rank, i, shade);
4375 }
4376
4377 void AddTwoPieces(Board board, int pieceType, int rank)
4378 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4379 {
4380         int i, n=squaresLeft[ANY], j=n-1, k;
4381
4382         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4383         i = seed % k;  // pick one
4384         nrOfShuffles *= k;
4385         seed /= k;
4386         while(i >= j) i -= j--;
4387         j = n - 1 - j; i += j;
4388         put(board, pieceType, rank, j, ANY);
4389         put(board, pieceType, rank, i, ANY);
4390 }
4391
4392 void SetUpShuffle(Board board, int number)
4393 {
4394         int i, p, first=1;
4395
4396         GetPositionNumber(); nrOfShuffles = 1;
4397
4398         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4399         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4400         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4401
4402         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4403
4404         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4405             p = (int) board[0][i];
4406             if(p < (int) BlackPawn) piecesLeft[p] ++;
4407             board[0][i] = EmptySquare;
4408         }
4409
4410         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4411             // shuffles restricted to allow normal castling put KRR first
4412             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4413                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4414             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4415                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4416             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4417                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4418             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4419                 put(board, WhiteRook, 0, 0, ANY);
4420             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4421         }
4422
4423         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4424             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4425             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4426                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4427                 while(piecesLeft[p] >= 2) {
4428                     AddOnePiece(board, p, 0, LITE);
4429                     AddOnePiece(board, p, 0, DARK);
4430                 }
4431                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4432             }
4433
4434         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4435             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4436             // but we leave King and Rooks for last, to possibly obey FRC restriction
4437             if(p == (int)WhiteRook) continue;
4438             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4439             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4440         }
4441
4442         // now everything is placed, except perhaps King (Unicorn) and Rooks
4443
4444         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4445             // Last King gets castling rights
4446             while(piecesLeft[(int)WhiteUnicorn]) {
4447                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4448                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4449             }
4450
4451             while(piecesLeft[(int)WhiteKing]) {
4452                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4453                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4454             }
4455
4456
4457         } else {
4458             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4459             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4460         }
4461
4462         // Only Rooks can be left; simply place them all
4463         while(piecesLeft[(int)WhiteRook]) {
4464                 i = put(board, WhiteRook, 0, 0, ANY);
4465                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4466                         if(first) {
4467                                 first=0;
4468                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4469                         }
4470                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4471                 }
4472         }
4473         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4474             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4475         }
4476
4477         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4478 }
4479
4480 int SetCharTable( char *table, const char * map )
4481 /* [HGM] moved here from winboard.c because of its general usefulness */
4482 /*       Basically a safe strcpy that uses the last character as King */
4483 {
4484     int result = FALSE; int NrPieces;
4485
4486     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4487                     && NrPieces >= 12 && !(NrPieces&1)) {
4488         int i; /* [HGM] Accept even length from 12 to 34 */
4489
4490         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4491         for( i=0; i<NrPieces/2-1; i++ ) {
4492             table[i] = map[i];
4493             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4494         }
4495         table[(int) WhiteKing]  = map[NrPieces/2-1];
4496         table[(int) BlackKing]  = map[NrPieces-1];
4497
4498         result = TRUE;
4499     }
4500
4501     return result;
4502 }
4503
4504 void Prelude(Board board)
4505 {       // [HGM] superchess: random selection of exo-pieces
4506         int i, j, k; ChessSquare p; 
4507         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4508
4509         GetPositionNumber(); // use FRC position number
4510
4511         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4512             SetCharTable(pieceToChar, appData.pieceToCharTable);
4513             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4514                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4515         }
4516
4517         j = seed%4;                 seed /= 4; 
4518         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4519         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4520         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4521         j = seed%3 + (seed%3 >= j); seed /= 3; 
4522         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4523         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4524         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4525         j = seed%3;                 seed /= 3; 
4526         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4527         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4528         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4529         j = seed%2 + (seed%2 >= j); seed /= 2; 
4530         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4531         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4532         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4533         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4534         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4535         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4536         put(board, exoPieces[0],    0, 0, ANY);
4537         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4538 }
4539
4540 void
4541 InitPosition(redraw)
4542      int redraw;
4543 {
4544     ChessSquare (* pieces)[BOARD_SIZE];
4545     int i, j, pawnRow, overrule,
4546     oldx = gameInfo.boardWidth,
4547     oldy = gameInfo.boardHeight,
4548     oldh = gameInfo.holdingsWidth,
4549     oldv = gameInfo.variant;
4550
4551     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4552
4553     /* [AS] Initialize pv info list [HGM] and game status */
4554     {
4555         for( i=0; i<MAX_MOVES; i++ ) {
4556             pvInfoList[i].depth = 0;
4557             epStatus[i]=EP_NONE;
4558             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4559         }
4560
4561         initialRulePlies = 0; /* 50-move counter start */
4562
4563         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4564         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4565     }
4566
4567     
4568     /* [HGM] logic here is completely changed. In stead of full positions */
4569     /* the initialized data only consist of the two backranks. The switch */
4570     /* selects which one we will use, which is than copied to the Board   */
4571     /* initialPosition, which for the rest is initialized by Pawns and    */
4572     /* empty squares. This initial position is then copied to boards[0],  */
4573     /* possibly after shuffling, so that it remains available.            */
4574
4575     gameInfo.holdingsWidth = 0; /* default board sizes */
4576     gameInfo.boardWidth    = 8;
4577     gameInfo.boardHeight   = 8;
4578     gameInfo.holdingsSize  = 0;
4579     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4580     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4581     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4582
4583     switch (gameInfo.variant) {
4584     case VariantFischeRandom:
4585       shuffleOpenings = TRUE;
4586     default:
4587       pieces = FIDEArray;
4588       break;
4589     case VariantShatranj:
4590       pieces = ShatranjArray;
4591       nrCastlingRights = 0;
4592       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4593       break;
4594     case VariantTwoKings:
4595       pieces = twoKingsArray;
4596       break;
4597     case VariantCapaRandom:
4598       shuffleOpenings = TRUE;
4599     case VariantCapablanca:
4600       pieces = CapablancaArray;
4601       gameInfo.boardWidth = 10;
4602       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4603       break;
4604     case VariantGothic:
4605       pieces = GothicArray;
4606       gameInfo.boardWidth = 10;
4607       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4608       break;
4609     case VariantJanus:
4610       pieces = JanusArray;
4611       gameInfo.boardWidth = 10;
4612       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4613       nrCastlingRights = 6;
4614         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4615         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4616         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4617         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4618         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4619         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4620       break;
4621     case VariantFalcon:
4622       pieces = FalconArray;
4623       gameInfo.boardWidth = 10;
4624       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4625       break;
4626     case VariantXiangqi:
4627       pieces = XiangqiArray;
4628       gameInfo.boardWidth  = 9;
4629       gameInfo.boardHeight = 10;
4630       nrCastlingRights = 0;
4631       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4632       break;
4633     case VariantShogi:
4634       pieces = ShogiArray;
4635       gameInfo.boardWidth  = 9;
4636       gameInfo.boardHeight = 9;
4637       gameInfo.holdingsSize = 7;
4638       nrCastlingRights = 0;
4639       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4640       break;
4641     case VariantCourier:
4642       pieces = CourierArray;
4643       gameInfo.boardWidth  = 12;
4644       nrCastlingRights = 0;
4645       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4646       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4647       break;
4648     case VariantKnightmate:
4649       pieces = KnightmateArray;
4650       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4651       break;
4652     case VariantFairy:
4653       pieces = fairyArray;
4654       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4655       break;
4656     case VariantGreat:
4657       pieces = GreatArray;
4658       gameInfo.boardWidth = 10;
4659       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4660       gameInfo.holdingsSize = 8;
4661       break;
4662     case VariantSuper:
4663       pieces = FIDEArray;
4664       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4665       gameInfo.holdingsSize = 8;
4666       startedFromSetupPosition = TRUE;
4667       break;
4668     case VariantCrazyhouse:
4669     case VariantBughouse:
4670       pieces = FIDEArray;
4671       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4672       gameInfo.holdingsSize = 5;
4673       break;
4674     case VariantWildCastle:
4675       pieces = FIDEArray;
4676       /* !!?shuffle with kings guaranteed to be on d or e file */
4677       shuffleOpenings = 1;
4678       break;
4679     case VariantNoCastle:
4680       pieces = FIDEArray;
4681       nrCastlingRights = 0;
4682       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4683       /* !!?unconstrained back-rank shuffle */
4684       shuffleOpenings = 1;
4685       break;
4686     }
4687
4688     overrule = 0;
4689     if(appData.NrFiles >= 0) {
4690         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4691         gameInfo.boardWidth = appData.NrFiles;
4692     }
4693     if(appData.NrRanks >= 0) {
4694         gameInfo.boardHeight = appData.NrRanks;
4695     }
4696     if(appData.holdingsSize >= 0) {
4697         i = appData.holdingsSize;
4698         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4699         gameInfo.holdingsSize = i;
4700     }
4701     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4702     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4703         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4704
4705     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4706     if(pawnRow < 1) pawnRow = 1;
4707
4708     /* User pieceToChar list overrules defaults */
4709     if(appData.pieceToCharTable != NULL)
4710         SetCharTable(pieceToChar, appData.pieceToCharTable);
4711
4712     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4713
4714         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4715             s = (ChessSquare) 0; /* account holding counts in guard band */
4716         for( i=0; i<BOARD_HEIGHT; i++ )
4717             initialPosition[i][j] = s;
4718
4719         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4720         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4721         initialPosition[pawnRow][j] = WhitePawn;
4722         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4723         if(gameInfo.variant == VariantXiangqi) {
4724             if(j&1) {
4725                 initialPosition[pawnRow][j] = 
4726                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4727                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4728                    initialPosition[2][j] = WhiteCannon;
4729                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4730                 }
4731             }
4732         }
4733         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4734     }
4735     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4736
4737             j=BOARD_LEFT+1;
4738             initialPosition[1][j] = WhiteBishop;
4739             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4740             j=BOARD_RGHT-2;
4741             initialPosition[1][j] = WhiteRook;
4742             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4743     }
4744
4745     if( nrCastlingRights == -1) {
4746         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4747         /*       This sets default castling rights from none to normal corners   */
4748         /* Variants with other castling rights must set them themselves above    */
4749         nrCastlingRights = 6;
4750        
4751         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4752         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4753         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4754         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4755         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4756         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4757      }
4758
4759      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4760      if(gameInfo.variant == VariantGreat) { // promotion commoners
4761         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4762         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4763         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4764         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4765      }
4766   if (appData.debugMode) {
4767     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4768   }
4769     if(shuffleOpenings) {
4770         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4771         startedFromSetupPosition = TRUE;
4772     }
4773     if(startedFromPositionFile) {
4774       /* [HGM] loadPos: use PositionFile for every new game */
4775       CopyBoard(initialPosition, filePosition);
4776       for(i=0; i<nrCastlingRights; i++)
4777           castlingRights[0][i] = initialRights[i] = fileRights[i];
4778       startedFromSetupPosition = TRUE;
4779     }
4780
4781     CopyBoard(boards[0], initialPosition);
4782
4783     if(oldx != gameInfo.boardWidth ||
4784        oldy != gameInfo.boardHeight ||
4785        oldh != gameInfo.holdingsWidth
4786 #ifdef GOTHIC
4787        || oldv == VariantGothic ||        // For licensing popups
4788        gameInfo.variant == VariantGothic
4789 #endif
4790 #ifdef FALCON
4791        || oldv == VariantFalcon ||
4792        gameInfo.variant == VariantFalcon
4793 #endif
4794                                          )
4795             InitDrawingSizes(-2 ,0);
4796
4797     if (redraw)
4798       DrawPosition(TRUE, boards[currentMove]);
4799 }
4800
4801 void
4802 SendBoard(cps, moveNum)
4803      ChessProgramState *cps;
4804      int moveNum;
4805 {
4806     char message[MSG_SIZ];
4807     
4808     if (cps->useSetboard) {
4809       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4810       sprintf(message, "setboard %s\n", fen);
4811       SendToProgram(message, cps);
4812       free(fen);
4813
4814     } else {
4815       ChessSquare *bp;
4816       int i, j;
4817       /* Kludge to set black to move, avoiding the troublesome and now
4818        * deprecated "black" command.
4819        */
4820       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4821
4822       SendToProgram("edit\n", cps);
4823       SendToProgram("#\n", cps);
4824       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4825         bp = &boards[moveNum][i][BOARD_LEFT];
4826         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4827           if ((int) *bp < (int) BlackPawn) {
4828             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4829                     AAA + j, ONE + i);
4830             if(message[0] == '+' || message[0] == '~') {
4831                 sprintf(message, "%c%c%c+\n",
4832                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4833                         AAA + j, ONE + i);
4834             }
4835             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4836                 message[1] = BOARD_RGHT   - 1 - j + '1';
4837                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4838             }
4839             SendToProgram(message, cps);
4840           }
4841         }
4842       }
4843     
4844       SendToProgram("c\n", cps);
4845       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4846         bp = &boards[moveNum][i][BOARD_LEFT];
4847         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4848           if (((int) *bp != (int) EmptySquare)
4849               && ((int) *bp >= (int) BlackPawn)) {
4850             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4851                     AAA + j, ONE + i);
4852             if(message[0] == '+' || message[0] == '~') {
4853                 sprintf(message, "%c%c%c+\n",
4854                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4855                         AAA + j, ONE + i);
4856             }
4857             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4858                 message[1] = BOARD_RGHT   - 1 - j + '1';
4859                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4860             }
4861             SendToProgram(message, cps);
4862           }
4863         }
4864       }
4865     
4866       SendToProgram(".\n", cps);
4867     }
4868     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4869 }
4870
4871 int
4872 IsPromotion(fromX, fromY, toX, toY)
4873      int fromX, fromY, toX, toY;
4874 {
4875     /* [HGM] add Shogi promotions */
4876     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4877     ChessSquare piece;
4878
4879     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4880       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4881    /* [HGM] Note to self: line above also weeds out drops */
4882     piece = boards[currentMove][fromY][fromX];
4883     if(gameInfo.variant == VariantShogi) {
4884         promotionZoneSize = 3;
4885         highestPromotingPiece = (int)WhiteKing;
4886         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4887            and if in normal chess we then allow promotion to King, why not
4888            allow promotion of other piece in Shogi?                         */
4889     }
4890     if((int)piece >= BlackPawn) {
4891         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4892              return FALSE;
4893         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4894     } else {
4895         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4896            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4897     }
4898     return ( (int)piece <= highestPromotingPiece );
4899 }
4900
4901 int
4902 InPalace(row, column)
4903      int row, column;
4904 {   /* [HGM] for Xiangqi */
4905     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4906          column < (BOARD_WIDTH + 4)/2 &&
4907          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4908     return FALSE;
4909 }
4910
4911 int
4912 PieceForSquare (x, y)
4913      int x;
4914      int y;
4915 {
4916   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4917      return -1;
4918   else
4919      return boards[currentMove][y][x];
4920 }
4921
4922 int
4923 OKToStartUserMove(x, y)
4924      int x, y;
4925 {
4926     ChessSquare from_piece;
4927     int white_piece;
4928
4929     if (matchMode) return FALSE;
4930     if (gameMode == EditPosition) return TRUE;
4931
4932     if (x >= 0 && y >= 0)
4933       from_piece = boards[currentMove][y][x];
4934     else
4935       from_piece = EmptySquare;
4936
4937     if (from_piece == EmptySquare) return FALSE;
4938
4939     white_piece = (int)from_piece >= (int)WhitePawn &&
4940       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4941
4942     switch (gameMode) {
4943       case PlayFromGameFile:
4944       case AnalyzeFile:
4945       case TwoMachinesPlay:
4946       case EndOfGame:
4947         return FALSE;
4948
4949       case IcsObserving:
4950       case IcsIdle:
4951         return FALSE;
4952
4953       case MachinePlaysWhite:
4954       case IcsPlayingBlack:
4955         if (appData.zippyPlay) return FALSE;
4956         if (white_piece) {
4957             DisplayMoveError(_("You are playing Black"));
4958             return FALSE;
4959         }
4960         break;
4961
4962       case MachinePlaysBlack:
4963       case IcsPlayingWhite:
4964         if (appData.zippyPlay) return FALSE;
4965         if (!white_piece) {
4966             DisplayMoveError(_("You are playing White"));
4967             return FALSE;
4968         }
4969         break;
4970
4971       case EditGame:
4972         if (!white_piece && WhiteOnMove(currentMove)) {
4973             DisplayMoveError(_("It is White's turn"));
4974             return FALSE;
4975         }           
4976         if (white_piece && !WhiteOnMove(currentMove)) {
4977             DisplayMoveError(_("It is Black's turn"));
4978             return FALSE;
4979         }           
4980         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4981             /* Editing correspondence game history */
4982             /* Could disallow this or prompt for confirmation */
4983             cmailOldMove = -1;
4984         }
4985         if (currentMove < forwardMostMove) {
4986             /* Discarding moves */
4987             /* Could prompt for confirmation here,
4988                but I don't think that's such a good idea */
4989             forwardMostMove = currentMove;
4990         }
4991         break;
4992
4993       case BeginningOfGame:
4994         if (appData.icsActive) return FALSE;
4995         if (!appData.noChessProgram) {
4996             if (!white_piece) {
4997                 DisplayMoveError(_("You are playing White"));
4998                 return FALSE;
4999             }
5000         }
5001         break;
5002         
5003       case Training:
5004         if (!white_piece && WhiteOnMove(currentMove)) {
5005             DisplayMoveError(_("It is White's turn"));
5006             return FALSE;
5007         }           
5008         if (white_piece && !WhiteOnMove(currentMove)) {
5009             DisplayMoveError(_("It is Black's turn"));
5010             return FALSE;
5011         }           
5012         break;
5013
5014       default:
5015       case IcsExamining:
5016         break;
5017     }
5018     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5019         && gameMode != AnalyzeFile && gameMode != Training) {
5020         DisplayMoveError(_("Displayed position is not current"));
5021         return FALSE;
5022     }
5023     return TRUE;
5024 }
5025
5026 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5027 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5028 int lastLoadGameUseList = FALSE;
5029 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5030 ChessMove lastLoadGameStart = (ChessMove) 0;
5031
5032 ChessMove
5033 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5034      int fromX, fromY, toX, toY;
5035      int promoChar;
5036      Boolean captureOwn;
5037 {
5038     ChessMove moveType;
5039     ChessSquare pdown, pup;
5040
5041     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5042
5043     /* [HGM] suppress all moves into holdings area and guard band */
5044     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5045             return ImpossibleMove;
5046
5047     /* [HGM] <sameColor> moved to here from winboard.c */
5048     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5049     pdown = boards[currentMove][fromY][fromX];
5050     pup = boards[currentMove][toY][toX];
5051     if (    gameMode != EditPosition && !captureOwn &&
5052             (WhitePawn <= pdown && pdown < BlackPawn &&
5053              WhitePawn <= pup && pup < BlackPawn  ||
5054              BlackPawn <= pdown && pdown < EmptySquare &&
5055              BlackPawn <= pup && pup < EmptySquare 
5056             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5057                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5058                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5059                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5060                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5061         )           )
5062          return Comment;
5063
5064     /* Check if the user is playing in turn.  This is complicated because we
5065        let the user "pick up" a piece before it is his turn.  So the piece he
5066        tried to pick up may have been captured by the time he puts it down!
5067        Therefore we use the color the user is supposed to be playing in this
5068        test, not the color of the piece that is currently on the starting
5069        square---except in EditGame mode, where the user is playing both
5070        sides; fortunately there the capture race can't happen.  (It can
5071        now happen in IcsExamining mode, but that's just too bad.  The user
5072        will get a somewhat confusing message in that case.)
5073        */
5074
5075     switch (gameMode) {
5076       case PlayFromGameFile:
5077       case AnalyzeFile:
5078       case TwoMachinesPlay:
5079       case EndOfGame:
5080       case IcsObserving:
5081       case IcsIdle:
5082         /* We switched into a game mode where moves are not accepted,
5083            perhaps while the mouse button was down. */
5084         return ImpossibleMove;
5085
5086       case MachinePlaysWhite:
5087         /* User is moving for Black */
5088         if (WhiteOnMove(currentMove)) {
5089             DisplayMoveError(_("It is White's turn"));
5090             return ImpossibleMove;
5091         }
5092         break;
5093
5094       case MachinePlaysBlack:
5095         /* User is moving for White */
5096         if (!WhiteOnMove(currentMove)) {
5097             DisplayMoveError(_("It is Black's turn"));
5098             return ImpossibleMove;
5099         }
5100         break;
5101
5102       case EditGame:
5103       case IcsExamining:
5104       case BeginningOfGame:
5105       case AnalyzeMode:
5106       case Training:
5107         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5108             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5109             /* User is moving for Black */
5110             if (WhiteOnMove(currentMove)) {
5111                 DisplayMoveError(_("It is White's turn"));
5112                 return ImpossibleMove;
5113             }
5114         } else {
5115             /* User is moving for White */
5116             if (!WhiteOnMove(currentMove)) {
5117                 DisplayMoveError(_("It is Black's turn"));
5118                 return ImpossibleMove;
5119             }
5120         }
5121         break;
5122
5123       case IcsPlayingBlack:
5124         /* User is moving for Black */
5125         if (WhiteOnMove(currentMove)) {
5126             if (!appData.premove) {
5127                 DisplayMoveError(_("It is White's turn"));
5128             } else if (toX >= 0 && toY >= 0) {
5129                 premoveToX = toX;
5130                 premoveToY = toY;
5131                 premoveFromX = fromX;
5132                 premoveFromY = fromY;
5133                 premovePromoChar = promoChar;
5134                 gotPremove = 1;
5135                 if (appData.debugMode) 
5136                     fprintf(debugFP, "Got premove: fromX %d,"
5137                             "fromY %d, toX %d, toY %d\n",
5138                             fromX, fromY, toX, toY);
5139             }
5140             return ImpossibleMove;
5141         }
5142         break;
5143
5144       case IcsPlayingWhite:
5145         /* User is moving for White */
5146         if (!WhiteOnMove(currentMove)) {
5147             if (!appData.premove) {
5148                 DisplayMoveError(_("It is Black's turn"));
5149             } else if (toX >= 0 && toY >= 0) {
5150                 premoveToX = toX;
5151                 premoveToY = toY;
5152                 premoveFromX = fromX;
5153                 premoveFromY = fromY;
5154                 premovePromoChar = promoChar;
5155                 gotPremove = 1;
5156                 if (appData.debugMode) 
5157                     fprintf(debugFP, "Got premove: fromX %d,"
5158                             "fromY %d, toX %d, toY %d\n",
5159                             fromX, fromY, toX, toY);
5160             }
5161             return ImpossibleMove;
5162         }
5163         break;
5164
5165       default:
5166         break;
5167
5168       case EditPosition:
5169         /* EditPosition, empty square, or different color piece;
5170            click-click move is possible */
5171         if (toX == -2 || toY == -2) {
5172             boards[0][fromY][fromX] = EmptySquare;
5173             return AmbiguousMove;
5174         } else if (toX >= 0 && toY >= 0) {
5175             boards[0][toY][toX] = boards[0][fromY][fromX];
5176             boards[0][fromY][fromX] = EmptySquare;
5177             return AmbiguousMove;
5178         }
5179         return ImpossibleMove;
5180     }
5181
5182     /* [HGM] If move started in holdings, it means a drop */
5183     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5184          if( pup != EmptySquare ) return ImpossibleMove;
5185          if(appData.testLegality) {
5186              /* it would be more logical if LegalityTest() also figured out
5187               * which drops are legal. For now we forbid pawns on back rank.
5188               * Shogi is on its own here...
5189               */
5190              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5191                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5192                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5193          }
5194          return WhiteDrop; /* Not needed to specify white or black yet */
5195     }
5196
5197     userOfferedDraw = FALSE;
5198         
5199     /* [HGM] always test for legality, to get promotion info */
5200     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5201                           epStatus[currentMove], castlingRights[currentMove],
5202                                          fromY, fromX, toY, toX, promoChar);
5203     /* [HGM] but possibly ignore an IllegalMove result */
5204     if (appData.testLegality) {
5205         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5206             DisplayMoveError(_("Illegal move"));
5207             return ImpossibleMove;
5208         }
5209     }
5210 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5211     return moveType;
5212     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5213        function is made into one that returns an OK move type if FinishMove
5214        should be called. This to give the calling driver routine the
5215        opportunity to finish the userMove input with a promotion popup,
5216        without bothering the user with this for invalid or illegal moves */
5217
5218 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5219 }
5220
5221 /* Common tail of UserMoveEvent and DropMenuEvent */
5222 int
5223 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5224      ChessMove moveType;
5225      int fromX, fromY, toX, toY;
5226      /*char*/int promoChar;
5227 {
5228     char *bookHit = 0;
5229 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5230     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5231         // [HGM] superchess: suppress promotions to non-available piece
5232         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5233         if(WhiteOnMove(currentMove)) {
5234             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5235         } else {
5236             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5237         }
5238     }
5239
5240     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5241        move type in caller when we know the move is a legal promotion */
5242     if(moveType == NormalMove && promoChar)
5243         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5244 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5245     /* [HGM] convert drag-and-drop piece drops to standard form */
5246     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5247          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5248            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5249                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5250 //         fromX = boards[currentMove][fromY][fromX];
5251            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5252            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5253            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5254            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5255          fromY = DROP_RANK;
5256     }
5257
5258     /* [HGM] <popupFix> The following if has been moved here from
5259        UserMoveEvent(). Because it seemed to belon here (why not allow
5260        piece drops in training games?), and because it can only be
5261        performed after it is known to what we promote. */
5262     if (gameMode == Training) {
5263       /* compare the move played on the board to the next move in the
5264        * game. If they match, display the move and the opponent's response. 
5265        * If they don't match, display an error message.
5266        */
5267       int saveAnimate;
5268       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5269       CopyBoard(testBoard, boards[currentMove]);
5270       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5271
5272       if (CompareBoards(testBoard, boards[currentMove+1])) {
5273         ForwardInner(currentMove+1);
5274
5275         /* Autoplay the opponent's response.
5276          * if appData.animate was TRUE when Training mode was entered,
5277          * the response will be animated.
5278          */
5279         saveAnimate = appData.animate;
5280         appData.animate = animateTraining;
5281         ForwardInner(currentMove+1);
5282         appData.animate = saveAnimate;
5283
5284         /* check for the end of the game */
5285         if (currentMove >= forwardMostMove) {
5286           gameMode = PlayFromGameFile;
5287           ModeHighlight();
5288           SetTrainingModeOff();
5289           DisplayInformation(_("End of game"));
5290         }
5291       } else {
5292         DisplayError(_("Incorrect move"), 0);
5293       }
5294       return 1;
5295     }
5296
5297   /* Ok, now we know that the move is good, so we can kill
5298      the previous line in Analysis Mode */
5299   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5300     forwardMostMove = currentMove;
5301   }
5302
5303   /* If we need the chess program but it's dead, restart it */
5304   ResurrectChessProgram();
5305
5306   /* A user move restarts a paused game*/
5307   if (pausing)
5308     PauseEvent();
5309
5310   thinkOutput[0] = NULLCHAR;
5311
5312   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5313
5314   if (gameMode == BeginningOfGame) {
5315     if (appData.noChessProgram) {
5316       gameMode = EditGame;
5317       SetGameInfo();
5318     } else {
5319       char buf[MSG_SIZ];
5320       gameMode = MachinePlaysBlack;
5321       StartClocks();
5322       SetGameInfo();
5323       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5324       DisplayTitle(buf);
5325       if (first.sendName) {
5326         sprintf(buf, "name %s\n", gameInfo.white);
5327         SendToProgram(buf, &first);
5328       }
5329       StartClocks();
5330     }
5331     ModeHighlight();
5332   }
5333 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5334   /* Relay move to ICS or chess engine */
5335   if (appData.icsActive) {
5336     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5337         gameMode == IcsExamining) {
5338       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5339       ics_user_moved = 1;
5340     }
5341   } else {
5342     if (first.sendTime && (gameMode == BeginningOfGame ||
5343                            gameMode == MachinePlaysWhite ||
5344                            gameMode == MachinePlaysBlack)) {
5345       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5346     }
5347     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5348          // [HGM] book: if program might be playing, let it use book
5349         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5350         first.maybeThinking = TRUE;
5351     } else SendMoveToProgram(forwardMostMove-1, &first);
5352     if (currentMove == cmailOldMove + 1) {
5353       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5354     }
5355   }
5356
5357   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5358
5359   switch (gameMode) {
5360   case EditGame:
5361     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5362                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5363     case MT_NONE:
5364     case MT_CHECK:
5365       break;
5366     case MT_CHECKMATE:
5367     case MT_STAINMATE:
5368       if (WhiteOnMove(currentMove)) {
5369         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5370       } else {
5371         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5372       }
5373       break;
5374     case MT_STALEMATE:
5375       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5376       break;
5377     }
5378     break;
5379     
5380   case MachinePlaysBlack:
5381   case MachinePlaysWhite:
5382     /* disable certain menu options while machine is thinking */
5383     SetMachineThinkingEnables();
5384     break;
5385
5386   default:
5387     break;
5388   }
5389
5390   if(bookHit) { // [HGM] book: simulate book reply
5391         static char bookMove[MSG_SIZ]; // a bit generous?
5392
5393         programStats.nodes = programStats.depth = programStats.time = 
5394         programStats.score = programStats.got_only_move = 0;
5395         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5396
5397         strcpy(bookMove, "move ");
5398         strcat(bookMove, bookHit);
5399         HandleMachineMove(bookMove, &first);
5400   }
5401   return 1;
5402 }
5403
5404 void
5405 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5406      int fromX, fromY, toX, toY;
5407      int promoChar;
5408 {
5409     /* [HGM] This routine was added to allow calling of its two logical
5410        parts from other modules in the old way. Before, UserMoveEvent()
5411        automatically called FinishMove() if the move was OK, and returned
5412        otherwise. I separated the two, in order to make it possible to
5413        slip a promotion popup in between. But that it always needs two
5414        calls, to the first part, (now called UserMoveTest() ), and to
5415        FinishMove if the first part succeeded. Calls that do not need
5416        to do anything in between, can call this routine the old way. 
5417     */
5418     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5419 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5420     if(moveType == AmbiguousMove)
5421         DrawPosition(FALSE, boards[currentMove]);
5422     else if(moveType != ImpossibleMove && moveType != Comment)
5423         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5424 }
5425
5426 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5427 {
5428 //    char * hint = lastHint;
5429     FrontEndProgramStats stats;
5430
5431     stats.which = cps == &first ? 0 : 1;
5432     stats.depth = cpstats->depth;
5433     stats.nodes = cpstats->nodes;
5434     stats.score = cpstats->score;
5435     stats.time = cpstats->time;
5436     stats.pv = cpstats->movelist;
5437     stats.hint = lastHint;
5438     stats.an_move_index = 0;
5439     stats.an_move_count = 0;
5440
5441     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5442         stats.hint = cpstats->move_name;
5443         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5444         stats.an_move_count = cpstats->nr_moves;
5445     }
5446
5447     SetProgramStats( &stats );
5448 }
5449
5450 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5451 {   // [HGM] book: this routine intercepts moves to simulate book replies
5452     char *bookHit = NULL;
5453
5454     //first determine if the incoming move brings opponent into his book
5455     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5456         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5457     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5458     if(bookHit != NULL && !cps->bookSuspend) {
5459         // make sure opponent is not going to reply after receiving move to book position
5460         SendToProgram("force\n", cps);
5461         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5462     }
5463     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5464     // now arrange restart after book miss
5465     if(bookHit) {
5466         // after a book hit we never send 'go', and the code after the call to this routine
5467         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5468         char buf[MSG_SIZ];
5469         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5470         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5471         SendToProgram(buf, cps);
5472         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5473     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5474         SendToProgram("go\n", cps);
5475         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5476     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5477         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5478             SendToProgram("go\n", cps); 
5479         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5480     }
5481     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5482 }
5483
5484 char *savedMessage;
5485 ChessProgramState *savedState;
5486 void DeferredBookMove(void)
5487 {
5488         if(savedState->lastPing != savedState->lastPong)
5489                     ScheduleDelayedEvent(DeferredBookMove, 10);
5490         else
5491         HandleMachineMove(savedMessage, savedState);
5492 }
5493
5494 void
5495 HandleMachineMove(message, cps)
5496      char *message;
5497      ChessProgramState *cps;
5498 {
5499     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5500     char realname[MSG_SIZ];
5501     int fromX, fromY, toX, toY;
5502     ChessMove moveType;
5503     char promoChar;
5504     char *p;
5505     int machineWhite;
5506     char *bookHit;
5507
5508 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5509     /*
5510      * Kludge to ignore BEL characters
5511      */
5512     while (*message == '\007') message++;
5513
5514     /*
5515      * [HGM] engine debug message: ignore lines starting with '#' character
5516      */
5517     if(cps->debug && *message == '#') return;
5518
5519     /*
5520      * Look for book output
5521      */
5522     if (cps == &first && bookRequested) {
5523         if (message[0] == '\t' || message[0] == ' ') {
5524             /* Part of the book output is here; append it */
5525             strcat(bookOutput, message);
5526             strcat(bookOutput, "  \n");
5527             return;
5528         } else if (bookOutput[0] != NULLCHAR) {
5529             /* All of book output has arrived; display it */
5530             char *p = bookOutput;
5531             while (*p != NULLCHAR) {
5532                 if (*p == '\t') *p = ' ';
5533                 p++;
5534             }
5535             DisplayInformation(bookOutput);
5536             bookRequested = FALSE;
5537             /* Fall through to parse the current output */
5538         }
5539     }
5540
5541     /*
5542      * Look for machine move.
5543      */
5544     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5545         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5546     {
5547         /* This method is only useful on engines that support ping */
5548         if (cps->lastPing != cps->lastPong) {
5549           if (gameMode == BeginningOfGame) {
5550             /* Extra move from before last new; ignore */
5551             if (appData.debugMode) {
5552                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5553             }
5554           } else {
5555             if (appData.debugMode) {
5556                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5557                         cps->which, gameMode);
5558             }
5559
5560             SendToProgram("undo\n", cps);
5561           }
5562           return;
5563         }
5564
5565         switch (gameMode) {
5566           case BeginningOfGame:
5567             /* Extra move from before last reset; ignore */
5568             if (appData.debugMode) {
5569                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5570             }
5571             return;
5572
5573           case EndOfGame:
5574           case IcsIdle:
5575           default:
5576             /* Extra move after we tried to stop.  The mode test is
5577                not a reliable way of detecting this problem, but it's
5578                the best we can do on engines that don't support ping.
5579             */
5580             if (appData.debugMode) {
5581                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5582                         cps->which, gameMode);
5583             }
5584             SendToProgram("undo\n", cps);
5585             return;
5586
5587           case MachinePlaysWhite:
5588           case IcsPlayingWhite:
5589             machineWhite = TRUE;
5590             break;
5591
5592           case MachinePlaysBlack:
5593           case IcsPlayingBlack:
5594             machineWhite = FALSE;
5595             break;
5596
5597           case TwoMachinesPlay:
5598             machineWhite = (cps->twoMachinesColor[0] == 'w');
5599             break;
5600         }
5601         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5602             if (appData.debugMode) {
5603                 fprintf(debugFP,
5604                         "Ignoring move out of turn by %s, gameMode %d"
5605                         ", forwardMost %d\n",
5606                         cps->which, gameMode, forwardMostMove);
5607             }
5608             return;
5609         }
5610
5611     if (appData.debugMode) { int f = forwardMostMove;
5612         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5613                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5614     }
5615         if(cps->alphaRank) AlphaRank(machineMove, 4);
5616         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5617                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5618             /* Machine move could not be parsed; ignore it. */
5619             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5620                     machineMove, cps->which);
5621             DisplayError(buf1, 0);
5622             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5623                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5624             if (gameMode == TwoMachinesPlay) {
5625               GameEnds(machineWhite ? BlackWins : WhiteWins,
5626                        buf1, GE_XBOARD);
5627             }
5628             return;
5629         }
5630
5631         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5632         /* So we have to redo legality test with true e.p. status here,  */
5633         /* to make sure an illegal e.p. capture does not slip through,   */
5634         /* to cause a forfeit on a justified illegal-move complaint      */
5635         /* of the opponent.                                              */
5636         if( gameMode==TwoMachinesPlay && appData.testLegality
5637             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5638                                                               ) {
5639            ChessMove moveType;
5640            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5641                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5642                              fromY, fromX, toY, toX, promoChar);
5643             if (appData.debugMode) {
5644                 int i;
5645                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5646                     castlingRights[forwardMostMove][i], castlingRank[i]);
5647                 fprintf(debugFP, "castling rights\n");
5648             }
5649             if(moveType == IllegalMove) {
5650                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5651                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5652                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5653                            buf1, GE_XBOARD);
5654                 return;
5655            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5656            /* [HGM] Kludge to handle engines that send FRC-style castling
5657               when they shouldn't (like TSCP-Gothic) */
5658            switch(moveType) {
5659              case WhiteASideCastleFR:
5660              case BlackASideCastleFR:
5661                toX+=2;
5662                currentMoveString[2]++;
5663                break;
5664              case WhiteHSideCastleFR:
5665              case BlackHSideCastleFR:
5666                toX--;
5667                currentMoveString[2]--;
5668                break;
5669              default: ; // nothing to do, but suppresses warning of pedantic compilers
5670            }
5671         }
5672         hintRequested = FALSE;
5673         lastHint[0] = NULLCHAR;
5674         bookRequested = FALSE;
5675         /* Program may be pondering now */
5676         cps->maybeThinking = TRUE;
5677         if (cps->sendTime == 2) cps->sendTime = 1;
5678         if (cps->offeredDraw) cps->offeredDraw--;
5679
5680 #if ZIPPY
5681         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5682             first.initDone) {
5683           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5684           ics_user_moved = 1;
5685           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5686                 char buf[3*MSG_SIZ];
5687
5688                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5689                         programStats.score / 100.,
5690                         programStats.depth,
5691                         programStats.time / 100.,
5692                         (unsigned int)programStats.nodes,
5693                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5694                         programStats.movelist);
5695                 SendToICS(buf);
5696 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5697           }
5698         }
5699 #endif
5700         /* currentMoveString is set as a side-effect of ParseOneMove */
5701         strcpy(machineMove, currentMoveString);
5702         strcat(machineMove, "\n");
5703         strcpy(moveList[forwardMostMove], machineMove);
5704
5705         /* [AS] Save move info and clear stats for next move */
5706         pvInfoList[ forwardMostMove ].score = programStats.score;
5707         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5708         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5709         ClearProgramStats();
5710         thinkOutput[0] = NULLCHAR;
5711         hiddenThinkOutputState = 0;
5712
5713         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5714
5715         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5716         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5717             int count = 0;
5718
5719             while( count < adjudicateLossPlies ) {
5720                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5721
5722                 if( count & 1 ) {
5723                     score = -score; /* Flip score for winning side */
5724                 }
5725
5726                 if( score > adjudicateLossThreshold ) {
5727                     break;
5728                 }
5729
5730                 count++;
5731             }
5732
5733             if( count >= adjudicateLossPlies ) {
5734                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5735
5736                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5737                     "Xboard adjudication", 
5738                     GE_XBOARD );
5739
5740                 return;
5741             }
5742         }
5743
5744         if( gameMode == TwoMachinesPlay ) {
5745           // [HGM] some adjudications useful with buggy engines
5746             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5747           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5748
5749
5750             if( appData.testLegality )
5751             {   /* [HGM] Some more adjudications for obstinate engines */
5752                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5753                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5754                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5755                 static int moveCount = 6;
5756                 ChessMove result;
5757                 char *reason = NULL;
5758
5759                 /* Count what is on board. */
5760                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5761                 {   ChessSquare p = boards[forwardMostMove][i][j];
5762                     int m=i;
5763
5764                     switch((int) p)
5765                     {   /* count B,N,R and other of each side */
5766                         case WhiteKing:
5767                         case BlackKing:
5768                              NrK++; break; // [HGM] atomic: count Kings
5769                         case WhiteKnight:
5770                              NrWN++; break;
5771                         case WhiteBishop:
5772                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5773                              bishopsColor |= 1 << ((i^j)&1);
5774                              NrWB++; break;
5775                         case BlackKnight:
5776                              NrBN++; break;
5777                         case BlackBishop:
5778                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5779                              bishopsColor |= 1 << ((i^j)&1);
5780                              NrBB++; break;
5781                         case WhiteRook:
5782                              NrWR++; break;
5783                         case BlackRook:
5784                              NrBR++; break;
5785                         case WhiteQueen:
5786                              NrWQ++; break;
5787                         case BlackQueen:
5788                              NrBQ++; break;
5789                         case EmptySquare: 
5790                              break;
5791                         case BlackPawn:
5792                              m = 7-i;
5793                         case WhitePawn:
5794                              PawnAdvance += m; NrPawns++;
5795                     }
5796                     NrPieces += (p != EmptySquare);
5797                     NrW += ((int)p < (int)BlackPawn);
5798                     if(gameInfo.variant == VariantXiangqi && 
5799                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5800                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5801                         NrW -= ((int)p < (int)BlackPawn);
5802                     }
5803                 }
5804
5805                 /* Some material-based adjudications that have to be made before stalemate test */
5806                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5807                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5808                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5809                      if(appData.checkMates) {
5810                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5811                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5812                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5813                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5814                          return;
5815                      }
5816                 }
5817
5818                 /* Bare King in Shatranj (loses) or Losers (wins) */
5819                 if( NrW == 1 || NrPieces - NrW == 1) {
5820                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5821                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5822                      if(appData.checkMates) {
5823                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5824                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5825                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5826                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5827                          return;
5828                      }
5829                   } else
5830                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5831                   {    /* bare King */
5832                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5833                         if(appData.checkMates) {
5834                             /* but only adjudicate if adjudication enabled */
5835                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5836                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5837                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5838                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5839                             return;
5840                         }
5841                   }
5842                 } else bare = 1;
5843
5844
5845             // don't wait for engine to announce game end if we can judge ourselves
5846             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5847                                        castlingRights[forwardMostMove]) ) {
5848               case MT_CHECK:
5849                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5850                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5851                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5852                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5853                             checkCnt++;
5854                         if(checkCnt >= 2) {
5855                             reason = "Xboard adjudication: 3rd check";
5856                             epStatus[forwardMostMove] = EP_CHECKMATE;
5857                             break;
5858                         }
5859                     }
5860                 }
5861               case MT_NONE:
5862               default:
5863                 break;
5864               case MT_STALEMATE:
5865               case MT_STAINMATE:
5866                 reason = "Xboard adjudication: Stalemate";
5867                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5868                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5869                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5870                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5871                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5872                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5873                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5874                                                                         EP_CHECKMATE : EP_WINS);
5875                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5876                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5877                 }
5878                 break;
5879               case MT_CHECKMATE:
5880                 reason = "Xboard adjudication: Checkmate";
5881                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5882                 break;
5883             }
5884
5885                 switch(i = epStatus[forwardMostMove]) {
5886                     case EP_STALEMATE:
5887                         result = GameIsDrawn; break;
5888                     case EP_CHECKMATE:
5889                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5890                     case EP_WINS:
5891                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5892                     default:
5893                         result = (ChessMove) 0;
5894                 }
5895                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5896                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5897                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5898                     GameEnds( result, reason, GE_XBOARD );
5899                     return;
5900                 }
5901
5902                 /* Next absolutely insufficient mating material. */
5903                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5904                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5905                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5906                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5907                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5908
5909                      /* always flag draws, for judging claims */
5910                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5911
5912                      if(appData.materialDraws) {
5913                          /* but only adjudicate them if adjudication enabled */
5914                          SendToProgram("force\n", cps->other); // suppress reply
5915                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5916                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5917                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5918                          return;
5919                      }
5920                 }
5921
5922                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5923                 if(NrPieces == 4 && 
5924                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5925                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5926                    || NrWN==2 || NrBN==2     /* KNNK */
5927                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5928                   ) ) {
5929                      if(--moveCount < 0 && appData.trivialDraws)
5930                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5931                           SendToProgram("force\n", cps->other); // suppress reply
5932                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5933                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5934                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5935                           return;
5936                      }
5937                 } else moveCount = 6;
5938             }
5939           }
5940           
5941           if (appData.debugMode) { int i;
5942             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5943                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5944                     appData.drawRepeats);
5945             for( i=forwardMostMove; i>=backwardMostMove; i-- )
5946               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5947             
5948           }
5949
5950                 /* Check for rep-draws */
5951                 count = 0;
5952                 for(k = forwardMostMove-2;
5953                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5954                         epStatus[k] < EP_UNKNOWN &&
5955                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5956                     k-=2)
5957                 {   int rights=0;
5958                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5959                         /* compare castling rights */
5960                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5961                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5962                                 rights++; /* King lost rights, while rook still had them */
5963                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5964                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5965                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5966                                    rights++; /* but at least one rook lost them */
5967                         }
5968                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5969                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5970                                 rights++; 
5971                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5972                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5973                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5974                                    rights++;
5975                         }
5976                         if( rights == 0 && ++count > appData.drawRepeats-2
5977                             && appData.drawRepeats > 1) {
5978                              /* adjudicate after user-specified nr of repeats */
5979                              SendToProgram("force\n", cps->other); // suppress reply
5980                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5981                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5982                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5983                                 // [HGM] xiangqi: check for forbidden perpetuals
5984                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5985                                 for(m=forwardMostMove; m>k; m-=2) {
5986                                     if(MateTest(boards[m], PosFlags(m), 
5987                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5988                                         ourPerpetual = 0; // the current mover did not always check
5989                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5990                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5991                                         hisPerpetual = 0; // the opponent did not always check
5992                                 }
5993                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5994                                                                         ourPerpetual, hisPerpetual);
5995                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5996                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5997                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5998                                     return;
5999                                 }
6000                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6001                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6002                                 // Now check for perpetual chases
6003                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6004                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6005                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6006                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6007                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6008                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6009                                         return;
6010                                     }
6011                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6012                                         break; // Abort repetition-checking loop.
6013                                 }
6014                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6015                              }
6016                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6017                              return;
6018                         }
6019                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6020                              epStatus[forwardMostMove] = EP_REP_DRAW;
6021                     }
6022                 }
6023
6024                 /* Now we test for 50-move draws. Determine ply count */
6025                 count = forwardMostMove;
6026                 /* look for last irreversble move */
6027                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6028                     count--;
6029                 /* if we hit starting position, add initial plies */
6030                 if( count == backwardMostMove )
6031                     count -= initialRulePlies;
6032                 count = forwardMostMove - count; 
6033                 if( count >= 100)
6034                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6035                          /* this is used to judge if draw claims are legal */
6036                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6037                          SendToProgram("force\n", cps->other); // suppress reply
6038                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6039                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6040                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6041                          return;
6042                 }
6043
6044                 /* if draw offer is pending, treat it as a draw claim
6045                  * when draw condition present, to allow engines a way to
6046                  * claim draws before making their move to avoid a race
6047                  * condition occurring after their move
6048                  */
6049                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6050                          char *p = NULL;
6051                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6052                              p = "Draw claim: 50-move rule";
6053                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6054                              p = "Draw claim: 3-fold repetition";
6055                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6056                              p = "Draw claim: insufficient mating material";
6057                          if( p != NULL ) {
6058                              SendToProgram("force\n", cps->other); // suppress reply
6059                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6060                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6061                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6062                              return;
6063                          }
6064                 }
6065
6066
6067                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6068                     SendToProgram("force\n", cps->other); // suppress reply
6069                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6070                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6071
6072                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6073
6074                     return;
6075                 }
6076         }
6077
6078         bookHit = NULL;
6079         if (gameMode == TwoMachinesPlay) {
6080             /* [HGM] relaying draw offers moved to after reception of move */
6081             /* and interpreting offer as claim if it brings draw condition */
6082             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6083                 SendToProgram("draw\n", cps->other);
6084             }
6085             if (cps->other->sendTime) {
6086                 SendTimeRemaining(cps->other,
6087                                   cps->other->twoMachinesColor[0] == 'w');
6088             }
6089             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6090             if (firstMove && !bookHit) {
6091                 firstMove = FALSE;
6092                 if (cps->other->useColors) {
6093                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6094                 }
6095                 SendToProgram("go\n", cps->other);
6096             }
6097             cps->other->maybeThinking = TRUE;
6098         }
6099
6100         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6101         
6102         if (!pausing && appData.ringBellAfterMoves) {
6103             RingBell();
6104         }
6105
6106         /* 
6107          * Reenable menu items that were disabled while
6108          * machine was thinking
6109          */
6110         if (gameMode != TwoMachinesPlay)
6111             SetUserThinkingEnables();
6112
6113         // [HGM] book: after book hit opponent has received move and is now in force mode
6114         // force the book reply into it, and then fake that it outputted this move by jumping
6115         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6116         if(bookHit) {
6117                 static char bookMove[MSG_SIZ]; // a bit generous?
6118
6119                 strcpy(bookMove, "move ");
6120                 strcat(bookMove, bookHit);
6121                 message = bookMove;
6122                 cps = cps->other;
6123                 programStats.nodes = programStats.depth = programStats.time = 
6124                 programStats.score = programStats.got_only_move = 0;
6125                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6126
6127                 if(cps->lastPing != cps->lastPong) {
6128                     savedMessage = message; // args for deferred call
6129                     savedState = cps;
6130                     ScheduleDelayedEvent(DeferredBookMove, 10);
6131                     return;
6132                 }
6133                 goto FakeBookMove;
6134         }
6135
6136         return;
6137     }
6138
6139     /* Set special modes for chess engines.  Later something general
6140      *  could be added here; for now there is just one kludge feature,
6141      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6142      *  when "xboard" is given as an interactive command.
6143      */
6144     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6145         cps->useSigint = FALSE;
6146         cps->useSigterm = FALSE;
6147     }
6148     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6149       ParseFeatures(message+8, cps);
6150       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6151     }
6152
6153     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6154      * want this, I was asked to put it in, and obliged.
6155      */
6156     if (!strncmp(message, "setboard ", 9)) {
6157         Board initial_position; int i;
6158
6159         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6160
6161         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6162             DisplayError(_("Bad FEN received from engine"), 0);
6163             return ;
6164         } else {
6165            Reset(FALSE, FALSE);
6166            CopyBoard(boards[0], initial_position);
6167            initialRulePlies = FENrulePlies;
6168            epStatus[0] = FENepStatus;
6169            for( i=0; i<nrCastlingRights; i++ )
6170                 castlingRights[0][i] = FENcastlingRights[i];
6171            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6172            else gameMode = MachinePlaysBlack;                 
6173            DrawPosition(FALSE, boards[currentMove]);
6174         }
6175         return;
6176     }
6177
6178     /*
6179      * Look for communication commands
6180      */
6181     if (!strncmp(message, "telluser ", 9)) {
6182         DisplayNote(message + 9);
6183         return;
6184     }
6185     if (!strncmp(message, "tellusererror ", 14)) {
6186         DisplayError(message + 14, 0);
6187         return;
6188     }
6189     if (!strncmp(message, "tellopponent ", 13)) {
6190       if (appData.icsActive) {
6191         if (loggedOn) {
6192           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6193           SendToICS(buf1);
6194         }
6195       } else {
6196         DisplayNote(message + 13);
6197       }
6198       return;
6199     }
6200     if (!strncmp(message, "tellothers ", 11)) {
6201       if (appData.icsActive) {
6202         if (loggedOn) {
6203           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6204           SendToICS(buf1);
6205         }
6206       }
6207       return;
6208     }
6209     if (!strncmp(message, "tellall ", 8)) {
6210       if (appData.icsActive) {
6211         if (loggedOn) {
6212           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6213           SendToICS(buf1);
6214         }
6215       } else {
6216         DisplayNote(message + 8);
6217       }
6218       return;
6219     }
6220     if (strncmp(message, "warning", 7) == 0) {
6221         /* Undocumented feature, use tellusererror in new code */
6222         DisplayError(message, 0);
6223         return;
6224     }
6225     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6226         strcpy(realname, cps->tidy);
6227         strcat(realname, " query");
6228         AskQuestion(realname, buf2, buf1, cps->pr);
6229         return;
6230     }
6231     /* Commands from the engine directly to ICS.  We don't allow these to be 
6232      *  sent until we are logged on. Crafty kibitzes have been known to 
6233      *  interfere with the login process.
6234      */
6235     if (loggedOn) {
6236         if (!strncmp(message, "tellics ", 8)) {
6237             SendToICS(message + 8);
6238             SendToICS("\n");
6239             return;
6240         }
6241         if (!strncmp(message, "tellicsnoalias ", 15)) {
6242             SendToICS(ics_prefix);
6243             SendToICS(message + 15);
6244             SendToICS("\n");
6245             return;
6246         }
6247         /* The following are for backward compatibility only */
6248         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6249             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6250             SendToICS(ics_prefix);
6251             SendToICS(message);
6252             SendToICS("\n");
6253             return;
6254         }
6255     }
6256     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6257         return;
6258     }
6259     /*
6260      * If the move is illegal, cancel it and redraw the board.
6261      * Also deal with other error cases.  Matching is rather loose
6262      * here to accommodate engines written before the spec.
6263      */
6264     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6265         strncmp(message, "Error", 5) == 0) {
6266         if (StrStr(message, "name") || 
6267             StrStr(message, "rating") || StrStr(message, "?") ||
6268             StrStr(message, "result") || StrStr(message, "board") ||
6269             StrStr(message, "bk") || StrStr(message, "computer") ||
6270             StrStr(message, "variant") || StrStr(message, "hint") ||
6271             StrStr(message, "random") || StrStr(message, "depth") ||
6272             StrStr(message, "accepted")) {
6273             return;
6274         }
6275         if (StrStr(message, "protover")) {
6276           /* Program is responding to input, so it's apparently done
6277              initializing, and this error message indicates it is
6278              protocol version 1.  So we don't need to wait any longer
6279              for it to initialize and send feature commands. */
6280           FeatureDone(cps, 1);
6281           cps->protocolVersion = 1;
6282           return;
6283         }
6284         cps->maybeThinking = FALSE;
6285
6286         if (StrStr(message, "draw")) {
6287             /* Program doesn't have "draw" command */
6288             cps->sendDrawOffers = 0;
6289             return;
6290         }
6291         if (cps->sendTime != 1 &&
6292             (StrStr(message, "time") || StrStr(message, "otim"))) {
6293           /* Program apparently doesn't have "time" or "otim" command */
6294           cps->sendTime = 0;
6295           return;
6296         }
6297         if (StrStr(message, "analyze")) {
6298             cps->analysisSupport = FALSE;
6299             cps->analyzing = FALSE;
6300             Reset(FALSE, TRUE);
6301             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6302             DisplayError(buf2, 0);
6303             return;
6304         }
6305         if (StrStr(message, "(no matching move)st")) {
6306           /* Special kludge for GNU Chess 4 only */
6307           cps->stKludge = TRUE;
6308           SendTimeControl(cps, movesPerSession, timeControl,
6309                           timeIncrement, appData.searchDepth,
6310                           searchTime);
6311           return;
6312         }
6313         if (StrStr(message, "(no matching move)sd")) {
6314           /* Special kludge for GNU Chess 4 only */
6315           cps->sdKludge = TRUE;
6316           SendTimeControl(cps, movesPerSession, timeControl,
6317                           timeIncrement, appData.searchDepth,
6318                           searchTime);
6319           return;
6320         }
6321         if (!StrStr(message, "llegal")) {
6322             return;
6323         }
6324         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6325             gameMode == IcsIdle) return;
6326         if (forwardMostMove <= backwardMostMove) return;
6327         if (pausing) PauseEvent();
6328       if(appData.forceIllegal) {
6329             // [HGM] illegal: machine refused move; force position after move into it
6330           SendToProgram("force\n", cps);
6331           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6332                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6333                 // when black is to move, while there might be nothing on a2 or black
6334                 // might already have the move. So send the board as if white has the move.
6335                 // But first we must change the stm of the engine, as it refused the last move
6336                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6337                 if(WhiteOnMove(forwardMostMove)) {
6338                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6339                     SendBoard(cps, forwardMostMove); // kludgeless board
6340                 } else {
6341                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6342                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6343                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6344                 }
6345           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6346             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6347                  gameMode == TwoMachinesPlay)
6348               SendToProgram("go\n", cps);
6349             return;
6350       } else
6351         if (gameMode == PlayFromGameFile) {
6352             /* Stop reading this game file */
6353             gameMode = EditGame;
6354             ModeHighlight();
6355         }
6356         currentMove = --forwardMostMove;
6357         DisplayMove(currentMove-1); /* before DisplayMoveError */
6358         SwitchClocks();
6359         DisplayBothClocks();
6360         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6361                 parseList[currentMove], cps->which);
6362         DisplayMoveError(buf1);
6363         DrawPosition(FALSE, boards[currentMove]);
6364
6365         /* [HGM] illegal-move claim should forfeit game when Xboard */
6366         /* only passes fully legal moves                            */
6367         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6368             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6369                                 "False illegal-move claim", GE_XBOARD );
6370         }
6371         return;
6372     }
6373     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6374         /* Program has a broken "time" command that
6375            outputs a string not ending in newline.
6376            Don't use it. */
6377         cps->sendTime = 0;
6378     }
6379     
6380     /*
6381      * If chess program startup fails, exit with an error message.
6382      * Attempts to recover here are futile.
6383      */
6384     if ((StrStr(message, "unknown host") != NULL)
6385         || (StrStr(message, "No remote directory") != NULL)
6386         || (StrStr(message, "not found") != NULL)
6387         || (StrStr(message, "No such file") != NULL)
6388         || (StrStr(message, "can't alloc") != NULL)
6389         || (StrStr(message, "Permission denied") != NULL)) {
6390
6391         cps->maybeThinking = FALSE;
6392         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6393                 cps->which, cps->program, cps->host, message);
6394         RemoveInputSource(cps->isr);
6395         DisplayFatalError(buf1, 0, 1);
6396         return;
6397     }
6398     
6399     /* 
6400      * Look for hint output
6401      */
6402     if (sscanf(message, "Hint: %s", buf1) == 1) {
6403         if (cps == &first && hintRequested) {
6404             hintRequested = FALSE;
6405             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6406                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6407                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6408                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6409                                     fromY, fromX, toY, toX, promoChar, buf1);
6410                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6411                 DisplayInformation(buf2);
6412             } else {
6413                 /* Hint move could not be parsed!? */
6414               snprintf(buf2, sizeof(buf2),
6415                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6416                         buf1, cps->which);
6417                 DisplayError(buf2, 0);
6418             }
6419         } else {
6420             strcpy(lastHint, buf1);
6421         }
6422         return;
6423     }
6424
6425     /*
6426      * Ignore other messages if game is not in progress
6427      */
6428     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6429         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6430
6431     /*
6432      * look for win, lose, draw, or draw offer
6433      */
6434     if (strncmp(message, "1-0", 3) == 0) {
6435         char *p, *q, *r = "";
6436         p = strchr(message, '{');
6437         if (p) {
6438             q = strchr(p, '}');
6439             if (q) {
6440                 *q = NULLCHAR;
6441                 r = p + 1;
6442             }
6443         }
6444         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6445         return;
6446     } else if (strncmp(message, "0-1", 3) == 0) {
6447         char *p, *q, *r = "";
6448         p = strchr(message, '{');
6449         if (p) {
6450             q = strchr(p, '}');
6451             if (q) {
6452                 *q = NULLCHAR;
6453                 r = p + 1;
6454             }
6455         }
6456         /* Kludge for Arasan 4.1 bug */
6457         if (strcmp(r, "Black resigns") == 0) {
6458             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6459             return;
6460         }
6461         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6462         return;
6463     } else if (strncmp(message, "1/2", 3) == 0) {
6464         char *p, *q, *r = "";
6465         p = strchr(message, '{');
6466         if (p) {
6467             q = strchr(p, '}');
6468             if (q) {
6469                 *q = NULLCHAR;
6470                 r = p + 1;
6471             }
6472         }
6473             
6474         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6475         return;
6476
6477     } else if (strncmp(message, "White resign", 12) == 0) {
6478         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6479         return;
6480     } else if (strncmp(message, "Black resign", 12) == 0) {
6481         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6482         return;
6483     } else if (strncmp(message, "White matches", 13) == 0 ||
6484                strncmp(message, "Black matches", 13) == 0   ) {
6485         /* [HGM] ignore GNUShogi noises */
6486         return;
6487     } else if (strncmp(message, "White", 5) == 0 &&
6488                message[5] != '(' &&
6489                StrStr(message, "Black") == NULL) {
6490         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6491         return;
6492     } else if (strncmp(message, "Black", 5) == 0 &&
6493                message[5] != '(') {
6494         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6495         return;
6496     } else if (strcmp(message, "resign") == 0 ||
6497                strcmp(message, "computer resigns") == 0) {
6498         switch (gameMode) {
6499           case MachinePlaysBlack:
6500           case IcsPlayingBlack:
6501             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6502             break;
6503           case MachinePlaysWhite:
6504           case IcsPlayingWhite:
6505             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6506             break;
6507           case TwoMachinesPlay:
6508             if (cps->twoMachinesColor[0] == 'w')
6509               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6510             else
6511               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6512             break;
6513           default:
6514             /* can't happen */
6515             break;
6516         }
6517         return;
6518     } else if (strncmp(message, "opponent mates", 14) == 0) {
6519         switch (gameMode) {
6520           case MachinePlaysBlack:
6521           case IcsPlayingBlack:
6522             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6523             break;
6524           case MachinePlaysWhite:
6525           case IcsPlayingWhite:
6526             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6527             break;
6528           case TwoMachinesPlay:
6529             if (cps->twoMachinesColor[0] == 'w')
6530               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6531             else
6532               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6533             break;
6534           default:
6535             /* can't happen */
6536             break;
6537         }
6538         return;
6539     } else if (strncmp(message, "computer mates", 14) == 0) {
6540         switch (gameMode) {
6541           case MachinePlaysBlack:
6542           case IcsPlayingBlack:
6543             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6544             break;
6545           case MachinePlaysWhite:
6546           case IcsPlayingWhite:
6547             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6548             break;
6549           case TwoMachinesPlay:
6550             if (cps->twoMachinesColor[0] == 'w')
6551               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6552             else
6553               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6554             break;
6555           default:
6556             /* can't happen */
6557             break;
6558         }
6559         return;
6560     } else if (strncmp(message, "checkmate", 9) == 0) {
6561         if (WhiteOnMove(forwardMostMove)) {
6562             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6563         } else {
6564             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6565         }
6566         return;
6567     } else if (strstr(message, "Draw") != NULL ||
6568                strstr(message, "game is a draw") != NULL) {
6569         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6570         return;
6571     } else if (strstr(message, "offer") != NULL &&
6572                strstr(message, "draw") != NULL) {
6573 #if ZIPPY
6574         if (appData.zippyPlay && first.initDone) {
6575             /* Relay offer to ICS */
6576             SendToICS(ics_prefix);
6577             SendToICS("draw\n");
6578         }
6579 #endif
6580         cps->offeredDraw = 2; /* valid until this engine moves twice */
6581         if (gameMode == TwoMachinesPlay) {
6582             if (cps->other->offeredDraw) {
6583                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6584             /* [HGM] in two-machine mode we delay relaying draw offer      */
6585             /* until after we also have move, to see if it is really claim */
6586             }
6587         } else if (gameMode == MachinePlaysWhite ||
6588                    gameMode == MachinePlaysBlack) {
6589           if (userOfferedDraw) {
6590             DisplayInformation(_("Machine accepts your draw offer"));
6591             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6592           } else {
6593             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6594           }
6595         }
6596     }
6597
6598     
6599     /*
6600      * Look for thinking output
6601      */
6602     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6603           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6604                                 ) {
6605         int plylev, mvleft, mvtot, curscore, time;
6606         char mvname[MOVE_LEN];
6607         u64 nodes; // [DM]
6608         char plyext;
6609         int ignore = FALSE;
6610         int prefixHint = FALSE;
6611         mvname[0] = NULLCHAR;
6612
6613         switch (gameMode) {
6614           case MachinePlaysBlack:
6615           case IcsPlayingBlack:
6616             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6617             break;
6618           case MachinePlaysWhite:
6619           case IcsPlayingWhite:
6620             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6621             break;
6622           case AnalyzeMode:
6623           case AnalyzeFile:
6624             break;
6625           case IcsObserving: /* [DM] icsEngineAnalyze */
6626             if (!appData.icsEngineAnalyze) ignore = TRUE;
6627             break;
6628           case TwoMachinesPlay:
6629             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6630                 ignore = TRUE;
6631             }
6632             break;
6633           default:
6634             ignore = TRUE;
6635             break;
6636         }
6637
6638         if (!ignore) {
6639             buf1[0] = NULLCHAR;
6640             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6641                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6642
6643                 if (plyext != ' ' && plyext != '\t') {
6644                     time *= 100;
6645                 }
6646
6647                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6648                 if( cps->scoreIsAbsolute && 
6649                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6650                 {
6651                     curscore = -curscore;
6652                 }
6653
6654
6655                 programStats.depth = plylev;
6656                 programStats.nodes = nodes;
6657                 programStats.time = time;
6658                 programStats.score = curscore;
6659                 programStats.got_only_move = 0;
6660
6661                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6662                         int ticklen;
6663
6664                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6665                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6666                         if(WhiteOnMove(forwardMostMove)) 
6667                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6668                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6669                 }
6670
6671                 /* Buffer overflow protection */
6672                 if (buf1[0] != NULLCHAR) {
6673                     if (strlen(buf1) >= sizeof(programStats.movelist)
6674                         && appData.debugMode) {
6675                         fprintf(debugFP,
6676                                 "PV is too long; using the first %d bytes.\n",
6677                                 sizeof(programStats.movelist) - 1);
6678                     }
6679
6680                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6681                 } else {
6682                     sprintf(programStats.movelist, " no PV\n");
6683                 }
6684
6685                 if (programStats.seen_stat) {
6686                     programStats.ok_to_send = 1;
6687                 }
6688
6689                 if (strchr(programStats.movelist, '(') != NULL) {
6690                     programStats.line_is_book = 1;
6691                     programStats.nr_moves = 0;
6692                     programStats.moves_left = 0;
6693                 } else {
6694                     programStats.line_is_book = 0;
6695                 }
6696
6697                 SendProgramStatsToFrontend( cps, &programStats );
6698
6699                 /* 
6700                     [AS] Protect the thinkOutput buffer from overflow... this
6701                     is only useful if buf1 hasn't overflowed first!
6702                 */
6703                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6704                         plylev, 
6705                         (gameMode == TwoMachinesPlay ?
6706                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6707                         ((double) curscore) / 100.0,
6708                         prefixHint ? lastHint : "",
6709                         prefixHint ? " " : "" );
6710
6711                 if( buf1[0] != NULLCHAR ) {
6712                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6713
6714                     if( strlen(buf1) > max_len ) {
6715                         if( appData.debugMode) {
6716                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6717                         }
6718                         buf1[max_len+1] = '\0';
6719                     }
6720
6721                     strcat( thinkOutput, buf1 );
6722                 }
6723
6724                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6725                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6726                     DisplayMove(currentMove - 1);
6727                 }
6728                 return;
6729
6730             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6731                 /* crafty (9.25+) says "(only move) <move>"
6732                  * if there is only 1 legal move
6733                  */
6734                 sscanf(p, "(only move) %s", buf1);
6735                 sprintf(thinkOutput, "%s (only move)", buf1);
6736                 sprintf(programStats.movelist, "%s (only move)", buf1);
6737                 programStats.depth = 1;
6738                 programStats.nr_moves = 1;
6739                 programStats.moves_left = 1;
6740                 programStats.nodes = 1;
6741                 programStats.time = 1;
6742                 programStats.got_only_move = 1;
6743
6744                 /* Not really, but we also use this member to
6745                    mean "line isn't going to change" (Crafty
6746                    isn't searching, so stats won't change) */
6747                 programStats.line_is_book = 1;
6748
6749                 SendProgramStatsToFrontend( cps, &programStats );
6750                 
6751                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6752                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6753                     DisplayMove(currentMove - 1);
6754                 }
6755                 return;
6756             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6757                               &time, &nodes, &plylev, &mvleft,
6758                               &mvtot, mvname) >= 5) {
6759                 /* The stat01: line is from Crafty (9.29+) in response
6760                    to the "." command */
6761                 programStats.seen_stat = 1;
6762                 cps->maybeThinking = TRUE;
6763
6764                 if (programStats.got_only_move || !appData.periodicUpdates)
6765                   return;
6766
6767                 programStats.depth = plylev;
6768                 programStats.time = time;
6769                 programStats.nodes = nodes;
6770                 programStats.moves_left = mvleft;
6771                 programStats.nr_moves = mvtot;
6772                 strcpy(programStats.move_name, mvname);
6773                 programStats.ok_to_send = 1;
6774                 programStats.movelist[0] = '\0';
6775
6776                 SendProgramStatsToFrontend( cps, &programStats );
6777
6778                 return;
6779
6780             } else if (strncmp(message,"++",2) == 0) {
6781                 /* Crafty 9.29+ outputs this */
6782                 programStats.got_fail = 2;
6783                 return;
6784
6785             } else if (strncmp(message,"--",2) == 0) {
6786                 /* Crafty 9.29+ outputs this */
6787                 programStats.got_fail = 1;
6788                 return;
6789
6790             } else if (thinkOutput[0] != NULLCHAR &&
6791                        strncmp(message, "    ", 4) == 0) {
6792                 unsigned message_len;
6793
6794                 p = message;
6795                 while (*p && *p == ' ') p++;
6796
6797                 message_len = strlen( p );
6798
6799                 /* [AS] Avoid buffer overflow */
6800                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6801                     strcat(thinkOutput, " ");
6802                     strcat(thinkOutput, p);
6803                 }
6804
6805                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6806                     strcat(programStats.movelist, " ");
6807                     strcat(programStats.movelist, p);
6808                 }
6809
6810                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6811                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6812                     DisplayMove(currentMove - 1);
6813                 }
6814                 return;
6815             }
6816         }
6817         else {
6818             buf1[0] = NULLCHAR;
6819
6820             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6821                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6822             {
6823                 ChessProgramStats cpstats;
6824
6825                 if (plyext != ' ' && plyext != '\t') {
6826                     time *= 100;
6827                 }
6828
6829                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6830                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6831                     curscore = -curscore;
6832                 }
6833
6834                 cpstats.depth = plylev;
6835                 cpstats.nodes = nodes;
6836                 cpstats.time = time;
6837                 cpstats.score = curscore;
6838                 cpstats.got_only_move = 0;
6839                 cpstats.movelist[0] = '\0';
6840
6841                 if (buf1[0] != NULLCHAR) {
6842                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6843                 }
6844
6845                 cpstats.ok_to_send = 0;
6846                 cpstats.line_is_book = 0;
6847                 cpstats.nr_moves = 0;
6848                 cpstats.moves_left = 0;
6849
6850                 SendProgramStatsToFrontend( cps, &cpstats );
6851             }
6852         }
6853     }
6854 }
6855
6856
6857 /* Parse a game score from the character string "game", and
6858    record it as the history of the current game.  The game
6859    score is NOT assumed to start from the standard position. 
6860    The display is not updated in any way.
6861    */
6862 void
6863 ParseGameHistory(game)
6864      char *game;
6865 {
6866     ChessMove moveType;
6867     int fromX, fromY, toX, toY, boardIndex;
6868     char promoChar;
6869     char *p, *q;
6870     char buf[MSG_SIZ];
6871
6872     if (appData.debugMode)
6873       fprintf(debugFP, "Parsing game history: %s\n", game);
6874
6875     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6876     gameInfo.site = StrSave(appData.icsHost);
6877     gameInfo.date = PGNDate();
6878     gameInfo.round = StrSave("-");
6879
6880     /* Parse out names of players */
6881     while (*game == ' ') game++;
6882     p = buf;
6883     while (*game != ' ') *p++ = *game++;
6884     *p = NULLCHAR;
6885     gameInfo.white = StrSave(buf);
6886     while (*game == ' ') game++;
6887     p = buf;
6888     while (*game != ' ' && *game != '\n') *p++ = *game++;
6889     *p = NULLCHAR;
6890     gameInfo.black = StrSave(buf);
6891
6892     /* Parse moves */
6893     boardIndex = blackPlaysFirst ? 1 : 0;
6894     yynewstr(game);
6895     for (;;) {
6896         yyboardindex = boardIndex;
6897         moveType = (ChessMove) yylex();
6898         switch (moveType) {
6899           case IllegalMove:             /* maybe suicide chess, etc. */
6900   if (appData.debugMode) {
6901     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6902     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6903     setbuf(debugFP, NULL);
6904   }
6905           case WhitePromotionChancellor:
6906           case BlackPromotionChancellor:
6907           case WhitePromotionArchbishop:
6908           case BlackPromotionArchbishop:
6909           case WhitePromotionQueen:
6910           case BlackPromotionQueen:
6911           case WhitePromotionRook:
6912           case BlackPromotionRook:
6913           case WhitePromotionBishop:
6914           case BlackPromotionBishop:
6915           case WhitePromotionKnight:
6916           case BlackPromotionKnight:
6917           case WhitePromotionKing:
6918           case BlackPromotionKing:
6919           case NormalMove:
6920           case WhiteCapturesEnPassant:
6921           case BlackCapturesEnPassant:
6922           case WhiteKingSideCastle:
6923           case WhiteQueenSideCastle:
6924           case BlackKingSideCastle:
6925           case BlackQueenSideCastle:
6926           case WhiteKingSideCastleWild:
6927           case WhiteQueenSideCastleWild:
6928           case BlackKingSideCastleWild:
6929           case BlackQueenSideCastleWild:
6930           /* PUSH Fabien */
6931           case WhiteHSideCastleFR:
6932           case WhiteASideCastleFR:
6933           case BlackHSideCastleFR:
6934           case BlackASideCastleFR:
6935           /* POP Fabien */
6936             fromX = currentMoveString[0] - AAA;
6937             fromY = currentMoveString[1] - ONE;
6938             toX = currentMoveString[2] - AAA;
6939             toY = currentMoveString[3] - ONE;
6940             promoChar = currentMoveString[4];
6941             break;
6942           case WhiteDrop:
6943           case BlackDrop:
6944             fromX = moveType == WhiteDrop ?
6945               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6946             (int) CharToPiece(ToLower(currentMoveString[0]));
6947             fromY = DROP_RANK;
6948             toX = currentMoveString[2] - AAA;
6949             toY = currentMoveString[3] - ONE;
6950             promoChar = NULLCHAR;
6951             break;
6952           case AmbiguousMove:
6953             /* bug? */
6954             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6955   if (appData.debugMode) {
6956     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6957     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6958     setbuf(debugFP, NULL);
6959   }
6960             DisplayError(buf, 0);
6961             return;
6962           case ImpossibleMove:
6963             /* bug? */
6964             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6965   if (appData.debugMode) {
6966     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6967     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6968     setbuf(debugFP, NULL);
6969   }
6970             DisplayError(buf, 0);
6971             return;
6972           case (ChessMove) 0:   /* end of file */
6973             if (boardIndex < backwardMostMove) {
6974                 /* Oops, gap.  How did that happen? */
6975                 DisplayError(_("Gap in move list"), 0);
6976                 return;
6977             }
6978             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6979             if (boardIndex > forwardMostMove) {
6980                 forwardMostMove = boardIndex;
6981             }
6982             return;
6983           case ElapsedTime:
6984             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6985                 strcat(parseList[boardIndex-1], " ");
6986                 strcat(parseList[boardIndex-1], yy_text);
6987             }
6988             continue;
6989           case Comment:
6990           case PGNTag:
6991           case NAG:
6992           default:
6993             /* ignore */
6994             continue;
6995           case WhiteWins:
6996           case BlackWins:
6997           case GameIsDrawn:
6998           case GameUnfinished:
6999             if (gameMode == IcsExamining) {
7000                 if (boardIndex < backwardMostMove) {
7001                     /* Oops, gap.  How did that happen? */
7002                     return;
7003                 }
7004                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7005                 return;
7006             }
7007             gameInfo.result = moveType;
7008             p = strchr(yy_text, '{');
7009             if (p == NULL) p = strchr(yy_text, '(');
7010             if (p == NULL) {
7011                 p = yy_text;
7012                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7013             } else {
7014                 q = strchr(p, *p == '{' ? '}' : ')');
7015                 if (q != NULL) *q = NULLCHAR;
7016                 p++;
7017             }
7018             gameInfo.resultDetails = StrSave(p);
7019             continue;
7020         }
7021         if (boardIndex >= forwardMostMove &&
7022             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7023             backwardMostMove = blackPlaysFirst ? 1 : 0;
7024             return;
7025         }
7026         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7027                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7028                                  parseList[boardIndex]);
7029         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7030         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7031         /* currentMoveString is set as a side-effect of yylex */
7032         strcpy(moveList[boardIndex], currentMoveString);
7033         strcat(moveList[boardIndex], "\n");
7034         boardIndex++;
7035         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7036                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7037         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7038                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7039           case MT_NONE:
7040           case MT_STALEMATE:
7041           default:
7042             break;
7043           case MT_CHECK:
7044             if(gameInfo.variant != VariantShogi)
7045                 strcat(parseList[boardIndex - 1], "+");
7046             break;
7047           case MT_CHECKMATE:
7048           case MT_STAINMATE:
7049             strcat(parseList[boardIndex - 1], "#");
7050             break;
7051         }
7052     }
7053 }
7054
7055
7056 /* Apply a move to the given board  */
7057 void
7058 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7059      int fromX, fromY, toX, toY;
7060      int promoChar;
7061      Board board;
7062      char *castling;
7063      char *ep;
7064 {
7065   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7066
7067     /* [HGM] compute & store e.p. status and castling rights for new position */
7068     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7069     { int i;
7070
7071       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7072       oldEP = *ep;
7073       *ep = EP_NONE;
7074
7075       if( board[toY][toX] != EmptySquare ) 
7076            *ep = EP_CAPTURE;  
7077
7078       if( board[fromY][fromX] == WhitePawn ) {
7079            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7080                *ep = EP_PAWN_MOVE;
7081            if( toY-fromY==2) {
7082                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7083                         gameInfo.variant != VariantBerolina || toX < fromX)
7084                       *ep = toX | berolina;
7085                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7086                         gameInfo.variant != VariantBerolina || toX > fromX) 
7087                       *ep = toX;
7088            }
7089       } else 
7090       if( board[fromY][fromX] == BlackPawn ) {
7091            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7092                *ep = EP_PAWN_MOVE; 
7093            if( toY-fromY== -2) {
7094                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7095                         gameInfo.variant != VariantBerolina || toX < fromX)
7096                       *ep = toX | berolina;
7097                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7098                         gameInfo.variant != VariantBerolina || toX > fromX) 
7099                       *ep = toX;
7100            }
7101        }
7102
7103        for(i=0; i<nrCastlingRights; i++) {
7104            if(castling[i] == fromX && castlingRank[i] == fromY ||
7105               castling[i] == toX   && castlingRank[i] == toY   
7106              ) castling[i] = -1; // revoke for moved or captured piece
7107        }
7108
7109     }
7110
7111   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7112   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7113        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7114          
7115   if (fromX == toX && fromY == toY) return;
7116
7117   if (fromY == DROP_RANK) {
7118         /* must be first */
7119         piece = board[toY][toX] = (ChessSquare) fromX;
7120   } else {
7121      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7122      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7123      if(gameInfo.variant == VariantKnightmate)
7124          king += (int) WhiteUnicorn - (int) WhiteKing;
7125
7126     /* Code added by Tord: */
7127     /* FRC castling assumed when king captures friendly rook. */
7128     if (board[fromY][fromX] == WhiteKing &&
7129              board[toY][toX] == WhiteRook) {
7130       board[fromY][fromX] = EmptySquare;
7131       board[toY][toX] = EmptySquare;
7132       if(toX > fromX) {
7133         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7134       } else {
7135         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7136       }
7137     } else if (board[fromY][fromX] == BlackKing &&
7138                board[toY][toX] == BlackRook) {
7139       board[fromY][fromX] = EmptySquare;
7140       board[toY][toX] = EmptySquare;
7141       if(toX > fromX) {
7142         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7143       } else {
7144         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7145       }
7146     /* End of code added by Tord */
7147
7148     } else if (board[fromY][fromX] == king
7149         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7150         && toY == fromY && toX > fromX+1) {
7151         board[fromY][fromX] = EmptySquare;
7152         board[toY][toX] = king;
7153         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7154         board[fromY][BOARD_RGHT-1] = EmptySquare;
7155     } else if (board[fromY][fromX] == king
7156         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7157                && toY == fromY && toX < fromX-1) {
7158         board[fromY][fromX] = EmptySquare;
7159         board[toY][toX] = king;
7160         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7161         board[fromY][BOARD_LEFT] = EmptySquare;
7162     } else if (board[fromY][fromX] == WhitePawn
7163                && toY == BOARD_HEIGHT-1
7164                && gameInfo.variant != VariantXiangqi
7165                ) {
7166         /* white pawn promotion */
7167         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7168         if (board[toY][toX] == EmptySquare) {
7169             board[toY][toX] = WhiteQueen;
7170         }
7171         if(gameInfo.variant==VariantBughouse ||
7172            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7173             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7174         board[fromY][fromX] = EmptySquare;
7175     } else if ((fromY == BOARD_HEIGHT-4)
7176                && (toX != fromX)
7177                && gameInfo.variant != VariantXiangqi
7178                && gameInfo.variant != VariantBerolina
7179                && (board[fromY][fromX] == WhitePawn)
7180                && (board[toY][toX] == EmptySquare)) {
7181         board[fromY][fromX] = EmptySquare;
7182         board[toY][toX] = WhitePawn;
7183         captured = board[toY - 1][toX];
7184         board[toY - 1][toX] = EmptySquare;
7185     } else if ((fromY == BOARD_HEIGHT-4)
7186                && (toX == fromX)
7187                && gameInfo.variant == VariantBerolina
7188                && (board[fromY][fromX] == WhitePawn)
7189                && (board[toY][toX] == EmptySquare)) {
7190         board[fromY][fromX] = EmptySquare;
7191         board[toY][toX] = WhitePawn;
7192         if(oldEP & EP_BEROLIN_A) {
7193                 captured = board[fromY][fromX-1];
7194                 board[fromY][fromX-1] = EmptySquare;
7195         }else{  captured = board[fromY][fromX+1];
7196                 board[fromY][fromX+1] = EmptySquare;
7197         }
7198     } else if (board[fromY][fromX] == king
7199         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7200                && toY == fromY && toX > fromX+1) {
7201         board[fromY][fromX] = EmptySquare;
7202         board[toY][toX] = king;
7203         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7204         board[fromY][BOARD_RGHT-1] = EmptySquare;
7205     } else if (board[fromY][fromX] == king
7206         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7207                && toY == fromY && toX < fromX-1) {
7208         board[fromY][fromX] = EmptySquare;
7209         board[toY][toX] = king;
7210         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7211         board[fromY][BOARD_LEFT] = EmptySquare;
7212     } else if (fromY == 7 && fromX == 3
7213                && board[fromY][fromX] == BlackKing
7214                && toY == 7 && toX == 5) {
7215         board[fromY][fromX] = EmptySquare;
7216         board[toY][toX] = BlackKing;
7217         board[fromY][7] = EmptySquare;
7218         board[toY][4] = BlackRook;
7219     } else if (fromY == 7 && fromX == 3
7220                && board[fromY][fromX] == BlackKing
7221                && toY == 7 && toX == 1) {
7222         board[fromY][fromX] = EmptySquare;
7223         board[toY][toX] = BlackKing;
7224         board[fromY][0] = EmptySquare;
7225         board[toY][2] = BlackRook;
7226     } else if (board[fromY][fromX] == BlackPawn
7227                && toY == 0
7228                && gameInfo.variant != VariantXiangqi
7229                ) {
7230         /* black pawn promotion */
7231         board[0][toX] = CharToPiece(ToLower(promoChar));
7232         if (board[0][toX] == EmptySquare) {
7233             board[0][toX] = BlackQueen;
7234         }
7235         if(gameInfo.variant==VariantBughouse ||
7236            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7237             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7238         board[fromY][fromX] = EmptySquare;
7239     } else if ((fromY == 3)
7240                && (toX != fromX)
7241                && gameInfo.variant != VariantXiangqi
7242                && gameInfo.variant != VariantBerolina
7243                && (board[fromY][fromX] == BlackPawn)
7244                && (board[toY][toX] == EmptySquare)) {
7245         board[fromY][fromX] = EmptySquare;
7246         board[toY][toX] = BlackPawn;
7247         captured = board[toY + 1][toX];
7248         board[toY + 1][toX] = EmptySquare;
7249     } else if ((fromY == 3)
7250                && (toX == fromX)
7251                && gameInfo.variant == VariantBerolina
7252                && (board[fromY][fromX] == BlackPawn)
7253                && (board[toY][toX] == EmptySquare)) {
7254         board[fromY][fromX] = EmptySquare;
7255         board[toY][toX] = BlackPawn;
7256         if(oldEP & EP_BEROLIN_A) {
7257                 captured = board[fromY][fromX-1];
7258                 board[fromY][fromX-1] = EmptySquare;
7259         }else{  captured = board[fromY][fromX+1];
7260                 board[fromY][fromX+1] = EmptySquare;
7261         }
7262     } else {
7263         board[toY][toX] = board[fromY][fromX];
7264         board[fromY][fromX] = EmptySquare;
7265     }
7266
7267     /* [HGM] now we promote for Shogi, if needed */
7268     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7269         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7270   }
7271
7272     if (gameInfo.holdingsWidth != 0) {
7273
7274       /* !!A lot more code needs to be written to support holdings  */
7275       /* [HGM] OK, so I have written it. Holdings are stored in the */
7276       /* penultimate board files, so they are automaticlly stored   */
7277       /* in the game history.                                       */
7278       if (fromY == DROP_RANK) {
7279         /* Delete from holdings, by decreasing count */
7280         /* and erasing image if necessary            */
7281         p = (int) fromX;
7282         if(p < (int) BlackPawn) { /* white drop */
7283              p -= (int)WhitePawn;
7284              if(p >= gameInfo.holdingsSize) p = 0;
7285              if(--board[p][BOARD_WIDTH-2] == 0)
7286                   board[p][BOARD_WIDTH-1] = EmptySquare;
7287         } else {                  /* black drop */
7288              p -= (int)BlackPawn;
7289              if(p >= gameInfo.holdingsSize) p = 0;
7290              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7291                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7292         }
7293       }
7294       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7295           && gameInfo.variant != VariantBughouse        ) {
7296         /* [HGM] holdings: Add to holdings, if holdings exist */
7297         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7298                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7299                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7300         }
7301         p = (int) captured;
7302         if (p >= (int) BlackPawn) {
7303           p -= (int)BlackPawn;
7304           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7305                   /* in Shogi restore piece to its original  first */
7306                   captured = (ChessSquare) (DEMOTED captured);
7307                   p = DEMOTED p;
7308           }
7309           p = PieceToNumber((ChessSquare)p);
7310           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7311           board[p][BOARD_WIDTH-2]++;
7312           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7313         } else {
7314           p -= (int)WhitePawn;
7315           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7316                   captured = (ChessSquare) (DEMOTED captured);
7317                   p = DEMOTED p;
7318           }
7319           p = PieceToNumber((ChessSquare)p);
7320           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7321           board[BOARD_HEIGHT-1-p][1]++;
7322           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7323         }
7324       }
7325
7326     } else if (gameInfo.variant == VariantAtomic) {
7327       if (captured != EmptySquare) {
7328         int y, x;
7329         for (y = toY-1; y <= toY+1; y++) {
7330           for (x = toX-1; x <= toX+1; x++) {
7331             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7332                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7333               board[y][x] = EmptySquare;
7334             }
7335           }
7336         }
7337         board[toY][toX] = EmptySquare;
7338       }
7339     }
7340     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7341         /* [HGM] Shogi promotions */
7342         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7343     }
7344
7345     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7346                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7347         // [HGM] superchess: take promotion piece out of holdings
7348         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7349         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7350             if(!--board[k][BOARD_WIDTH-2])
7351                 board[k][BOARD_WIDTH-1] = EmptySquare;
7352         } else {
7353             if(!--board[BOARD_HEIGHT-1-k][1])
7354                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7355         }
7356     }
7357
7358 }
7359
7360 /* Updates forwardMostMove */
7361 void
7362 MakeMove(fromX, fromY, toX, toY, promoChar)
7363      int fromX, fromY, toX, toY;
7364      int promoChar;
7365 {
7366 //    forwardMostMove++; // [HGM] bare: moved downstream
7367
7368     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7369         int timeLeft; static int lastLoadFlag=0; int king, piece;
7370         piece = boards[forwardMostMove][fromY][fromX];
7371         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7372         if(gameInfo.variant == VariantKnightmate)
7373             king += (int) WhiteUnicorn - (int) WhiteKing;
7374         if(forwardMostMove == 0) {
7375             if(blackPlaysFirst) 
7376                 fprintf(serverMoves, "%s;", second.tidy);
7377             fprintf(serverMoves, "%s;", first.tidy);
7378             if(!blackPlaysFirst) 
7379                 fprintf(serverMoves, "%s;", second.tidy);
7380         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7381         lastLoadFlag = loadFlag;
7382         // print base move
7383         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7384         // print castling suffix
7385         if( toY == fromY && piece == king ) {
7386             if(toX-fromX > 1)
7387                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7388             if(fromX-toX >1)
7389                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7390         }
7391         // e.p. suffix
7392         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7393              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7394              boards[forwardMostMove][toY][toX] == EmptySquare
7395              && fromX != toX )
7396                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7397         // promotion suffix
7398         if(promoChar != NULLCHAR)
7399                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7400         if(!loadFlag) {
7401             fprintf(serverMoves, "/%d/%d",
7402                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7403             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7404             else                      timeLeft = blackTimeRemaining/1000;
7405             fprintf(serverMoves, "/%d", timeLeft);
7406         }
7407         fflush(serverMoves);
7408     }
7409
7410     if (forwardMostMove+1 >= MAX_MOVES) {
7411       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7412                         0, 1);
7413       return;
7414     }
7415     if (commentList[forwardMostMove+1] != NULL) {
7416         free(commentList[forwardMostMove+1]);
7417         commentList[forwardMostMove+1] = NULL;
7418     }
7419     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7420     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7421     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7422                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7423     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7424     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7425     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7426     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7427     gameInfo.result = GameUnfinished;
7428     if (gameInfo.resultDetails != NULL) {
7429         free(gameInfo.resultDetails);
7430         gameInfo.resultDetails = NULL;
7431     }
7432     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7433                               moveList[forwardMostMove - 1]);
7434     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7435                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7436                              fromY, fromX, toY, toX, promoChar,
7437                              parseList[forwardMostMove - 1]);
7438     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7439                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7440                             castlingRights[forwardMostMove]) ) {
7441       case MT_NONE:
7442       case MT_STALEMATE:
7443       default:
7444         break;
7445       case MT_CHECK:
7446         if(gameInfo.variant != VariantShogi)
7447             strcat(parseList[forwardMostMove - 1], "+");
7448         break;
7449       case MT_CHECKMATE:
7450       case MT_STAINMATE:
7451         strcat(parseList[forwardMostMove - 1], "#");
7452         break;
7453     }
7454     if (appData.debugMode) {
7455         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7456     }
7457
7458 }
7459
7460 /* Updates currentMove if not pausing */
7461 void
7462 ShowMove(fromX, fromY, toX, toY)
7463 {
7464     int instant = (gameMode == PlayFromGameFile) ?
7465         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7466     if(appData.noGUI) return;
7467     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7468         if (!instant) {
7469             if (forwardMostMove == currentMove + 1) {
7470                 AnimateMove(boards[forwardMostMove - 1],
7471                             fromX, fromY, toX, toY);
7472             }
7473             if (appData.highlightLastMove) {
7474                 SetHighlights(fromX, fromY, toX, toY);
7475             }
7476         }
7477         currentMove = forwardMostMove;
7478     }
7479
7480     if (instant) return;
7481
7482     DisplayMove(currentMove - 1);
7483     DrawPosition(FALSE, boards[currentMove]);
7484     DisplayBothClocks();
7485     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7486 }
7487
7488 void SendEgtPath(ChessProgramState *cps)
7489 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7490         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7491
7492         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7493
7494         while(*p) {
7495             char c, *q = name+1, *r, *s;
7496
7497             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7498             while(*p && *p != ',') *q++ = *p++;
7499             *q++ = ':'; *q = 0;
7500             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7501                 strcmp(name, ",nalimov:") == 0 ) {
7502                 // take nalimov path from the menu-changeable option first, if it is defined
7503                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7504                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7505             } else
7506             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7507                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7508                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7509                 s = r = StrStr(s, ":") + 1; // beginning of path info
7510                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7511                 c = *r; *r = 0;             // temporarily null-terminate path info
7512                     *--q = 0;               // strip of trailig ':' from name
7513                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7514                 *r = c;
7515                 SendToProgram(buf,cps);     // send egtbpath command for this format
7516             }
7517             if(*p == ',') p++; // read away comma to position for next format name
7518         }
7519 }
7520
7521 void
7522 InitChessProgram(cps, setup)
7523      ChessProgramState *cps;
7524      int setup; /* [HGM] needed to setup FRC opening position */
7525 {
7526     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7527     if (appData.noChessProgram) return;
7528     hintRequested = FALSE;
7529     bookRequested = FALSE;
7530
7531     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7532     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7533     if(cps->memSize) { /* [HGM] memory */
7534         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7535         SendToProgram(buf, cps);
7536     }
7537     SendEgtPath(cps); /* [HGM] EGT */
7538     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7539         sprintf(buf, "cores %d\n", appData.smpCores);
7540         SendToProgram(buf, cps);
7541     }
7542
7543     SendToProgram(cps->initString, cps);
7544     if (gameInfo.variant != VariantNormal &&
7545         gameInfo.variant != VariantLoadable
7546         /* [HGM] also send variant if board size non-standard */
7547         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7548                                             ) {
7549       char *v = VariantName(gameInfo.variant);
7550       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7551         /* [HGM] in protocol 1 we have to assume all variants valid */
7552         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7553         DisplayFatalError(buf, 0, 1);
7554         return;
7555       }
7556
7557       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7558       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7559       if( gameInfo.variant == VariantXiangqi )
7560            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7561       if( gameInfo.variant == VariantShogi )
7562            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7563       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7564            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7565       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7566                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7567            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7568       if( gameInfo.variant == VariantCourier )
7569            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7570       if( gameInfo.variant == VariantSuper )
7571            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7572       if( gameInfo.variant == VariantGreat )
7573            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7574
7575       if(overruled) {
7576            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7577                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7578            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7579            if(StrStr(cps->variants, b) == NULL) { 
7580                // specific sized variant not known, check if general sizing allowed
7581                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7582                    if(StrStr(cps->variants, "boardsize") == NULL) {
7583                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7584                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7585                        DisplayFatalError(buf, 0, 1);
7586                        return;
7587                    }
7588                    /* [HGM] here we really should compare with the maximum supported board size */
7589                }
7590            }
7591       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7592       sprintf(buf, "variant %s\n", b);
7593       SendToProgram(buf, cps);
7594     }
7595     currentlyInitializedVariant = gameInfo.variant;
7596
7597     /* [HGM] send opening position in FRC to first engine */
7598     if(setup) {
7599           SendToProgram("force\n", cps);
7600           SendBoard(cps, 0);
7601           /* engine is now in force mode! Set flag to wake it up after first move. */
7602           setboardSpoiledMachineBlack = 1;
7603     }
7604
7605     if (cps->sendICS) {
7606       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7607       SendToProgram(buf, cps);
7608     }
7609     cps->maybeThinking = FALSE;
7610     cps->offeredDraw = 0;
7611     if (!appData.icsActive) {
7612         SendTimeControl(cps, movesPerSession, timeControl,
7613                         timeIncrement, appData.searchDepth,
7614                         searchTime);
7615     }
7616     if (appData.showThinking 
7617         // [HGM] thinking: four options require thinking output to be sent
7618         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7619                                 ) {
7620         SendToProgram("post\n", cps);
7621     }
7622     SendToProgram("hard\n", cps);
7623     if (!appData.ponderNextMove) {
7624         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7625            it without being sure what state we are in first.  "hard"
7626            is not a toggle, so that one is OK.
7627          */
7628         SendToProgram("easy\n", cps);
7629     }
7630     if (cps->usePing) {
7631       sprintf(buf, "ping %d\n", ++cps->lastPing);
7632       SendToProgram(buf, cps);
7633     }
7634     cps->initDone = TRUE;
7635 }   
7636
7637
7638 void
7639 StartChessProgram(cps)
7640      ChessProgramState *cps;
7641 {
7642     char buf[MSG_SIZ];
7643     int err;
7644
7645     if (appData.noChessProgram) return;
7646     cps->initDone = FALSE;
7647
7648     if (strcmp(cps->host, "localhost") == 0) {
7649         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7650     } else if (*appData.remoteShell == NULLCHAR) {
7651         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7652     } else {
7653         if (*appData.remoteUser == NULLCHAR) {
7654           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7655                     cps->program);
7656         } else {
7657           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7658                     cps->host, appData.remoteUser, cps->program);
7659         }
7660         err = StartChildProcess(buf, "", &cps->pr);
7661     }
7662     
7663     if (err != 0) {
7664         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7665         DisplayFatalError(buf, err, 1);
7666         cps->pr = NoProc;
7667         cps->isr = NULL;
7668         return;
7669     }
7670     
7671     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7672     if (cps->protocolVersion > 1) {
7673       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7674       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7675       cps->comboCnt = 0;  //                and values of combo boxes
7676       SendToProgram(buf, cps);
7677     } else {
7678       SendToProgram("xboard\n", cps);
7679     }
7680 }
7681
7682
7683 void
7684 TwoMachinesEventIfReady P((void))
7685 {
7686   if (first.lastPing != first.lastPong) {
7687     DisplayMessage("", _("Waiting for first chess program"));
7688     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7689     return;
7690   }
7691   if (second.lastPing != second.lastPong) {
7692     DisplayMessage("", _("Waiting for second chess program"));
7693     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7694     return;
7695   }
7696   ThawUI();
7697   TwoMachinesEvent();
7698 }
7699
7700 void
7701 NextMatchGame P((void))
7702 {
7703     int index; /* [HGM] autoinc: step lod index during match */
7704     Reset(FALSE, TRUE);
7705     if (*appData.loadGameFile != NULLCHAR) {
7706         index = appData.loadGameIndex;
7707         if(index < 0) { // [HGM] autoinc
7708             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7709             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7710         } 
7711         LoadGameFromFile(appData.loadGameFile,
7712                          index,
7713                          appData.loadGameFile, FALSE);
7714     } else if (*appData.loadPositionFile != NULLCHAR) {
7715         index = appData.loadPositionIndex;
7716         if(index < 0) { // [HGM] autoinc
7717             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7718             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7719         } 
7720         LoadPositionFromFile(appData.loadPositionFile,
7721                              index,
7722                              appData.loadPositionFile);
7723     }
7724     TwoMachinesEventIfReady();
7725 }
7726
7727 void UserAdjudicationEvent( int result )
7728 {
7729     ChessMove gameResult = GameIsDrawn;
7730
7731     if( result > 0 ) {
7732         gameResult = WhiteWins;
7733     }
7734     else if( result < 0 ) {
7735         gameResult = BlackWins;
7736     }
7737
7738     if( gameMode == TwoMachinesPlay ) {
7739         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7740     }
7741 }
7742
7743
7744 // [HGM] save: calculate checksum of game to make games easily identifiable
7745 int StringCheckSum(char *s)
7746 {
7747         int i = 0;
7748         if(s==NULL) return 0;
7749         while(*s) i = i*259 + *s++;
7750         return i;
7751 }
7752
7753 int GameCheckSum()
7754 {
7755         int i, sum=0;
7756         for(i=backwardMostMove; i<forwardMostMove; i++) {
7757                 sum += pvInfoList[i].depth;
7758                 sum += StringCheckSum(parseList[i]);
7759                 sum += StringCheckSum(commentList[i]);
7760                 sum *= 261;
7761         }
7762         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7763         return sum + StringCheckSum(commentList[i]);
7764 } // end of save patch
7765
7766 void
7767 GameEnds(result, resultDetails, whosays)
7768      ChessMove result;
7769      char *resultDetails;
7770      int whosays;
7771 {
7772     GameMode nextGameMode;
7773     int isIcsGame;
7774     char buf[MSG_SIZ];
7775
7776     if(endingGame) return; /* [HGM] crash: forbid recursion */
7777     endingGame = 1;
7778
7779     if (appData.debugMode) {
7780       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7781               result, resultDetails ? resultDetails : "(null)", whosays);
7782     }
7783
7784     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7785         /* If we are playing on ICS, the server decides when the
7786            game is over, but the engine can offer to draw, claim 
7787            a draw, or resign. 
7788          */
7789 #if ZIPPY
7790         if (appData.zippyPlay && first.initDone) {
7791             if (result == GameIsDrawn) {
7792                 /* In case draw still needs to be claimed */
7793                 SendToICS(ics_prefix);
7794                 SendToICS("draw\n");
7795             } else if (StrCaseStr(resultDetails, "resign")) {
7796                 SendToICS(ics_prefix);
7797                 SendToICS("resign\n");
7798             }
7799         }
7800 #endif
7801         endingGame = 0; /* [HGM] crash */
7802         return;
7803     }
7804
7805     /* If we're loading the game from a file, stop */
7806     if (whosays == GE_FILE) {
7807       (void) StopLoadGameTimer();
7808       gameFileFP = NULL;
7809     }
7810
7811     /* Cancel draw offers */
7812     first.offeredDraw = second.offeredDraw = 0;
7813
7814     /* If this is an ICS game, only ICS can really say it's done;
7815        if not, anyone can. */
7816     isIcsGame = (gameMode == IcsPlayingWhite || 
7817                  gameMode == IcsPlayingBlack || 
7818                  gameMode == IcsObserving    || 
7819                  gameMode == IcsExamining);
7820
7821     if (!isIcsGame || whosays == GE_ICS) {
7822         /* OK -- not an ICS game, or ICS said it was done */
7823         StopClocks();
7824         if (!isIcsGame && !appData.noChessProgram) 
7825           SetUserThinkingEnables();
7826     
7827         /* [HGM] if a machine claims the game end we verify this claim */
7828         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7829             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7830                 char claimer;
7831                 ChessMove trueResult = (ChessMove) -1;
7832
7833                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7834                                             first.twoMachinesColor[0] :
7835                                             second.twoMachinesColor[0] ;
7836
7837                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7838                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7839                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7840                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7841                 } else
7842                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7843                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7844                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7845                 } else
7846                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7847                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7848                 }
7849
7850                 // now verify win claims, but not in drop games, as we don't understand those yet
7851                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7852                                                  || gameInfo.variant == VariantGreat) &&
7853                     (result == WhiteWins && claimer == 'w' ||
7854                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7855                       if (appData.debugMode) {
7856                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7857                                 result, epStatus[forwardMostMove], forwardMostMove);
7858                       }
7859                       if(result != trueResult) {
7860                               sprintf(buf, "False win claim: '%s'", resultDetails);
7861                               result = claimer == 'w' ? BlackWins : WhiteWins;
7862                               resultDetails = buf;
7863                       }
7864                 } else
7865                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7866                     && (forwardMostMove <= backwardMostMove ||
7867                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7868                         (claimer=='b')==(forwardMostMove&1))
7869                                                                                   ) {
7870                       /* [HGM] verify: draws that were not flagged are false claims */
7871                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7872                       result = claimer == 'w' ? BlackWins : WhiteWins;
7873                       resultDetails = buf;
7874                 }
7875                 /* (Claiming a loss is accepted no questions asked!) */
7876             }
7877             /* [HGM] bare: don't allow bare King to win */
7878             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7879                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7880                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7881                && result != GameIsDrawn)
7882             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7883                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7884                         int p = (int)boards[forwardMostMove][i][j] - color;
7885                         if(p >= 0 && p <= (int)WhiteKing) k++;
7886                 }
7887                 if (appData.debugMode) {
7888                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7889                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7890                 }
7891                 if(k <= 1) {
7892                         result = GameIsDrawn;
7893                         sprintf(buf, "%s but bare king", resultDetails);
7894                         resultDetails = buf;
7895                 }
7896             }
7897         }
7898
7899
7900         if(serverMoves != NULL && !loadFlag) { char c = '=';
7901             if(result==WhiteWins) c = '+';
7902             if(result==BlackWins) c = '-';
7903             if(resultDetails != NULL)
7904                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7905         }
7906         if (resultDetails != NULL) {
7907             gameInfo.result = result;
7908             gameInfo.resultDetails = StrSave(resultDetails);
7909
7910             /* display last move only if game was not loaded from file */
7911             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7912                 DisplayMove(currentMove - 1);
7913     
7914             if (forwardMostMove != 0) {
7915                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7916                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7917                                                                 ) {
7918                     if (*appData.saveGameFile != NULLCHAR) {
7919                         SaveGameToFile(appData.saveGameFile, TRUE);
7920                     } else if (appData.autoSaveGames) {
7921                         AutoSaveGame();
7922                     }
7923                     if (*appData.savePositionFile != NULLCHAR) {
7924                         SavePositionToFile(appData.savePositionFile);
7925                     }
7926                 }
7927             }
7928
7929             /* Tell program how game ended in case it is learning */
7930             /* [HGM] Moved this to after saving the PGN, just in case */
7931             /* engine died and we got here through time loss. In that */
7932             /* case we will get a fatal error writing the pipe, which */
7933             /* would otherwise lose us the PGN.                       */
7934             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7935             /* output during GameEnds should never be fatal anymore   */
7936             if (gameMode == MachinePlaysWhite ||
7937                 gameMode == MachinePlaysBlack ||
7938                 gameMode == TwoMachinesPlay ||
7939                 gameMode == IcsPlayingWhite ||
7940                 gameMode == IcsPlayingBlack ||
7941                 gameMode == BeginningOfGame) {
7942                 char buf[MSG_SIZ];
7943                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7944                         resultDetails);
7945                 if (first.pr != NoProc) {
7946                     SendToProgram(buf, &first);
7947                 }
7948                 if (second.pr != NoProc &&
7949                     gameMode == TwoMachinesPlay) {
7950                     SendToProgram(buf, &second);
7951                 }
7952             }
7953         }
7954
7955         if (appData.icsActive) {
7956             if (appData.quietPlay &&
7957                 (gameMode == IcsPlayingWhite ||
7958                  gameMode == IcsPlayingBlack)) {
7959                 SendToICS(ics_prefix);
7960                 SendToICS("set shout 1\n");
7961             }
7962             nextGameMode = IcsIdle;
7963             ics_user_moved = FALSE;
7964             /* clean up premove.  It's ugly when the game has ended and the
7965              * premove highlights are still on the board.
7966              */
7967             if (gotPremove) {
7968               gotPremove = FALSE;
7969               ClearPremoveHighlights();
7970               DrawPosition(FALSE, boards[currentMove]);
7971             }
7972             if (whosays == GE_ICS) {
7973                 switch (result) {
7974                 case WhiteWins:
7975                     if (gameMode == IcsPlayingWhite)
7976                         PlayIcsWinSound();
7977                     else if(gameMode == IcsPlayingBlack)
7978                         PlayIcsLossSound();
7979                     break;
7980                 case BlackWins:
7981                     if (gameMode == IcsPlayingBlack)
7982                         PlayIcsWinSound();
7983                     else if(gameMode == IcsPlayingWhite)
7984                         PlayIcsLossSound();
7985                     break;
7986                 case GameIsDrawn:
7987                     PlayIcsDrawSound();
7988                     break;
7989                 default:
7990                     PlayIcsUnfinishedSound();
7991                 }
7992             }
7993         } else if (gameMode == EditGame ||
7994                    gameMode == PlayFromGameFile || 
7995                    gameMode == AnalyzeMode || 
7996                    gameMode == AnalyzeFile) {
7997             nextGameMode = gameMode;
7998         } else {
7999             nextGameMode = EndOfGame;
8000         }
8001         pausing = FALSE;
8002         ModeHighlight();
8003     } else {
8004         nextGameMode = gameMode;
8005     }
8006
8007     if (appData.noChessProgram) {
8008         gameMode = nextGameMode;
8009         ModeHighlight();
8010         endingGame = 0; /* [HGM] crash */
8011         return;
8012     }
8013
8014     if (first.reuse) {
8015         /* Put first chess program into idle state */
8016         if (first.pr != NoProc &&
8017             (gameMode == MachinePlaysWhite ||
8018              gameMode == MachinePlaysBlack ||
8019              gameMode == TwoMachinesPlay ||
8020              gameMode == IcsPlayingWhite ||
8021              gameMode == IcsPlayingBlack ||
8022              gameMode == BeginningOfGame)) {
8023             SendToProgram("force\n", &first);
8024             if (first.usePing) {
8025               char buf[MSG_SIZ];
8026               sprintf(buf, "ping %d\n", ++first.lastPing);
8027               SendToProgram(buf, &first);
8028             }
8029         }
8030     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8031         /* Kill off first chess program */
8032         if (first.isr != NULL)
8033           RemoveInputSource(first.isr);
8034         first.isr = NULL;
8035     
8036         if (first.pr != NoProc) {
8037             ExitAnalyzeMode();
8038             DoSleep( appData.delayBeforeQuit );
8039             SendToProgram("quit\n", &first);
8040             DoSleep( appData.delayAfterQuit );
8041             DestroyChildProcess(first.pr, first.useSigterm);
8042         }
8043         first.pr = NoProc;
8044     }
8045     if (second.reuse) {
8046         /* Put second chess program into idle state */
8047         if (second.pr != NoProc &&
8048             gameMode == TwoMachinesPlay) {
8049             SendToProgram("force\n", &second);
8050             if (second.usePing) {
8051               char buf[MSG_SIZ];
8052               sprintf(buf, "ping %d\n", ++second.lastPing);
8053               SendToProgram(buf, &second);
8054             }
8055         }
8056     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8057         /* Kill off second chess program */
8058         if (second.isr != NULL)
8059           RemoveInputSource(second.isr);
8060         second.isr = NULL;
8061     
8062         if (second.pr != NoProc) {
8063             DoSleep( appData.delayBeforeQuit );
8064             SendToProgram("quit\n", &second);
8065             DoSleep( appData.delayAfterQuit );
8066             DestroyChildProcess(second.pr, second.useSigterm);
8067         }
8068         second.pr = NoProc;
8069     }
8070
8071     if (matchMode && gameMode == TwoMachinesPlay) {
8072         switch (result) {
8073         case WhiteWins:
8074           if (first.twoMachinesColor[0] == 'w') {
8075             first.matchWins++;
8076           } else {
8077             second.matchWins++;
8078           }
8079           break;
8080         case BlackWins:
8081           if (first.twoMachinesColor[0] == 'b') {
8082             first.matchWins++;
8083           } else {
8084             second.matchWins++;
8085           }
8086           break;
8087         default:
8088           break;
8089         }
8090         if (matchGame < appData.matchGames) {
8091             char *tmp;
8092             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8093                 tmp = first.twoMachinesColor;
8094                 first.twoMachinesColor = second.twoMachinesColor;
8095                 second.twoMachinesColor = tmp;
8096             }
8097             gameMode = nextGameMode;
8098             matchGame++;
8099             if(appData.matchPause>10000 || appData.matchPause<10)
8100                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8101             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8102             endingGame = 0; /* [HGM] crash */
8103             return;
8104         } else {
8105             char buf[MSG_SIZ];
8106             gameMode = nextGameMode;
8107             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8108                     first.tidy, second.tidy,
8109                     first.matchWins, second.matchWins,
8110                     appData.matchGames - (first.matchWins + second.matchWins));
8111             DisplayFatalError(buf, 0, 0);
8112         }
8113     }
8114     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8115         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8116       ExitAnalyzeMode();
8117     gameMode = nextGameMode;
8118     ModeHighlight();
8119     endingGame = 0;  /* [HGM] crash */
8120 }
8121
8122 /* Assumes program was just initialized (initString sent).
8123    Leaves program in force mode. */
8124 void
8125 FeedMovesToProgram(cps, upto) 
8126      ChessProgramState *cps;
8127      int upto;
8128 {
8129     int i;
8130     
8131     if (appData.debugMode)
8132       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8133               startedFromSetupPosition ? "position and " : "",
8134               backwardMostMove, upto, cps->which);
8135     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8136         // [HGM] variantswitch: make engine aware of new variant
8137         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8138                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8139         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8140         SendToProgram(buf, cps);
8141         currentlyInitializedVariant = gameInfo.variant;
8142     }
8143     SendToProgram("force\n", cps);
8144     if (startedFromSetupPosition) {
8145         SendBoard(cps, backwardMostMove);
8146     if (appData.debugMode) {
8147         fprintf(debugFP, "feedMoves\n");
8148     }
8149     }
8150     for (i = backwardMostMove; i < upto; i++) {
8151         SendMoveToProgram(i, cps);
8152     }
8153 }
8154
8155
8156 void
8157 ResurrectChessProgram()
8158 {
8159      /* The chess program may have exited.
8160         If so, restart it and feed it all the moves made so far. */
8161
8162     if (appData.noChessProgram || first.pr != NoProc) return;
8163     
8164     StartChessProgram(&first);
8165     InitChessProgram(&first, FALSE);
8166     FeedMovesToProgram(&first, currentMove);
8167
8168     if (!first.sendTime) {
8169         /* can't tell gnuchess what its clock should read,
8170            so we bow to its notion. */
8171         ResetClocks();
8172         timeRemaining[0][currentMove] = whiteTimeRemaining;
8173         timeRemaining[1][currentMove] = blackTimeRemaining;
8174     }
8175
8176     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8177                 appData.icsEngineAnalyze) && first.analysisSupport) {
8178       SendToProgram("analyze\n", &first);
8179       first.analyzing = TRUE;
8180     }
8181 }
8182
8183 /*
8184  * Button procedures
8185  */
8186 void
8187 Reset(redraw, init)
8188      int redraw, init;
8189 {
8190     int i;
8191
8192     if (appData.debugMode) {
8193         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8194                 redraw, init, gameMode);
8195     }
8196     pausing = pauseExamInvalid = FALSE;
8197     startedFromSetupPosition = blackPlaysFirst = FALSE;
8198     firstMove = TRUE;
8199     whiteFlag = blackFlag = FALSE;
8200     userOfferedDraw = FALSE;
8201     hintRequested = bookRequested = FALSE;
8202     first.maybeThinking = FALSE;
8203     second.maybeThinking = FALSE;
8204     first.bookSuspend = FALSE; // [HGM] book
8205     second.bookSuspend = FALSE;
8206     thinkOutput[0] = NULLCHAR;
8207     lastHint[0] = NULLCHAR;
8208     ClearGameInfo(&gameInfo);
8209     gameInfo.variant = StringToVariant(appData.variant);
8210     ics_user_moved = ics_clock_paused = FALSE;
8211     ics_getting_history = H_FALSE;
8212     ics_gamenum = -1;
8213     white_holding[0] = black_holding[0] = NULLCHAR;
8214     ClearProgramStats();
8215     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8216     
8217     ResetFrontEnd();
8218     ClearHighlights();
8219     flipView = appData.flipView;
8220     ClearPremoveHighlights();
8221     gotPremove = FALSE;
8222     alarmSounded = FALSE;
8223
8224     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8225     if(appData.serverMovesName != NULL) {
8226         /* [HGM] prepare to make moves file for broadcasting */
8227         clock_t t = clock();
8228         if(serverMoves != NULL) fclose(serverMoves);
8229         serverMoves = fopen(appData.serverMovesName, "r");
8230         if(serverMoves != NULL) {
8231             fclose(serverMoves);
8232             /* delay 15 sec before overwriting, so all clients can see end */
8233             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8234         }
8235         serverMoves = fopen(appData.serverMovesName, "w");
8236     }
8237
8238     ExitAnalyzeMode();
8239     gameMode = BeginningOfGame;
8240     ModeHighlight();
8241     if(appData.icsActive) gameInfo.variant = VariantNormal;
8242     currentMove = forwardMostMove = backwardMostMove = 0;
8243     InitPosition(redraw);
8244     for (i = 0; i < MAX_MOVES; i++) {
8245         if (commentList[i] != NULL) {
8246             free(commentList[i]);
8247             commentList[i] = NULL;
8248         }
8249     }
8250     ResetClocks();
8251     timeRemaining[0][0] = whiteTimeRemaining;
8252     timeRemaining[1][0] = blackTimeRemaining;
8253     if (first.pr == NULL) {
8254         StartChessProgram(&first);
8255     }
8256     if (init) {
8257             InitChessProgram(&first, startedFromSetupPosition);
8258     }
8259     DisplayTitle("");
8260     DisplayMessage("", "");
8261     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8262     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8263 }
8264
8265 void
8266 AutoPlayGameLoop()
8267 {
8268     for (;;) {
8269         if (!AutoPlayOneMove())
8270           return;
8271         if (matchMode || appData.timeDelay == 0)
8272           continue;
8273         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8274           return;
8275         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8276         break;
8277     }
8278 }
8279
8280
8281 int
8282 AutoPlayOneMove()
8283 {
8284     int fromX, fromY, toX, toY;
8285
8286     if (appData.debugMode) {
8287       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8288     }
8289
8290     if (gameMode != PlayFromGameFile)
8291       return FALSE;
8292
8293     if (currentMove >= forwardMostMove) {
8294       gameMode = EditGame;
8295       ModeHighlight();
8296
8297       /* [AS] Clear current move marker at the end of a game */
8298       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8299
8300       return FALSE;
8301     }
8302     
8303     toX = moveList[currentMove][2] - AAA;
8304     toY = moveList[currentMove][3] - ONE;
8305
8306     if (moveList[currentMove][1] == '@') {
8307         if (appData.highlightLastMove) {
8308             SetHighlights(-1, -1, toX, toY);
8309         }
8310     } else {
8311         fromX = moveList[currentMove][0] - AAA;
8312         fromY = moveList[currentMove][1] - ONE;
8313
8314         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8315
8316         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8317
8318         if (appData.highlightLastMove) {
8319             SetHighlights(fromX, fromY, toX, toY);
8320         }
8321     }
8322     DisplayMove(currentMove);
8323     SendMoveToProgram(currentMove++, &first);
8324     DisplayBothClocks();
8325     DrawPosition(FALSE, boards[currentMove]);
8326     // [HGM] PV info: always display, routine tests if empty
8327     DisplayComment(currentMove - 1, commentList[currentMove]);
8328     return TRUE;
8329 }
8330
8331
8332 int
8333 LoadGameOneMove(readAhead)
8334      ChessMove readAhead;
8335 {
8336     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8337     char promoChar = NULLCHAR;
8338     ChessMove moveType;
8339     char move[MSG_SIZ];
8340     char *p, *q;
8341     
8342     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8343         gameMode != AnalyzeMode && gameMode != Training) {
8344         gameFileFP = NULL;
8345         return FALSE;
8346     }
8347     
8348     yyboardindex = forwardMostMove;
8349     if (readAhead != (ChessMove)0) {
8350       moveType = readAhead;
8351     } else {
8352       if (gameFileFP == NULL)
8353           return FALSE;
8354       moveType = (ChessMove) yylex();
8355     }
8356     
8357     done = FALSE;
8358     switch (moveType) {
8359       case Comment:
8360         if (appData.debugMode) 
8361           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8362         p = yy_text;
8363         if (*p == '{' || *p == '[' || *p == '(') {
8364             p[strlen(p) - 1] = NULLCHAR;
8365             p++;
8366         }
8367
8368         /* append the comment but don't display it */
8369         while (*p == '\n') p++;
8370         AppendComment(currentMove, p);
8371         return TRUE;
8372
8373       case WhiteCapturesEnPassant:
8374       case BlackCapturesEnPassant:
8375       case WhitePromotionChancellor:
8376       case BlackPromotionChancellor:
8377       case WhitePromotionArchbishop:
8378       case BlackPromotionArchbishop:
8379       case WhitePromotionCentaur:
8380       case BlackPromotionCentaur:
8381       case WhitePromotionQueen:
8382       case BlackPromotionQueen:
8383       case WhitePromotionRook:
8384       case BlackPromotionRook:
8385       case WhitePromotionBishop:
8386       case BlackPromotionBishop:
8387       case WhitePromotionKnight:
8388       case BlackPromotionKnight:
8389       case WhitePromotionKing:
8390       case BlackPromotionKing:
8391       case NormalMove:
8392       case WhiteKingSideCastle:
8393       case WhiteQueenSideCastle:
8394       case BlackKingSideCastle:
8395       case BlackQueenSideCastle:
8396       case WhiteKingSideCastleWild:
8397       case WhiteQueenSideCastleWild:
8398       case BlackKingSideCastleWild:
8399       case BlackQueenSideCastleWild:
8400       /* PUSH Fabien */
8401       case WhiteHSideCastleFR:
8402       case WhiteASideCastleFR:
8403       case BlackHSideCastleFR:
8404       case BlackASideCastleFR:
8405       /* POP Fabien */
8406         if (appData.debugMode)
8407           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8408         fromX = currentMoveString[0] - AAA;
8409         fromY = currentMoveString[1] - ONE;
8410         toX = currentMoveString[2] - AAA;
8411         toY = currentMoveString[3] - ONE;
8412         promoChar = currentMoveString[4];
8413         break;
8414
8415       case WhiteDrop:
8416       case BlackDrop:
8417         if (appData.debugMode)
8418           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8419         fromX = moveType == WhiteDrop ?
8420           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8421         (int) CharToPiece(ToLower(currentMoveString[0]));
8422         fromY = DROP_RANK;
8423         toX = currentMoveString[2] - AAA;
8424         toY = currentMoveString[3] - ONE;
8425         break;
8426
8427       case WhiteWins:
8428       case BlackWins:
8429       case GameIsDrawn:
8430       case GameUnfinished:
8431         if (appData.debugMode)
8432           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8433         p = strchr(yy_text, '{');
8434         if (p == NULL) p = strchr(yy_text, '(');
8435         if (p == NULL) {
8436             p = yy_text;
8437             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8438         } else {
8439             q = strchr(p, *p == '{' ? '}' : ')');
8440             if (q != NULL) *q = NULLCHAR;
8441             p++;
8442         }
8443         GameEnds(moveType, p, GE_FILE);
8444         done = TRUE;
8445         if (cmailMsgLoaded) {
8446             ClearHighlights();
8447             flipView = WhiteOnMove(currentMove);
8448             if (moveType == GameUnfinished) flipView = !flipView;
8449             if (appData.debugMode)
8450               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8451         }
8452         break;
8453
8454       case (ChessMove) 0:       /* end of file */
8455         if (appData.debugMode)
8456           fprintf(debugFP, "Parser hit end of file\n");
8457         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8458                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8459           case MT_NONE:
8460           case MT_CHECK:
8461             break;
8462           case MT_CHECKMATE:
8463           case MT_STAINMATE:
8464             if (WhiteOnMove(currentMove)) {
8465                 GameEnds(BlackWins, "Black mates", GE_FILE);
8466             } else {
8467                 GameEnds(WhiteWins, "White mates", GE_FILE);
8468             }
8469             break;
8470           case MT_STALEMATE:
8471             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8472             break;
8473         }
8474         done = TRUE;
8475         break;
8476
8477       case MoveNumberOne:
8478         if (lastLoadGameStart == GNUChessGame) {
8479             /* GNUChessGames have numbers, but they aren't move numbers */
8480             if (appData.debugMode)
8481               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8482                       yy_text, (int) moveType);
8483             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8484         }
8485         /* else fall thru */
8486
8487       case XBoardGame:
8488       case GNUChessGame:
8489       case PGNTag:
8490         /* Reached start of next game in file */
8491         if (appData.debugMode)
8492           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8493         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8494                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8495           case MT_NONE:
8496           case MT_CHECK:
8497             break;
8498           case MT_CHECKMATE:
8499           case MT_STAINMATE:
8500             if (WhiteOnMove(currentMove)) {
8501                 GameEnds(BlackWins, "Black mates", GE_FILE);
8502             } else {
8503                 GameEnds(WhiteWins, "White mates", GE_FILE);
8504             }
8505             break;
8506           case MT_STALEMATE:
8507             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8508             break;
8509         }
8510         done = TRUE;
8511         break;
8512
8513       case PositionDiagram:     /* should not happen; ignore */
8514       case ElapsedTime:         /* ignore */
8515       case NAG:                 /* ignore */
8516         if (appData.debugMode)
8517           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8518                   yy_text, (int) moveType);
8519         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8520
8521       case IllegalMove:
8522         if (appData.testLegality) {
8523             if (appData.debugMode)
8524               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8525             sprintf(move, _("Illegal move: %d.%s%s"),
8526                     (forwardMostMove / 2) + 1,
8527                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8528             DisplayError(move, 0);
8529             done = TRUE;
8530         } else {
8531             if (appData.debugMode)
8532               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8533                       yy_text, currentMoveString);
8534             fromX = currentMoveString[0] - AAA;
8535             fromY = currentMoveString[1] - ONE;
8536             toX = currentMoveString[2] - AAA;
8537             toY = currentMoveString[3] - ONE;
8538             promoChar = currentMoveString[4];
8539         }
8540         break;
8541
8542       case AmbiguousMove:
8543         if (appData.debugMode)
8544           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8545         sprintf(move, _("Ambiguous move: %d.%s%s"),
8546                 (forwardMostMove / 2) + 1,
8547                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8548         DisplayError(move, 0);
8549         done = TRUE;
8550         break;
8551
8552       default:
8553       case ImpossibleMove:
8554         if (appData.debugMode)
8555           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8556         sprintf(move, _("Illegal move: %d.%s%s"),
8557                 (forwardMostMove / 2) + 1,
8558                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8559         DisplayError(move, 0);
8560         done = TRUE;
8561         break;
8562     }
8563
8564     if (done) {
8565         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8566             DrawPosition(FALSE, boards[currentMove]);
8567             DisplayBothClocks();
8568             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8569               DisplayComment(currentMove - 1, commentList[currentMove]);
8570         }
8571         (void) StopLoadGameTimer();
8572         gameFileFP = NULL;
8573         cmailOldMove = forwardMostMove;
8574         return FALSE;
8575     } else {
8576         /* currentMoveString is set as a side-effect of yylex */
8577         strcat(currentMoveString, "\n");
8578         strcpy(moveList[forwardMostMove], currentMoveString);
8579         
8580         thinkOutput[0] = NULLCHAR;
8581         MakeMove(fromX, fromY, toX, toY, promoChar);
8582         currentMove = forwardMostMove;
8583         return TRUE;
8584     }
8585 }
8586
8587 /* Load the nth game from the given file */
8588 int
8589 LoadGameFromFile(filename, n, title, useList)
8590      char *filename;
8591      int n;
8592      char *title;
8593      /*Boolean*/ int useList;
8594 {
8595     FILE *f;
8596     char buf[MSG_SIZ];
8597
8598     if (strcmp(filename, "-") == 0) {
8599         f = stdin;
8600         title = "stdin";
8601     } else {
8602         f = fopen(filename, "rb");
8603         if (f == NULL) {
8604           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8605             DisplayError(buf, errno);
8606             return FALSE;
8607         }
8608     }
8609     if (fseek(f, 0, 0) == -1) {
8610         /* f is not seekable; probably a pipe */
8611         useList = FALSE;
8612     }
8613     if (useList && n == 0) {
8614         int error = GameListBuild(f);
8615         if (error) {
8616             DisplayError(_("Cannot build game list"), error);
8617         } else if (!ListEmpty(&gameList) &&
8618                    ((ListGame *) gameList.tailPred)->number > 1) {
8619             GameListPopUp(f, title);
8620             return TRUE;
8621         }
8622         GameListDestroy();
8623         n = 1;
8624     }
8625     if (n == 0) n = 1;
8626     return LoadGame(f, n, title, FALSE);
8627 }
8628
8629
8630 void
8631 MakeRegisteredMove()
8632 {
8633     int fromX, fromY, toX, toY;
8634     char promoChar;
8635     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8636         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8637           case CMAIL_MOVE:
8638           case CMAIL_DRAW:
8639             if (appData.debugMode)
8640               fprintf(debugFP, "Restoring %s for game %d\n",
8641                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8642     
8643             thinkOutput[0] = NULLCHAR;
8644             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8645             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8646             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8647             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8648             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8649             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8650             MakeMove(fromX, fromY, toX, toY, promoChar);
8651             ShowMove(fromX, fromY, toX, toY);
8652               
8653             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8654                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8655               case MT_NONE:
8656               case MT_CHECK:
8657                 break;
8658                 
8659               case MT_CHECKMATE:
8660               case MT_STAINMATE:
8661                 if (WhiteOnMove(currentMove)) {
8662                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8663                 } else {
8664                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8665                 }
8666                 break;
8667                 
8668               case MT_STALEMATE:
8669                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8670                 break;
8671             }
8672
8673             break;
8674             
8675           case CMAIL_RESIGN:
8676             if (WhiteOnMove(currentMove)) {
8677                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8678             } else {
8679                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8680             }
8681             break;
8682             
8683           case CMAIL_ACCEPT:
8684             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8685             break;
8686               
8687           default:
8688             break;
8689         }
8690     }
8691
8692     return;
8693 }
8694
8695 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8696 int
8697 CmailLoadGame(f, gameNumber, title, useList)
8698      FILE *f;
8699      int gameNumber;
8700      char *title;
8701      int useList;
8702 {
8703     int retVal;
8704
8705     if (gameNumber > nCmailGames) {
8706         DisplayError(_("No more games in this message"), 0);
8707         return FALSE;
8708     }
8709     if (f == lastLoadGameFP) {
8710         int offset = gameNumber - lastLoadGameNumber;
8711         if (offset == 0) {
8712             cmailMsg[0] = NULLCHAR;
8713             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8714                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8715                 nCmailMovesRegistered--;
8716             }
8717             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8718             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8719                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8720             }
8721         } else {
8722             if (! RegisterMove()) return FALSE;
8723         }
8724     }
8725
8726     retVal = LoadGame(f, gameNumber, title, useList);
8727
8728     /* Make move registered during previous look at this game, if any */
8729     MakeRegisteredMove();
8730
8731     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8732         commentList[currentMove]
8733           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8734         DisplayComment(currentMove - 1, commentList[currentMove]);
8735     }
8736
8737     return retVal;
8738 }
8739
8740 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8741 int
8742 ReloadGame(offset)
8743      int offset;
8744 {
8745     int gameNumber = lastLoadGameNumber + offset;
8746     if (lastLoadGameFP == NULL) {
8747         DisplayError(_("No game has been loaded yet"), 0);
8748         return FALSE;
8749     }
8750     if (gameNumber <= 0) {
8751         DisplayError(_("Can't back up any further"), 0);
8752         return FALSE;
8753     }
8754     if (cmailMsgLoaded) {
8755         return CmailLoadGame(lastLoadGameFP, gameNumber,
8756                              lastLoadGameTitle, lastLoadGameUseList);
8757     } else {
8758         return LoadGame(lastLoadGameFP, gameNumber,
8759                         lastLoadGameTitle, lastLoadGameUseList);
8760     }
8761 }
8762
8763
8764
8765 /* Load the nth game from open file f */
8766 int
8767 LoadGame(f, gameNumber, title, useList)
8768      FILE *f;
8769      int gameNumber;
8770      char *title;
8771      int useList;
8772 {
8773     ChessMove cm;
8774     char buf[MSG_SIZ];
8775     int gn = gameNumber;
8776     ListGame *lg = NULL;
8777     int numPGNTags = 0;
8778     int err;
8779     GameMode oldGameMode;
8780     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8781
8782     if (appData.debugMode) 
8783         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8784
8785     if (gameMode == Training )
8786         SetTrainingModeOff();
8787
8788     oldGameMode = gameMode;
8789     if (gameMode != BeginningOfGame) {
8790       Reset(FALSE, TRUE);
8791     }
8792
8793     gameFileFP = f;
8794     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8795         fclose(lastLoadGameFP);
8796     }
8797
8798     if (useList) {
8799         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8800         
8801         if (lg) {
8802             fseek(f, lg->offset, 0);
8803             GameListHighlight(gameNumber);
8804             gn = 1;
8805         }
8806         else {
8807             DisplayError(_("Game number out of range"), 0);
8808             return FALSE;
8809         }
8810     } else {
8811         GameListDestroy();
8812         if (fseek(f, 0, 0) == -1) {
8813             if (f == lastLoadGameFP ?
8814                 gameNumber == lastLoadGameNumber + 1 :
8815                 gameNumber == 1) {
8816                 gn = 1;
8817             } else {
8818                 DisplayError(_("Can't seek on game file"), 0);
8819                 return FALSE;
8820             }
8821         }
8822     }
8823     lastLoadGameFP = f;
8824     lastLoadGameNumber = gameNumber;
8825     strcpy(lastLoadGameTitle, title);
8826     lastLoadGameUseList = useList;
8827
8828     yynewfile(f);
8829
8830     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8831       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8832                 lg->gameInfo.black);
8833             DisplayTitle(buf);
8834     } else if (*title != NULLCHAR) {
8835         if (gameNumber > 1) {
8836             sprintf(buf, "%s %d", title, gameNumber);
8837             DisplayTitle(buf);
8838         } else {
8839             DisplayTitle(title);
8840         }
8841     }
8842
8843     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8844         gameMode = PlayFromGameFile;
8845         ModeHighlight();
8846     }
8847
8848     currentMove = forwardMostMove = backwardMostMove = 0;
8849     CopyBoard(boards[0], initialPosition);
8850     StopClocks();
8851
8852     /*
8853      * Skip the first gn-1 games in the file.
8854      * Also skip over anything that precedes an identifiable 
8855      * start of game marker, to avoid being confused by 
8856      * garbage at the start of the file.  Currently 
8857      * recognized start of game markers are the move number "1",
8858      * the pattern "gnuchess .* game", the pattern
8859      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8860      * A game that starts with one of the latter two patterns
8861      * will also have a move number 1, possibly
8862      * following a position diagram.
8863      * 5-4-02: Let's try being more lenient and allowing a game to
8864      * start with an unnumbered move.  Does that break anything?
8865      */
8866     cm = lastLoadGameStart = (ChessMove) 0;
8867     while (gn > 0) {
8868         yyboardindex = forwardMostMove;
8869         cm = (ChessMove) yylex();
8870         switch (cm) {
8871           case (ChessMove) 0:
8872             if (cmailMsgLoaded) {
8873                 nCmailGames = CMAIL_MAX_GAMES - gn;
8874             } else {
8875                 Reset(TRUE, TRUE);
8876                 DisplayError(_("Game not found in file"), 0);
8877             }
8878             return FALSE;
8879
8880           case GNUChessGame:
8881           case XBoardGame:
8882             gn--;
8883             lastLoadGameStart = cm;
8884             break;
8885             
8886           case MoveNumberOne:
8887             switch (lastLoadGameStart) {
8888               case GNUChessGame:
8889               case XBoardGame:
8890               case PGNTag:
8891                 break;
8892               case MoveNumberOne:
8893               case (ChessMove) 0:
8894                 gn--;           /* count this game */
8895                 lastLoadGameStart = cm;
8896                 break;
8897               default:
8898                 /* impossible */
8899                 break;
8900             }
8901             break;
8902
8903           case PGNTag:
8904             switch (lastLoadGameStart) {
8905               case GNUChessGame:
8906               case PGNTag:
8907               case MoveNumberOne:
8908               case (ChessMove) 0:
8909                 gn--;           /* count this game */
8910                 lastLoadGameStart = cm;
8911                 break;
8912               case XBoardGame:
8913                 lastLoadGameStart = cm; /* game counted already */
8914                 break;
8915               default:
8916                 /* impossible */
8917                 break;
8918             }
8919             if (gn > 0) {
8920                 do {
8921                     yyboardindex = forwardMostMove;
8922                     cm = (ChessMove) yylex();
8923                 } while (cm == PGNTag || cm == Comment);
8924             }
8925             break;
8926
8927           case WhiteWins:
8928           case BlackWins:
8929           case GameIsDrawn:
8930             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8931                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8932                     != CMAIL_OLD_RESULT) {
8933                     nCmailResults ++ ;
8934                     cmailResult[  CMAIL_MAX_GAMES
8935                                 - gn - 1] = CMAIL_OLD_RESULT;
8936                 }
8937             }
8938             break;
8939
8940           case NormalMove:
8941             /* Only a NormalMove can be at the start of a game
8942              * without a position diagram. */
8943             if (lastLoadGameStart == (ChessMove) 0) {
8944               gn--;
8945               lastLoadGameStart = MoveNumberOne;
8946             }
8947             break;
8948
8949           default:
8950             break;
8951         }
8952     }
8953     
8954     if (appData.debugMode)
8955       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8956
8957     if (cm == XBoardGame) {
8958         /* Skip any header junk before position diagram and/or move 1 */
8959         for (;;) {
8960             yyboardindex = forwardMostMove;
8961             cm = (ChessMove) yylex();
8962
8963             if (cm == (ChessMove) 0 ||
8964                 cm == GNUChessGame || cm == XBoardGame) {
8965                 /* Empty game; pretend end-of-file and handle later */
8966                 cm = (ChessMove) 0;
8967                 break;
8968             }
8969
8970             if (cm == MoveNumberOne || cm == PositionDiagram ||
8971                 cm == PGNTag || cm == Comment)
8972               break;
8973         }
8974     } else if (cm == GNUChessGame) {
8975         if (gameInfo.event != NULL) {
8976             free(gameInfo.event);
8977         }
8978         gameInfo.event = StrSave(yy_text);
8979     }   
8980
8981     startedFromSetupPosition = FALSE;
8982     while (cm == PGNTag) {
8983         if (appData.debugMode) 
8984           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8985         err = ParsePGNTag(yy_text, &gameInfo);
8986         if (!err) numPGNTags++;
8987
8988         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8989         if(gameInfo.variant != oldVariant) {
8990             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8991             InitPosition(TRUE);
8992             oldVariant = gameInfo.variant;
8993             if (appData.debugMode) 
8994               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8995         }
8996
8997
8998         if (gameInfo.fen != NULL) {
8999           Board initial_position;
9000           startedFromSetupPosition = TRUE;
9001           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9002             Reset(TRUE, TRUE);
9003             DisplayError(_("Bad FEN position in file"), 0);
9004             return FALSE;
9005           }
9006           CopyBoard(boards[0], initial_position);
9007           if (blackPlaysFirst) {
9008             currentMove = forwardMostMove = backwardMostMove = 1;
9009             CopyBoard(boards[1], initial_position);
9010             strcpy(moveList[0], "");
9011             strcpy(parseList[0], "");
9012             timeRemaining[0][1] = whiteTimeRemaining;
9013             timeRemaining[1][1] = blackTimeRemaining;
9014             if (commentList[0] != NULL) {
9015               commentList[1] = commentList[0];
9016               commentList[0] = NULL;
9017             }
9018           } else {
9019             currentMove = forwardMostMove = backwardMostMove = 0;
9020           }
9021           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9022           {   int i;
9023               initialRulePlies = FENrulePlies;
9024               epStatus[forwardMostMove] = FENepStatus;
9025               for( i=0; i< nrCastlingRights; i++ )
9026                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9027           }
9028           yyboardindex = forwardMostMove;
9029           free(gameInfo.fen);
9030           gameInfo.fen = NULL;
9031         }
9032
9033         yyboardindex = forwardMostMove;
9034         cm = (ChessMove) yylex();
9035
9036         /* Handle comments interspersed among the tags */
9037         while (cm == Comment) {
9038             char *p;
9039             if (appData.debugMode) 
9040               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9041             p = yy_text;
9042             if (*p == '{' || *p == '[' || *p == '(') {
9043                 p[strlen(p) - 1] = NULLCHAR;
9044                 p++;
9045             }
9046             while (*p == '\n') p++;
9047             AppendComment(currentMove, p);
9048             yyboardindex = forwardMostMove;
9049             cm = (ChessMove) yylex();
9050         }
9051     }
9052
9053     /* don't rely on existence of Event tag since if game was
9054      * pasted from clipboard the Event tag may not exist
9055      */
9056     if (numPGNTags > 0){
9057         char *tags;
9058         if (gameInfo.variant == VariantNormal) {
9059           gameInfo.variant = StringToVariant(gameInfo.event);
9060         }
9061         if (!matchMode) {
9062           if( appData.autoDisplayTags ) {
9063             tags = PGNTags(&gameInfo);
9064             TagsPopUp(tags, CmailMsg());
9065             free(tags);
9066           }
9067         }
9068     } else {
9069         /* Make something up, but don't display it now */
9070         SetGameInfo();
9071         TagsPopDown();
9072     }
9073
9074     if (cm == PositionDiagram) {
9075         int i, j;
9076         char *p;
9077         Board initial_position;
9078
9079         if (appData.debugMode)
9080           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9081
9082         if (!startedFromSetupPosition) {
9083             p = yy_text;
9084             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9085               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9086                 switch (*p) {
9087                   case '[':
9088                   case '-':
9089                   case ' ':
9090                   case '\t':
9091                   case '\n':
9092                   case '\r':
9093                     break;
9094                   default:
9095                     initial_position[i][j++] = CharToPiece(*p);
9096                     break;
9097                 }
9098             while (*p == ' ' || *p == '\t' ||
9099                    *p == '\n' || *p == '\r') p++;
9100         
9101             if (strncmp(p, "black", strlen("black"))==0)
9102               blackPlaysFirst = TRUE;
9103             else
9104               blackPlaysFirst = FALSE;
9105             startedFromSetupPosition = TRUE;
9106         
9107             CopyBoard(boards[0], initial_position);
9108             if (blackPlaysFirst) {
9109                 currentMove = forwardMostMove = backwardMostMove = 1;
9110                 CopyBoard(boards[1], initial_position);
9111                 strcpy(moveList[0], "");
9112                 strcpy(parseList[0], "");
9113                 timeRemaining[0][1] = whiteTimeRemaining;
9114                 timeRemaining[1][1] = blackTimeRemaining;
9115                 if (commentList[0] != NULL) {
9116                     commentList[1] = commentList[0];
9117                     commentList[0] = NULL;
9118                 }
9119             } else {
9120                 currentMove = forwardMostMove = backwardMostMove = 0;
9121             }
9122         }
9123         yyboardindex = forwardMostMove;
9124         cm = (ChessMove) yylex();
9125     }
9126
9127     if (first.pr == NoProc) {
9128         StartChessProgram(&first);
9129     }
9130     InitChessProgram(&first, FALSE);
9131     SendToProgram("force\n", &first);
9132     if (startedFromSetupPosition) {
9133         SendBoard(&first, forwardMostMove);
9134     if (appData.debugMode) {
9135         fprintf(debugFP, "Load Game\n");
9136     }
9137         DisplayBothClocks();
9138     }      
9139
9140     /* [HGM] server: flag to write setup moves in broadcast file as one */
9141     loadFlag = appData.suppressLoadMoves;
9142
9143     while (cm == Comment) {
9144         char *p;
9145         if (appData.debugMode) 
9146           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9147         p = yy_text;
9148         if (*p == '{' || *p == '[' || *p == '(') {
9149             p[strlen(p) - 1] = NULLCHAR;
9150             p++;
9151         }
9152         while (*p == '\n') p++;
9153         AppendComment(currentMove, p);
9154         yyboardindex = forwardMostMove;
9155         cm = (ChessMove) yylex();
9156     }
9157
9158     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9159         cm == WhiteWins || cm == BlackWins ||
9160         cm == GameIsDrawn || cm == GameUnfinished) {
9161         DisplayMessage("", _("No moves in game"));
9162         if (cmailMsgLoaded) {
9163             if (appData.debugMode)
9164               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9165             ClearHighlights();
9166             flipView = FALSE;
9167         }
9168         DrawPosition(FALSE, boards[currentMove]);
9169         DisplayBothClocks();
9170         gameMode = EditGame;
9171         ModeHighlight();
9172         gameFileFP = NULL;
9173         cmailOldMove = 0;
9174         return TRUE;
9175     }
9176
9177     // [HGM] PV info: routine tests if comment empty
9178     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9179         DisplayComment(currentMove - 1, commentList[currentMove]);
9180     }
9181     if (!matchMode && appData.timeDelay != 0) 
9182       DrawPosition(FALSE, boards[currentMove]);
9183
9184     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9185       programStats.ok_to_send = 1;
9186     }
9187
9188     /* if the first token after the PGN tags is a move
9189      * and not move number 1, retrieve it from the parser 
9190      */
9191     if (cm != MoveNumberOne)
9192         LoadGameOneMove(cm);
9193
9194     /* load the remaining moves from the file */
9195     while (LoadGameOneMove((ChessMove)0)) {
9196       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9197       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9198     }
9199
9200     /* rewind to the start of the game */
9201     currentMove = backwardMostMove;
9202
9203     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9204
9205     if (oldGameMode == AnalyzeFile ||
9206         oldGameMode == AnalyzeMode) {
9207       AnalyzeFileEvent();
9208     }
9209
9210     if (matchMode || appData.timeDelay == 0) {
9211       ToEndEvent();
9212       gameMode = EditGame;
9213       ModeHighlight();
9214     } else if (appData.timeDelay > 0) {
9215       AutoPlayGameLoop();
9216     }
9217
9218     if (appData.debugMode) 
9219         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9220
9221     loadFlag = 0; /* [HGM] true game starts */
9222     return TRUE;
9223 }
9224
9225 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9226 int
9227 ReloadPosition(offset)
9228      int offset;
9229 {
9230     int positionNumber = lastLoadPositionNumber + offset;
9231     if (lastLoadPositionFP == NULL) {
9232         DisplayError(_("No position has been loaded yet"), 0);
9233         return FALSE;
9234     }
9235     if (positionNumber <= 0) {
9236         DisplayError(_("Can't back up any further"), 0);
9237         return FALSE;
9238     }
9239     return LoadPosition(lastLoadPositionFP, positionNumber,
9240                         lastLoadPositionTitle);
9241 }
9242
9243 /* Load the nth position from the given file */
9244 int
9245 LoadPositionFromFile(filename, n, title)
9246      char *filename;
9247      int n;
9248      char *title;
9249 {
9250     FILE *f;
9251     char buf[MSG_SIZ];
9252
9253     if (strcmp(filename, "-") == 0) {
9254         return LoadPosition(stdin, n, "stdin");
9255     } else {
9256         f = fopen(filename, "rb");
9257         if (f == NULL) {
9258             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9259             DisplayError(buf, errno);
9260             return FALSE;
9261         } else {
9262             return LoadPosition(f, n, title);
9263         }
9264     }
9265 }
9266
9267 /* Load the nth position from the given open file, and close it */
9268 int
9269 LoadPosition(f, positionNumber, title)
9270      FILE *f;
9271      int positionNumber;
9272      char *title;
9273 {
9274     char *p, line[MSG_SIZ];
9275     Board initial_position;
9276     int i, j, fenMode, pn;
9277     
9278     if (gameMode == Training )
9279         SetTrainingModeOff();
9280
9281     if (gameMode != BeginningOfGame) {
9282         Reset(FALSE, TRUE);
9283     }
9284     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9285         fclose(lastLoadPositionFP);
9286     }
9287     if (positionNumber == 0) positionNumber = 1;
9288     lastLoadPositionFP = f;
9289     lastLoadPositionNumber = positionNumber;
9290     strcpy(lastLoadPositionTitle, title);
9291     if (first.pr == NoProc) {
9292       StartChessProgram(&first);
9293       InitChessProgram(&first, FALSE);
9294     }    
9295     pn = positionNumber;
9296     if (positionNumber < 0) {
9297         /* Negative position number means to seek to that byte offset */
9298         if (fseek(f, -positionNumber, 0) == -1) {
9299             DisplayError(_("Can't seek on position file"), 0);
9300             return FALSE;
9301         };
9302         pn = 1;
9303     } else {
9304         if (fseek(f, 0, 0) == -1) {
9305             if (f == lastLoadPositionFP ?
9306                 positionNumber == lastLoadPositionNumber + 1 :
9307                 positionNumber == 1) {
9308                 pn = 1;
9309             } else {
9310                 DisplayError(_("Can't seek on position file"), 0);
9311                 return FALSE;
9312             }
9313         }
9314     }
9315     /* See if this file is FEN or old-style xboard */
9316     if (fgets(line, MSG_SIZ, f) == NULL) {
9317         DisplayError(_("Position not found in file"), 0);
9318         return FALSE;
9319     }
9320     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9321     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9322
9323     if (pn >= 2) {
9324         if (fenMode || line[0] == '#') pn--;
9325         while (pn > 0) {
9326             /* skip positions before number pn */
9327             if (fgets(line, MSG_SIZ, f) == NULL) {
9328                 Reset(TRUE, TRUE);
9329                 DisplayError(_("Position not found in file"), 0);
9330                 return FALSE;
9331             }
9332             if (fenMode || line[0] == '#') pn--;
9333         }
9334     }
9335
9336     if (fenMode) {
9337         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9338             DisplayError(_("Bad FEN position in file"), 0);
9339             return FALSE;
9340         }
9341     } else {
9342         (void) fgets(line, MSG_SIZ, f);
9343         (void) fgets(line, MSG_SIZ, f);
9344     
9345         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9346             (void) fgets(line, MSG_SIZ, f);
9347             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9348                 if (*p == ' ')
9349                   continue;
9350                 initial_position[i][j++] = CharToPiece(*p);
9351             }
9352         }
9353     
9354         blackPlaysFirst = FALSE;
9355         if (!feof(f)) {
9356             (void) fgets(line, MSG_SIZ, f);
9357             if (strncmp(line, "black", strlen("black"))==0)
9358               blackPlaysFirst = TRUE;
9359         }
9360     }
9361     startedFromSetupPosition = TRUE;
9362     
9363     SendToProgram("force\n", &first);
9364     CopyBoard(boards[0], initial_position);
9365     if (blackPlaysFirst) {
9366         currentMove = forwardMostMove = backwardMostMove = 1;
9367         strcpy(moveList[0], "");
9368         strcpy(parseList[0], "");
9369         CopyBoard(boards[1], initial_position);
9370         DisplayMessage("", _("Black to play"));
9371     } else {
9372         currentMove = forwardMostMove = backwardMostMove = 0;
9373         DisplayMessage("", _("White to play"));
9374     }
9375           /* [HGM] copy FEN attributes as well */
9376           {   int i;
9377               initialRulePlies = FENrulePlies;
9378               epStatus[forwardMostMove] = FENepStatus;
9379               for( i=0; i< nrCastlingRights; i++ )
9380                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9381           }
9382     SendBoard(&first, forwardMostMove);
9383     if (appData.debugMode) {
9384 int i, j;
9385   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9386   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9387         fprintf(debugFP, "Load Position\n");
9388     }
9389
9390     if (positionNumber > 1) {
9391         sprintf(line, "%s %d", title, positionNumber);
9392         DisplayTitle(line);
9393     } else {
9394         DisplayTitle(title);
9395     }
9396     gameMode = EditGame;
9397     ModeHighlight();
9398     ResetClocks();
9399     timeRemaining[0][1] = whiteTimeRemaining;
9400     timeRemaining[1][1] = blackTimeRemaining;
9401     DrawPosition(FALSE, boards[currentMove]);
9402    
9403     return TRUE;
9404 }
9405
9406
9407 void
9408 CopyPlayerNameIntoFileName(dest, src)
9409      char **dest, *src;
9410 {
9411     while (*src != NULLCHAR && *src != ',') {
9412         if (*src == ' ') {
9413             *(*dest)++ = '_';
9414             src++;
9415         } else {
9416             *(*dest)++ = *src++;
9417         }
9418     }
9419 }
9420
9421 char *DefaultFileName(ext)
9422      char *ext;
9423 {
9424     static char def[MSG_SIZ];
9425     char *p;
9426
9427     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9428         p = def;
9429         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9430         *p++ = '-';
9431         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9432         *p++ = '.';
9433         strcpy(p, ext);
9434     } else {
9435         def[0] = NULLCHAR;
9436     }
9437     return def;
9438 }
9439
9440 /* Save the current game to the given file */
9441 int
9442 SaveGameToFile(filename, append)
9443      char *filename;
9444      int append;
9445 {
9446     FILE *f;
9447     char buf[MSG_SIZ];
9448
9449     if (strcmp(filename, "-") == 0) {
9450         return SaveGame(stdout, 0, NULL);
9451     } else {
9452         f = fopen(filename, append ? "a" : "w");
9453         if (f == NULL) {
9454             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9455             DisplayError(buf, errno);
9456             return FALSE;
9457         } else {
9458             return SaveGame(f, 0, NULL);
9459         }
9460     }
9461 }
9462
9463 char *
9464 SavePart(str)
9465      char *str;
9466 {
9467     static char buf[MSG_SIZ];
9468     char *p;
9469     
9470     p = strchr(str, ' ');
9471     if (p == NULL) return str;
9472     strncpy(buf, str, p - str);
9473     buf[p - str] = NULLCHAR;
9474     return buf;
9475 }
9476
9477 #define PGN_MAX_LINE 75
9478
9479 #define PGN_SIDE_WHITE  0
9480 #define PGN_SIDE_BLACK  1
9481
9482 /* [AS] */
9483 static int FindFirstMoveOutOfBook( int side )
9484 {
9485     int result = -1;
9486
9487     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9488         int index = backwardMostMove;
9489         int has_book_hit = 0;
9490
9491         if( (index % 2) != side ) {
9492             index++;
9493         }
9494
9495         while( index < forwardMostMove ) {
9496             /* Check to see if engine is in book */
9497             int depth = pvInfoList[index].depth;
9498             int score = pvInfoList[index].score;
9499             int in_book = 0;
9500
9501             if( depth <= 2 ) {
9502                 in_book = 1;
9503             }
9504             else if( score == 0 && depth == 63 ) {
9505                 in_book = 1; /* Zappa */
9506             }
9507             else if( score == 2 && depth == 99 ) {
9508                 in_book = 1; /* Abrok */
9509             }
9510
9511             has_book_hit += in_book;
9512
9513             if( ! in_book ) {
9514                 result = index;
9515
9516                 break;
9517             }
9518
9519             index += 2;
9520         }
9521     }
9522
9523     return result;
9524 }
9525
9526 /* [AS] */
9527 void GetOutOfBookInfo( char * buf )
9528 {
9529     int oob[2];
9530     int i;
9531     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9532
9533     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9534     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9535
9536     *buf = '\0';
9537
9538     if( oob[0] >= 0 || oob[1] >= 0 ) {
9539         for( i=0; i<2; i++ ) {
9540             int idx = oob[i];
9541
9542             if( idx >= 0 ) {
9543                 if( i > 0 && oob[0] >= 0 ) {
9544                     strcat( buf, "   " );
9545                 }
9546
9547                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9548                 sprintf( buf+strlen(buf), "%s%.2f", 
9549                     pvInfoList[idx].score >= 0 ? "+" : "",
9550                     pvInfoList[idx].score / 100.0 );
9551             }
9552         }
9553     }
9554 }
9555
9556 /* Save game in PGN style and close the file */
9557 int
9558 SaveGamePGN(f)
9559      FILE *f;
9560 {
9561     int i, offset, linelen, newblock;
9562     time_t tm;
9563 //    char *movetext;
9564     char numtext[32];
9565     int movelen, numlen, blank;
9566     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9567
9568     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9569     
9570     tm = time((time_t *) NULL);
9571     
9572     PrintPGNTags(f, &gameInfo);
9573     
9574     if (backwardMostMove > 0 || startedFromSetupPosition) {
9575         char *fen = PositionToFEN(backwardMostMove, NULL);
9576         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9577         fprintf(f, "\n{--------------\n");
9578         PrintPosition(f, backwardMostMove);
9579         fprintf(f, "--------------}\n");
9580         free(fen);
9581     }
9582     else {
9583         /* [AS] Out of book annotation */
9584         if( appData.saveOutOfBookInfo ) {
9585             char buf[64];
9586
9587             GetOutOfBookInfo( buf );
9588
9589             if( buf[0] != '\0' ) {
9590                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9591             }
9592         }
9593
9594         fprintf(f, "\n");
9595     }
9596
9597     i = backwardMostMove;
9598     linelen = 0;
9599     newblock = TRUE;
9600
9601     while (i < forwardMostMove) {
9602         /* Print comments preceding this move */
9603         if (commentList[i] != NULL) {
9604             if (linelen > 0) fprintf(f, "\n");
9605             fprintf(f, "{\n%s}\n", commentList[i]);
9606             linelen = 0;
9607             newblock = TRUE;
9608         }
9609
9610         /* Format move number */
9611         if ((i % 2) == 0) {
9612             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9613         } else {
9614             if (newblock) {
9615                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9616             } else {
9617                 numtext[0] = NULLCHAR;
9618             }
9619         }
9620         numlen = strlen(numtext);
9621         newblock = FALSE;
9622
9623         /* Print move number */
9624         blank = linelen > 0 && numlen > 0;
9625         if (linelen + (blank ? 1 : 0) + numlen > 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, "%s", numtext);
9635         linelen += numlen;
9636
9637         /* Get move */
9638         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9639         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9640
9641         /* Print move */
9642         blank = linelen > 0 && movelen > 0;
9643         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9644             fprintf(f, "\n");
9645             linelen = 0;
9646             blank = 0;
9647         }
9648         if (blank) {
9649             fprintf(f, " ");
9650             linelen++;
9651         }
9652         fprintf(f, "%s", move_buffer);
9653         linelen += movelen;
9654
9655         /* [AS] Add PV info if present */
9656         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9657             /* [HGM] add time */
9658             char buf[MSG_SIZ]; int seconds = 0;
9659
9660             if(i >= backwardMostMove) {
9661                 if(WhiteOnMove(i))
9662                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9663                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9664                 else
9665                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9666                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9667             }
9668             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9669
9670             if( seconds <= 0) buf[0] = 0; else
9671             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9672                 seconds = (seconds + 4)/10; // round to full seconds
9673                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9674                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9675             }
9676
9677             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9678                 pvInfoList[i].score >= 0 ? "+" : "",
9679                 pvInfoList[i].score / 100.0,
9680                 pvInfoList[i].depth,
9681                 buf );
9682
9683             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9684
9685             /* Print score/depth */
9686             blank = linelen > 0 && movelen > 0;
9687             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9688                 fprintf(f, "\n");
9689                 linelen = 0;
9690                 blank = 0;
9691             }
9692             if (blank) {
9693                 fprintf(f, " ");
9694                 linelen++;
9695             }
9696             fprintf(f, "%s", move_buffer);
9697             linelen += movelen;
9698         }
9699
9700         i++;
9701     }
9702     
9703     /* Start a new line */
9704     if (linelen > 0) fprintf(f, "\n");
9705
9706     /* Print comments after last move */
9707     if (commentList[i] != NULL) {
9708         fprintf(f, "{\n%s}\n", commentList[i]);
9709     }
9710
9711     /* Print result */
9712     if (gameInfo.resultDetails != NULL &&
9713         gameInfo.resultDetails[0] != NULLCHAR) {
9714         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9715                 PGNResult(gameInfo.result));
9716     } else {
9717         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9718     }
9719
9720     fclose(f);
9721     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9722     return TRUE;
9723 }
9724
9725 /* Save game in old style and close the file */
9726 int
9727 SaveGameOldStyle(f)
9728      FILE *f;
9729 {
9730     int i, offset;
9731     time_t tm;
9732     
9733     tm = time((time_t *) NULL);
9734     
9735     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9736     PrintOpponents(f);
9737     
9738     if (backwardMostMove > 0 || startedFromSetupPosition) {
9739         fprintf(f, "\n[--------------\n");
9740         PrintPosition(f, backwardMostMove);
9741         fprintf(f, "--------------]\n");
9742     } else {
9743         fprintf(f, "\n");
9744     }
9745
9746     i = backwardMostMove;
9747     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9748
9749     while (i < forwardMostMove) {
9750         if (commentList[i] != NULL) {
9751             fprintf(f, "[%s]\n", commentList[i]);
9752         }
9753
9754         if ((i % 2) == 1) {
9755             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9756             i++;
9757         } else {
9758             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9759             i++;
9760             if (commentList[i] != NULL) {
9761                 fprintf(f, "\n");
9762                 continue;
9763             }
9764             if (i >= forwardMostMove) {
9765                 fprintf(f, "\n");
9766                 break;
9767             }
9768             fprintf(f, "%s\n", parseList[i]);
9769             i++;
9770         }
9771     }
9772     
9773     if (commentList[i] != NULL) {
9774         fprintf(f, "[%s]\n", commentList[i]);
9775     }
9776
9777     /* This isn't really the old style, but it's close enough */
9778     if (gameInfo.resultDetails != NULL &&
9779         gameInfo.resultDetails[0] != NULLCHAR) {
9780         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9781                 gameInfo.resultDetails);
9782     } else {
9783         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9784     }
9785
9786     fclose(f);
9787     return TRUE;
9788 }
9789
9790 /* Save the current game to open file f and close the file */
9791 int
9792 SaveGame(f, dummy, dummy2)
9793      FILE *f;
9794      int dummy;
9795      char *dummy2;
9796 {
9797     if (gameMode == EditPosition) EditPositionDone();
9798     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9799     if (appData.oldSaveStyle)
9800       return SaveGameOldStyle(f);
9801     else
9802       return SaveGamePGN(f);
9803 }
9804
9805 /* Save the current position to the given file */
9806 int
9807 SavePositionToFile(filename)
9808      char *filename;
9809 {
9810     FILE *f;
9811     char buf[MSG_SIZ];
9812
9813     if (strcmp(filename, "-") == 0) {
9814         return SavePosition(stdout, 0, NULL);
9815     } else {
9816         f = fopen(filename, "a");
9817         if (f == NULL) {
9818             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9819             DisplayError(buf, errno);
9820             return FALSE;
9821         } else {
9822             SavePosition(f, 0, NULL);
9823             return TRUE;
9824         }
9825     }
9826 }
9827
9828 /* Save the current position to the given open file and close the file */
9829 int
9830 SavePosition(f, dummy, dummy2)
9831      FILE *f;
9832      int dummy;
9833      char *dummy2;
9834 {
9835     time_t tm;
9836     char *fen;
9837     
9838     if (appData.oldSaveStyle) {
9839         tm = time((time_t *) NULL);
9840     
9841         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9842         PrintOpponents(f);
9843         fprintf(f, "[--------------\n");
9844         PrintPosition(f, currentMove);
9845         fprintf(f, "--------------]\n");
9846     } else {
9847         fen = PositionToFEN(currentMove, NULL);
9848         fprintf(f, "%s\n", fen);
9849         free(fen);
9850     }
9851     fclose(f);
9852     return TRUE;
9853 }
9854
9855 void
9856 ReloadCmailMsgEvent(unregister)
9857      int unregister;
9858 {
9859 #if !WIN32
9860     static char *inFilename = NULL;
9861     static char *outFilename;
9862     int i;
9863     struct stat inbuf, outbuf;
9864     int status;
9865     
9866     /* Any registered moves are unregistered if unregister is set, */
9867     /* i.e. invoked by the signal handler */
9868     if (unregister) {
9869         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9870             cmailMoveRegistered[i] = FALSE;
9871             if (cmailCommentList[i] != NULL) {
9872                 free(cmailCommentList[i]);
9873                 cmailCommentList[i] = NULL;
9874             }
9875         }
9876         nCmailMovesRegistered = 0;
9877     }
9878
9879     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9880         cmailResult[i] = CMAIL_NOT_RESULT;
9881     }
9882     nCmailResults = 0;
9883
9884     if (inFilename == NULL) {
9885         /* Because the filenames are static they only get malloced once  */
9886         /* and they never get freed                                      */
9887         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9888         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9889
9890         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9891         sprintf(outFilename, "%s.out", appData.cmailGameName);
9892     }
9893     
9894     status = stat(outFilename, &outbuf);
9895     if (status < 0) {
9896         cmailMailedMove = FALSE;
9897     } else {
9898         status = stat(inFilename, &inbuf);
9899         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9900     }
9901     
9902     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9903        counts the games, notes how each one terminated, etc.
9904        
9905        It would be nice to remove this kludge and instead gather all
9906        the information while building the game list.  (And to keep it
9907        in the game list nodes instead of having a bunch of fixed-size
9908        parallel arrays.)  Note this will require getting each game's
9909        termination from the PGN tags, as the game list builder does
9910        not process the game moves.  --mann
9911        */
9912     cmailMsgLoaded = TRUE;
9913     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9914     
9915     /* Load first game in the file or popup game menu */
9916     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9917
9918 #endif /* !WIN32 */
9919     return;
9920 }
9921
9922 int
9923 RegisterMove()
9924 {
9925     FILE *f;
9926     char string[MSG_SIZ];
9927
9928     if (   cmailMailedMove
9929         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9930         return TRUE;            /* Allow free viewing  */
9931     }
9932
9933     /* Unregister move to ensure that we don't leave RegisterMove        */
9934     /* with the move registered when the conditions for registering no   */
9935     /* longer hold                                                       */
9936     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9937         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9938         nCmailMovesRegistered --;
9939
9940         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9941           {
9942               free(cmailCommentList[lastLoadGameNumber - 1]);
9943               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9944           }
9945     }
9946
9947     if (cmailOldMove == -1) {
9948         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9949         return FALSE;
9950     }
9951
9952     if (currentMove > cmailOldMove + 1) {
9953         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9954         return FALSE;
9955     }
9956
9957     if (currentMove < cmailOldMove) {
9958         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9959         return FALSE;
9960     }
9961
9962     if (forwardMostMove > currentMove) {
9963         /* Silently truncate extra moves */
9964         TruncateGame();
9965     }
9966
9967     if (   (currentMove == cmailOldMove + 1)
9968         || (   (currentMove == cmailOldMove)
9969             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9970                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9971         if (gameInfo.result != GameUnfinished) {
9972             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9973         }
9974
9975         if (commentList[currentMove] != NULL) {
9976             cmailCommentList[lastLoadGameNumber - 1]
9977               = StrSave(commentList[currentMove]);
9978         }
9979         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9980
9981         if (appData.debugMode)
9982           fprintf(debugFP, "Saving %s for game %d\n",
9983                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9984
9985         sprintf(string,
9986                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9987         
9988         f = fopen(string, "w");
9989         if (appData.oldSaveStyle) {
9990             SaveGameOldStyle(f); /* also closes the file */
9991             
9992             sprintf(string, "%s.pos.out", appData.cmailGameName);
9993             f = fopen(string, "w");
9994             SavePosition(f, 0, NULL); /* also closes the file */
9995         } else {
9996             fprintf(f, "{--------------\n");
9997             PrintPosition(f, currentMove);
9998             fprintf(f, "--------------}\n\n");
9999             
10000             SaveGame(f, 0, NULL); /* also closes the file*/
10001         }
10002         
10003         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10004         nCmailMovesRegistered ++;
10005     } else if (nCmailGames == 1) {
10006         DisplayError(_("You have not made a move yet"), 0);
10007         return FALSE;
10008     }
10009
10010     return TRUE;
10011 }
10012
10013 void
10014 MailMoveEvent()
10015 {
10016 #if !WIN32
10017     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10018     FILE *commandOutput;
10019     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10020     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10021     int nBuffers;
10022     int i;
10023     int archived;
10024     char *arcDir;
10025
10026     if (! cmailMsgLoaded) {
10027         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10028         return;
10029     }
10030
10031     if (nCmailGames == nCmailResults) {
10032         DisplayError(_("No unfinished games"), 0);
10033         return;
10034     }
10035
10036 #if CMAIL_PROHIBIT_REMAIL
10037     if (cmailMailedMove) {
10038         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);
10039         DisplayError(msg, 0);
10040         return;
10041     }
10042 #endif
10043
10044     if (! (cmailMailedMove || RegisterMove())) return;
10045     
10046     if (   cmailMailedMove
10047         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10048         sprintf(string, partCommandString,
10049                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10050         commandOutput = popen(string, "r");
10051
10052         if (commandOutput == NULL) {
10053             DisplayError(_("Failed to invoke cmail"), 0);
10054         } else {
10055             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10056                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10057             }
10058             if (nBuffers > 1) {
10059                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10060                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10061                 nBytes = MSG_SIZ - 1;
10062             } else {
10063                 (void) memcpy(msg, buffer, nBytes);
10064             }
10065             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10066
10067             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10068                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10069
10070                 archived = TRUE;
10071                 for (i = 0; i < nCmailGames; i ++) {
10072                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10073                         archived = FALSE;
10074                     }
10075                 }
10076                 if (   archived
10077                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10078                         != NULL)) {
10079                     sprintf(buffer, "%s/%s.%s.archive",
10080                             arcDir,
10081                             appData.cmailGameName,
10082                             gameInfo.date);
10083                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10084                     cmailMsgLoaded = FALSE;
10085                 }
10086             }
10087
10088             DisplayInformation(msg);
10089             pclose(commandOutput);
10090         }
10091     } else {
10092         if ((*cmailMsg) != '\0') {
10093             DisplayInformation(cmailMsg);
10094         }
10095     }
10096
10097     return;
10098 #endif /* !WIN32 */
10099 }
10100
10101 char *
10102 CmailMsg()
10103 {
10104 #if WIN32
10105     return NULL;
10106 #else
10107     int  prependComma = 0;
10108     char number[5];
10109     char string[MSG_SIZ];       /* Space for game-list */
10110     int  i;
10111     
10112     if (!cmailMsgLoaded) return "";
10113
10114     if (cmailMailedMove) {
10115         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10116     } else {
10117         /* Create a list of games left */
10118         sprintf(string, "[");
10119         for (i = 0; i < nCmailGames; i ++) {
10120             if (! (   cmailMoveRegistered[i]
10121                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10122                 if (prependComma) {
10123                     sprintf(number, ",%d", i + 1);
10124                 } else {
10125                     sprintf(number, "%d", i + 1);
10126                     prependComma = 1;
10127                 }
10128                 
10129                 strcat(string, number);
10130             }
10131         }
10132         strcat(string, "]");
10133
10134         if (nCmailMovesRegistered + nCmailResults == 0) {
10135             switch (nCmailGames) {
10136               case 1:
10137                 sprintf(cmailMsg,
10138                         _("Still need to make move for game\n"));
10139                 break;
10140                 
10141               case 2:
10142                 sprintf(cmailMsg,
10143                         _("Still need to make moves for both games\n"));
10144                 break;
10145                 
10146               default:
10147                 sprintf(cmailMsg,
10148                         _("Still need to make moves for all %d games\n"),
10149                         nCmailGames);
10150                 break;
10151             }
10152         } else {
10153             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10154               case 1:
10155                 sprintf(cmailMsg,
10156                         _("Still need to make a move for game %s\n"),
10157                         string);
10158                 break;
10159                 
10160               case 0:
10161                 if (nCmailResults == nCmailGames) {
10162                     sprintf(cmailMsg, _("No unfinished games\n"));
10163                 } else {
10164                     sprintf(cmailMsg, _("Ready to send mail\n"));
10165                 }
10166                 break;
10167                 
10168               default:
10169                 sprintf(cmailMsg,
10170                         _("Still need to make moves for games %s\n"),
10171                         string);
10172             }
10173         }
10174     }
10175     return cmailMsg;
10176 #endif /* WIN32 */
10177 }
10178
10179 void
10180 ResetGameEvent()
10181 {
10182     if (gameMode == Training)
10183       SetTrainingModeOff();
10184
10185     Reset(TRUE, TRUE);
10186     cmailMsgLoaded = FALSE;
10187     if (appData.icsActive) {
10188       SendToICS(ics_prefix);
10189       SendToICS("refresh\n");
10190     }
10191 }
10192
10193 void
10194 ExitEvent(status)
10195      int status;
10196 {
10197     exiting++;
10198     if (exiting > 2) {
10199       /* Give up on clean exit */
10200       exit(status);
10201     }
10202     if (exiting > 1) {
10203       /* Keep trying for clean exit */
10204       return;
10205     }
10206
10207     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10208
10209     if (telnetISR != NULL) {
10210       RemoveInputSource(telnetISR);
10211     }
10212     if (icsPR != NoProc) {
10213       DestroyChildProcess(icsPR, TRUE);
10214     }
10215
10216     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10217     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10218
10219     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10220     /* make sure this other one finishes before killing it!                  */
10221     if(endingGame) { int count = 0;
10222         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10223         while(endingGame && count++ < 10) DoSleep(1);
10224         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10225     }
10226
10227     /* Kill off chess programs */
10228     if (first.pr != NoProc) {
10229         ExitAnalyzeMode();
10230         
10231         DoSleep( appData.delayBeforeQuit );
10232         SendToProgram("quit\n", &first);
10233         DoSleep( appData.delayAfterQuit );
10234         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10235     }
10236     if (second.pr != NoProc) {
10237         DoSleep( appData.delayBeforeQuit );
10238         SendToProgram("quit\n", &second);
10239         DoSleep( appData.delayAfterQuit );
10240         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10241     }
10242     if (first.isr != NULL) {
10243         RemoveInputSource(first.isr);
10244     }
10245     if (second.isr != NULL) {
10246         RemoveInputSource(second.isr);
10247     }
10248
10249     ShutDownFrontEnd();
10250     exit(status);
10251 }
10252
10253 void
10254 PauseEvent()
10255 {
10256     if (appData.debugMode)
10257         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10258     if (pausing) {
10259         pausing = FALSE;
10260         ModeHighlight();
10261         if (gameMode == MachinePlaysWhite ||
10262             gameMode == MachinePlaysBlack) {
10263             StartClocks();
10264         } else {
10265             DisplayBothClocks();
10266         }
10267         if (gameMode == PlayFromGameFile) {
10268             if (appData.timeDelay >= 0) 
10269                 AutoPlayGameLoop();
10270         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10271             Reset(FALSE, TRUE);
10272             SendToICS(ics_prefix);
10273             SendToICS("refresh\n");
10274         } else if (currentMove < forwardMostMove) {
10275             ForwardInner(forwardMostMove);
10276         }
10277         pauseExamInvalid = FALSE;
10278     } else {
10279         switch (gameMode) {
10280           default:
10281             return;
10282           case IcsExamining:
10283             pauseExamForwardMostMove = forwardMostMove;
10284             pauseExamInvalid = FALSE;
10285             /* fall through */
10286           case IcsObserving:
10287           case IcsPlayingWhite:
10288           case IcsPlayingBlack:
10289             pausing = TRUE;
10290             ModeHighlight();
10291             return;
10292           case PlayFromGameFile:
10293             (void) StopLoadGameTimer();
10294             pausing = TRUE;
10295             ModeHighlight();
10296             break;
10297           case BeginningOfGame:
10298             if (appData.icsActive) return;
10299             /* else fall through */
10300           case MachinePlaysWhite:
10301           case MachinePlaysBlack:
10302           case TwoMachinesPlay:
10303             if (forwardMostMove == 0)
10304               return;           /* don't pause if no one has moved */
10305             if ((gameMode == MachinePlaysWhite &&
10306                  !WhiteOnMove(forwardMostMove)) ||
10307                 (gameMode == MachinePlaysBlack &&
10308                  WhiteOnMove(forwardMostMove))) {
10309                 StopClocks();
10310             }
10311             pausing = TRUE;
10312             ModeHighlight();
10313             break;
10314         }
10315     }
10316 }
10317
10318 void
10319 EditCommentEvent()
10320 {
10321     char title[MSG_SIZ];
10322
10323     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10324         strcpy(title, _("Edit comment"));
10325     } else {
10326         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10327                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10328                 parseList[currentMove - 1]);
10329     }
10330
10331     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10332 }
10333
10334
10335 void
10336 EditTagsEvent()
10337 {
10338     char *tags = PGNTags(&gameInfo);
10339     EditTagsPopUp(tags);
10340     free(tags);
10341 }
10342
10343 void
10344 AnalyzeModeEvent()
10345 {
10346     if (appData.noChessProgram || gameMode == AnalyzeMode)
10347       return;
10348
10349     if (gameMode != AnalyzeFile) {
10350         if (!appData.icsEngineAnalyze) {
10351                EditGameEvent();
10352                if (gameMode != EditGame) return;
10353         }
10354         ResurrectChessProgram();
10355         SendToProgram("analyze\n", &first);
10356         first.analyzing = TRUE;
10357         /*first.maybeThinking = TRUE;*/
10358         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10359         EngineOutputPopUp();
10360     }
10361     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10362     pausing = FALSE;
10363     ModeHighlight();
10364     SetGameInfo();
10365
10366     StartAnalysisClock();
10367     GetTimeMark(&lastNodeCountTime);
10368     lastNodeCount = 0;
10369 }
10370
10371 void
10372 AnalyzeFileEvent()
10373 {
10374     if (appData.noChessProgram || gameMode == AnalyzeFile)
10375       return;
10376
10377     if (gameMode != AnalyzeMode) {
10378         EditGameEvent();
10379         if (gameMode != EditGame) return;
10380         ResurrectChessProgram();
10381         SendToProgram("analyze\n", &first);
10382         first.analyzing = TRUE;
10383         /*first.maybeThinking = TRUE;*/
10384         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10385         EngineOutputPopUp();
10386     }
10387     gameMode = AnalyzeFile;
10388     pausing = FALSE;
10389     ModeHighlight();
10390     SetGameInfo();
10391
10392     StartAnalysisClock();
10393     GetTimeMark(&lastNodeCountTime);
10394     lastNodeCount = 0;
10395 }
10396
10397 void
10398 MachineWhiteEvent()
10399 {
10400     char buf[MSG_SIZ];
10401     char *bookHit = NULL;
10402
10403     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10404       return;
10405
10406
10407     if (gameMode == PlayFromGameFile || 
10408         gameMode == TwoMachinesPlay  || 
10409         gameMode == Training         || 
10410         gameMode == AnalyzeMode      || 
10411         gameMode == EndOfGame)
10412         EditGameEvent();
10413
10414     if (gameMode == EditPosition) 
10415         EditPositionDone();
10416
10417     if (!WhiteOnMove(currentMove)) {
10418         DisplayError(_("It is not White's turn"), 0);
10419         return;
10420     }
10421   
10422     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10423       ExitAnalyzeMode();
10424
10425     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10426         gameMode == AnalyzeFile)
10427         TruncateGame();
10428
10429     ResurrectChessProgram();    /* in case it isn't running */
10430     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10431         gameMode = MachinePlaysWhite;
10432         ResetClocks();
10433     } else
10434     gameMode = MachinePlaysWhite;
10435     pausing = FALSE;
10436     ModeHighlight();
10437     SetGameInfo();
10438     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10439     DisplayTitle(buf);
10440     if (first.sendName) {
10441       sprintf(buf, "name %s\n", gameInfo.black);
10442       SendToProgram(buf, &first);
10443     }
10444     if (first.sendTime) {
10445       if (first.useColors) {
10446         SendToProgram("black\n", &first); /*gnu kludge*/
10447       }
10448       SendTimeRemaining(&first, TRUE);
10449     }
10450     if (first.useColors) {
10451       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10452     }
10453     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10454     SetMachineThinkingEnables();
10455     first.maybeThinking = TRUE;
10456     StartClocks();
10457     firstMove = FALSE;
10458
10459     if (appData.autoFlipView && !flipView) {
10460       flipView = !flipView;
10461       DrawPosition(FALSE, NULL);
10462       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10463     }
10464
10465     if(bookHit) { // [HGM] book: simulate book reply
10466         static char bookMove[MSG_SIZ]; // a bit generous?
10467
10468         programStats.nodes = programStats.depth = programStats.time = 
10469         programStats.score = programStats.got_only_move = 0;
10470         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10471
10472         strcpy(bookMove, "move ");
10473         strcat(bookMove, bookHit);
10474         HandleMachineMove(bookMove, &first);
10475     }
10476 }
10477
10478 void
10479 MachineBlackEvent()
10480 {
10481     char buf[MSG_SIZ];
10482    char *bookHit = NULL;
10483
10484     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10485         return;
10486
10487
10488     if (gameMode == PlayFromGameFile || 
10489         gameMode == TwoMachinesPlay  || 
10490         gameMode == Training         || 
10491         gameMode == AnalyzeMode      || 
10492         gameMode == EndOfGame)
10493         EditGameEvent();
10494
10495     if (gameMode == EditPosition) 
10496         EditPositionDone();
10497
10498     if (WhiteOnMove(currentMove)) {
10499         DisplayError(_("It is not Black's turn"), 0);
10500         return;
10501     }
10502     
10503     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10504       ExitAnalyzeMode();
10505
10506     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10507         gameMode == AnalyzeFile)
10508         TruncateGame();
10509
10510     ResurrectChessProgram();    /* in case it isn't running */
10511     gameMode = MachinePlaysBlack;
10512     pausing = FALSE;
10513     ModeHighlight();
10514     SetGameInfo();
10515     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10516     DisplayTitle(buf);
10517     if (first.sendName) {
10518       sprintf(buf, "name %s\n", gameInfo.white);
10519       SendToProgram(buf, &first);
10520     }
10521     if (first.sendTime) {
10522       if (first.useColors) {
10523         SendToProgram("white\n", &first); /*gnu kludge*/
10524       }
10525       SendTimeRemaining(&first, FALSE);
10526     }
10527     if (first.useColors) {
10528       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10529     }
10530     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10531     SetMachineThinkingEnables();
10532     first.maybeThinking = TRUE;
10533     StartClocks();
10534
10535     if (appData.autoFlipView && flipView) {
10536       flipView = !flipView;
10537       DrawPosition(FALSE, NULL);
10538       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10539     }
10540     if(bookHit) { // [HGM] book: simulate book reply
10541         static char bookMove[MSG_SIZ]; // a bit generous?
10542
10543         programStats.nodes = programStats.depth = programStats.time = 
10544         programStats.score = programStats.got_only_move = 0;
10545         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10546
10547         strcpy(bookMove, "move ");
10548         strcat(bookMove, bookHit);
10549         HandleMachineMove(bookMove, &first);
10550     }
10551 }
10552
10553
10554 void
10555 DisplayTwoMachinesTitle()
10556 {
10557     char buf[MSG_SIZ];
10558     if (appData.matchGames > 0) {
10559         if (first.twoMachinesColor[0] == 'w') {
10560             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10561                     gameInfo.white, gameInfo.black,
10562                     first.matchWins, second.matchWins,
10563                     matchGame - 1 - (first.matchWins + second.matchWins));
10564         } else {
10565             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10566                     gameInfo.white, gameInfo.black,
10567                     second.matchWins, first.matchWins,
10568                     matchGame - 1 - (first.matchWins + second.matchWins));
10569         }
10570     } else {
10571         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10572     }
10573     DisplayTitle(buf);
10574 }
10575
10576 void
10577 TwoMachinesEvent P((void))
10578 {
10579     int i;
10580     char buf[MSG_SIZ];
10581     ChessProgramState *onmove;
10582     char *bookHit = NULL;
10583     
10584     if (appData.noChessProgram) return;
10585
10586     switch (gameMode) {
10587       case TwoMachinesPlay:
10588         return;
10589       case MachinePlaysWhite:
10590       case MachinePlaysBlack:
10591         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10592             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10593             return;
10594         }
10595         /* fall through */
10596       case BeginningOfGame:
10597       case PlayFromGameFile:
10598       case EndOfGame:
10599         EditGameEvent();
10600         if (gameMode != EditGame) return;
10601         break;
10602       case EditPosition:
10603         EditPositionDone();
10604         break;
10605       case AnalyzeMode:
10606       case AnalyzeFile:
10607         ExitAnalyzeMode();
10608         break;
10609       case EditGame:
10610       default:
10611         break;
10612     }
10613
10614     forwardMostMove = currentMove;
10615     ResurrectChessProgram();    /* in case first program isn't running */
10616
10617     if (second.pr == NULL) {
10618         StartChessProgram(&second);
10619         if (second.protocolVersion == 1) {
10620           TwoMachinesEventIfReady();
10621         } else {
10622           /* kludge: allow timeout for initial "feature" command */
10623           FreezeUI();
10624           DisplayMessage("", _("Starting second chess program"));
10625           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10626         }
10627         return;
10628     }
10629     DisplayMessage("", "");
10630     InitChessProgram(&second, FALSE);
10631     SendToProgram("force\n", &second);
10632     if (startedFromSetupPosition) {
10633         SendBoard(&second, backwardMostMove);
10634     if (appData.debugMode) {
10635         fprintf(debugFP, "Two Machines\n");
10636     }
10637     }
10638     for (i = backwardMostMove; i < forwardMostMove; i++) {
10639         SendMoveToProgram(i, &second);
10640     }
10641
10642     gameMode = TwoMachinesPlay;
10643     pausing = FALSE;
10644     ModeHighlight();
10645     SetGameInfo();
10646     DisplayTwoMachinesTitle();
10647     firstMove = TRUE;
10648     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10649         onmove = &first;
10650     } else {
10651         onmove = &second;
10652     }
10653
10654     SendToProgram(first.computerString, &first);
10655     if (first.sendName) {
10656       sprintf(buf, "name %s\n", second.tidy);
10657       SendToProgram(buf, &first);
10658     }
10659     SendToProgram(second.computerString, &second);
10660     if (second.sendName) {
10661       sprintf(buf, "name %s\n", first.tidy);
10662       SendToProgram(buf, &second);
10663     }
10664
10665     ResetClocks();
10666     if (!first.sendTime || !second.sendTime) {
10667         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10668         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10669     }
10670     if (onmove->sendTime) {
10671       if (onmove->useColors) {
10672         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10673       }
10674       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10675     }
10676     if (onmove->useColors) {
10677       SendToProgram(onmove->twoMachinesColor, onmove);
10678     }
10679     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10680 //    SendToProgram("go\n", onmove);
10681     onmove->maybeThinking = TRUE;
10682     SetMachineThinkingEnables();
10683
10684     StartClocks();
10685
10686     if(bookHit) { // [HGM] book: simulate book reply
10687         static char bookMove[MSG_SIZ]; // a bit generous?
10688
10689         programStats.nodes = programStats.depth = programStats.time = 
10690         programStats.score = programStats.got_only_move = 0;
10691         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10692
10693         strcpy(bookMove, "move ");
10694         strcat(bookMove, bookHit);
10695         HandleMachineMove(bookMove, &first);
10696     }
10697 }
10698
10699 void
10700 TrainingEvent()
10701 {
10702     if (gameMode == Training) {
10703       SetTrainingModeOff();
10704       gameMode = PlayFromGameFile;
10705       DisplayMessage("", _("Training mode off"));
10706     } else {
10707       gameMode = Training;
10708       animateTraining = appData.animate;
10709
10710       /* make sure we are not already at the end of the game */
10711       if (currentMove < forwardMostMove) {
10712         SetTrainingModeOn();
10713         DisplayMessage("", _("Training mode on"));
10714       } else {
10715         gameMode = PlayFromGameFile;
10716         DisplayError(_("Already at end of game"), 0);
10717       }
10718     }
10719     ModeHighlight();
10720 }
10721
10722 void
10723 IcsClientEvent()
10724 {
10725     if (!appData.icsActive) return;
10726     switch (gameMode) {
10727       case IcsPlayingWhite:
10728       case IcsPlayingBlack:
10729       case IcsObserving:
10730       case IcsIdle:
10731       case BeginningOfGame:
10732       case IcsExamining:
10733         return;
10734
10735       case EditGame:
10736         break;
10737
10738       case EditPosition:
10739         EditPositionDone();
10740         break;
10741
10742       case AnalyzeMode:
10743       case AnalyzeFile:
10744         ExitAnalyzeMode();
10745         break;
10746         
10747       default:
10748         EditGameEvent();
10749         break;
10750     }
10751
10752     gameMode = IcsIdle;
10753     ModeHighlight();
10754     return;
10755 }
10756
10757
10758 void
10759 EditGameEvent()
10760 {
10761     int i;
10762
10763     switch (gameMode) {
10764       case Training:
10765         SetTrainingModeOff();
10766         break;
10767       case MachinePlaysWhite:
10768       case MachinePlaysBlack:
10769       case BeginningOfGame:
10770         SendToProgram("force\n", &first);
10771         SetUserThinkingEnables();
10772         break;
10773       case PlayFromGameFile:
10774         (void) StopLoadGameTimer();
10775         if (gameFileFP != NULL) {
10776             gameFileFP = NULL;
10777         }
10778         break;
10779       case EditPosition:
10780         EditPositionDone();
10781         break;
10782       case AnalyzeMode:
10783       case AnalyzeFile:
10784         ExitAnalyzeMode();
10785         SendToProgram("force\n", &first);
10786         break;
10787       case TwoMachinesPlay:
10788         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10789         ResurrectChessProgram();
10790         SetUserThinkingEnables();
10791         break;
10792       case EndOfGame:
10793         ResurrectChessProgram();
10794         break;
10795       case IcsPlayingBlack:
10796       case IcsPlayingWhite:
10797         DisplayError(_("Warning: You are still playing a game"), 0);
10798         break;
10799       case IcsObserving:
10800         DisplayError(_("Warning: You are still observing a game"), 0);
10801         break;
10802       case IcsExamining:
10803         DisplayError(_("Warning: You are still examining a game"), 0);
10804         break;
10805       case IcsIdle:
10806         break;
10807       case EditGame:
10808       default:
10809         return;
10810     }
10811     
10812     pausing = FALSE;
10813     StopClocks();
10814     first.offeredDraw = second.offeredDraw = 0;
10815
10816     if (gameMode == PlayFromGameFile) {
10817         whiteTimeRemaining = timeRemaining[0][currentMove];
10818         blackTimeRemaining = timeRemaining[1][currentMove];
10819         DisplayTitle("");
10820     }
10821
10822     if (gameMode == MachinePlaysWhite ||
10823         gameMode == MachinePlaysBlack ||
10824         gameMode == TwoMachinesPlay ||
10825         gameMode == EndOfGame) {
10826         i = forwardMostMove;
10827         while (i > currentMove) {
10828             SendToProgram("undo\n", &first);
10829             i--;
10830         }
10831         whiteTimeRemaining = timeRemaining[0][currentMove];
10832         blackTimeRemaining = timeRemaining[1][currentMove];
10833         DisplayBothClocks();
10834         if (whiteFlag || blackFlag) {
10835             whiteFlag = blackFlag = 0;
10836         }
10837         DisplayTitle("");
10838     }           
10839     
10840     gameMode = EditGame;
10841     ModeHighlight();
10842     SetGameInfo();
10843 }
10844
10845
10846 void
10847 EditPositionEvent()
10848 {
10849     if (gameMode == EditPosition) {
10850         EditGameEvent();
10851         return;
10852     }
10853     
10854     EditGameEvent();
10855     if (gameMode != EditGame) return;
10856     
10857     gameMode = EditPosition;
10858     ModeHighlight();
10859     SetGameInfo();
10860     if (currentMove > 0)
10861       CopyBoard(boards[0], boards[currentMove]);
10862     
10863     blackPlaysFirst = !WhiteOnMove(currentMove);
10864     ResetClocks();
10865     currentMove = forwardMostMove = backwardMostMove = 0;
10866     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10867     DisplayMove(-1);
10868 }
10869
10870 void
10871 ExitAnalyzeMode()
10872 {
10873     /* [DM] icsEngineAnalyze - possible call from other functions */
10874     if (appData.icsEngineAnalyze) {
10875         appData.icsEngineAnalyze = FALSE;
10876
10877         DisplayMessage("",_("Close ICS engine analyze..."));
10878     }
10879     if (first.analysisSupport && first.analyzing) {
10880       SendToProgram("exit\n", &first);
10881       first.analyzing = FALSE;
10882     }
10883     thinkOutput[0] = NULLCHAR;
10884 }
10885
10886 void
10887 EditPositionDone()
10888 {
10889     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10890
10891     startedFromSetupPosition = TRUE;
10892     InitChessProgram(&first, FALSE);
10893     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10894     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10895         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10896         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10897     } else castlingRights[0][2] = -1;
10898     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10899         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10900         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10901     } else castlingRights[0][5] = -1;
10902     SendToProgram("force\n", &first);
10903     if (blackPlaysFirst) {
10904         strcpy(moveList[0], "");
10905         strcpy(parseList[0], "");
10906         currentMove = forwardMostMove = backwardMostMove = 1;
10907         CopyBoard(boards[1], boards[0]);
10908         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10909         { int i;
10910           epStatus[1] = epStatus[0];
10911           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10912         }
10913     } else {
10914         currentMove = forwardMostMove = backwardMostMove = 0;
10915     }
10916     SendBoard(&first, forwardMostMove);
10917     if (appData.debugMode) {
10918         fprintf(debugFP, "EditPosDone\n");
10919     }
10920     DisplayTitle("");
10921     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10922     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10923     gameMode = EditGame;
10924     ModeHighlight();
10925     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10926     ClearHighlights(); /* [AS] */
10927 }
10928
10929 /* Pause for `ms' milliseconds */
10930 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10931 void
10932 TimeDelay(ms)
10933      long ms;
10934 {
10935     TimeMark m1, m2;
10936
10937     GetTimeMark(&m1);
10938     do {
10939         GetTimeMark(&m2);
10940     } while (SubtractTimeMarks(&m2, &m1) < ms);
10941 }
10942
10943 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10944 void
10945 SendMultiLineToICS(buf)
10946      char *buf;
10947 {
10948     char temp[MSG_SIZ+1], *p;
10949     int len;
10950
10951     len = strlen(buf);
10952     if (len > MSG_SIZ)
10953       len = MSG_SIZ;
10954   
10955     strncpy(temp, buf, len);
10956     temp[len] = 0;
10957
10958     p = temp;
10959     while (*p) {
10960         if (*p == '\n' || *p == '\r')
10961           *p = ' ';
10962         ++p;
10963     }
10964
10965     strcat(temp, "\n");
10966     SendToICS(temp);
10967     SendToPlayer(temp, strlen(temp));
10968 }
10969
10970 void
10971 SetWhiteToPlayEvent()
10972 {
10973     if (gameMode == EditPosition) {
10974         blackPlaysFirst = FALSE;
10975         DisplayBothClocks();    /* works because currentMove is 0 */
10976     } else if (gameMode == IcsExamining) {
10977         SendToICS(ics_prefix);
10978         SendToICS("tomove white\n");
10979     }
10980 }
10981
10982 void
10983 SetBlackToPlayEvent()
10984 {
10985     if (gameMode == EditPosition) {
10986         blackPlaysFirst = TRUE;
10987         currentMove = 1;        /* kludge */
10988         DisplayBothClocks();
10989         currentMove = 0;
10990     } else if (gameMode == IcsExamining) {
10991         SendToICS(ics_prefix);
10992         SendToICS("tomove black\n");
10993     }
10994 }
10995
10996 void
10997 EditPositionMenuEvent(selection, x, y)
10998      ChessSquare selection;
10999      int x, y;
11000 {
11001     char buf[MSG_SIZ];
11002     ChessSquare piece = boards[0][y][x];
11003
11004     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11005
11006     switch (selection) {
11007       case ClearBoard:
11008         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11009             SendToICS(ics_prefix);
11010             SendToICS("bsetup clear\n");
11011         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11012             SendToICS(ics_prefix);
11013             SendToICS("clearboard\n");
11014         } else {
11015             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11016                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11017                 for (y = 0; y < BOARD_HEIGHT; y++) {
11018                     if (gameMode == IcsExamining) {
11019                         if (boards[currentMove][y][x] != EmptySquare) {
11020                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11021                                     AAA + x, ONE + y);
11022                             SendToICS(buf);
11023                         }
11024                     } else {
11025                         boards[0][y][x] = p;
11026                     }
11027                 }
11028             }
11029         }
11030         if (gameMode == EditPosition) {
11031             DrawPosition(FALSE, boards[0]);
11032         }
11033         break;
11034
11035       case WhitePlay:
11036         SetWhiteToPlayEvent();
11037         break;
11038
11039       case BlackPlay:
11040         SetBlackToPlayEvent();
11041         break;
11042
11043       case EmptySquare:
11044         if (gameMode == IcsExamining) {
11045             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11046             SendToICS(buf);
11047         } else {
11048             boards[0][y][x] = EmptySquare;
11049             DrawPosition(FALSE, boards[0]);
11050         }
11051         break;
11052
11053       case PromotePiece:
11054         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11055            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11056             selection = (ChessSquare) (PROMOTED piece);
11057         } else if(piece == EmptySquare) selection = WhiteSilver;
11058         else selection = (ChessSquare)((int)piece - 1);
11059         goto defaultlabel;
11060
11061       case DemotePiece:
11062         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11063            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11064             selection = (ChessSquare) (DEMOTED piece);
11065         } else if(piece == EmptySquare) selection = BlackSilver;
11066         else selection = (ChessSquare)((int)piece + 1);       
11067         goto defaultlabel;
11068
11069       case WhiteQueen:
11070       case BlackQueen:
11071         if(gameInfo.variant == VariantShatranj ||
11072            gameInfo.variant == VariantXiangqi  ||
11073            gameInfo.variant == VariantCourier    )
11074             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11075         goto defaultlabel;
11076
11077       case WhiteKing:
11078       case BlackKing:
11079         if(gameInfo.variant == VariantXiangqi)
11080             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11081         if(gameInfo.variant == VariantKnightmate)
11082             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11083       default:
11084         defaultlabel:
11085         if (gameMode == IcsExamining) {
11086             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11087                     PieceToChar(selection), AAA + x, ONE + y);
11088             SendToICS(buf);
11089         } else {
11090             boards[0][y][x] = selection;
11091             DrawPosition(FALSE, boards[0]);
11092         }
11093         break;
11094     }
11095 }
11096
11097
11098 void
11099 DropMenuEvent(selection, x, y)
11100      ChessSquare selection;
11101      int x, y;
11102 {
11103     ChessMove moveType;
11104
11105     switch (gameMode) {
11106       case IcsPlayingWhite:
11107       case MachinePlaysBlack:
11108         if (!WhiteOnMove(currentMove)) {
11109             DisplayMoveError(_("It is Black's turn"));
11110             return;
11111         }
11112         moveType = WhiteDrop;
11113         break;
11114       case IcsPlayingBlack:
11115       case MachinePlaysWhite:
11116         if (WhiteOnMove(currentMove)) {
11117             DisplayMoveError(_("It is White's turn"));
11118             return;
11119         }
11120         moveType = BlackDrop;
11121         break;
11122       case EditGame:
11123         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11124         break;
11125       default:
11126         return;
11127     }
11128
11129     if (moveType == BlackDrop && selection < BlackPawn) {
11130       selection = (ChessSquare) ((int) selection
11131                                  + (int) BlackPawn - (int) WhitePawn);
11132     }
11133     if (boards[currentMove][y][x] != EmptySquare) {
11134         DisplayMoveError(_("That square is occupied"));
11135         return;
11136     }
11137
11138     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11139 }
11140
11141 void
11142 AcceptEvent()
11143 {
11144     /* Accept a pending offer of any kind from opponent */
11145     
11146     if (appData.icsActive) {
11147         SendToICS(ics_prefix);
11148         SendToICS("accept\n");
11149     } else if (cmailMsgLoaded) {
11150         if (currentMove == cmailOldMove &&
11151             commentList[cmailOldMove] != NULL &&
11152             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11153                    "Black offers a draw" : "White offers a draw")) {
11154             TruncateGame();
11155             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11156             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11157         } else {
11158             DisplayError(_("There is no pending offer on this move"), 0);
11159             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11160         }
11161     } else {
11162         /* Not used for offers from chess program */
11163     }
11164 }
11165
11166 void
11167 DeclineEvent()
11168 {
11169     /* Decline a pending offer of any kind from opponent */
11170     
11171     if (appData.icsActive) {
11172         SendToICS(ics_prefix);
11173         SendToICS("decline\n");
11174     } else if (cmailMsgLoaded) {
11175         if (currentMove == cmailOldMove &&
11176             commentList[cmailOldMove] != NULL &&
11177             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11178                    "Black offers a draw" : "White offers a draw")) {
11179 #ifdef NOTDEF
11180             AppendComment(cmailOldMove, "Draw declined");
11181             DisplayComment(cmailOldMove - 1, "Draw declined");
11182 #endif /*NOTDEF*/
11183         } else {
11184             DisplayError(_("There is no pending offer on this move"), 0);
11185         }
11186     } else {
11187         /* Not used for offers from chess program */
11188     }
11189 }
11190
11191 void
11192 RematchEvent()
11193 {
11194     /* Issue ICS rematch command */
11195     if (appData.icsActive) {
11196         SendToICS(ics_prefix);
11197         SendToICS("rematch\n");
11198     }
11199 }
11200
11201 void
11202 CallFlagEvent()
11203 {
11204     /* Call your opponent's flag (claim a win on time) */
11205     if (appData.icsActive) {
11206         SendToICS(ics_prefix);
11207         SendToICS("flag\n");
11208     } else {
11209         switch (gameMode) {
11210           default:
11211             return;
11212           case MachinePlaysWhite:
11213             if (whiteFlag) {
11214                 if (blackFlag)
11215                   GameEnds(GameIsDrawn, "Both players ran out of time",
11216                            GE_PLAYER);
11217                 else
11218                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11219             } else {
11220                 DisplayError(_("Your opponent is not out of time"), 0);
11221             }
11222             break;
11223           case MachinePlaysBlack:
11224             if (blackFlag) {
11225                 if (whiteFlag)
11226                   GameEnds(GameIsDrawn, "Both players ran out of time",
11227                            GE_PLAYER);
11228                 else
11229                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11230             } else {
11231                 DisplayError(_("Your opponent is not out of time"), 0);
11232             }
11233             break;
11234         }
11235     }
11236 }
11237
11238 void
11239 DrawEvent()
11240 {
11241     /* Offer draw or accept pending draw offer from opponent */
11242     
11243     if (appData.icsActive) {
11244         /* Note: tournament rules require draw offers to be
11245            made after you make your move but before you punch
11246            your clock.  Currently ICS doesn't let you do that;
11247            instead, you immediately punch your clock after making
11248            a move, but you can offer a draw at any time. */
11249         
11250         SendToICS(ics_prefix);
11251         SendToICS("draw\n");
11252     } else if (cmailMsgLoaded) {
11253         if (currentMove == cmailOldMove &&
11254             commentList[cmailOldMove] != NULL &&
11255             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11256                    "Black offers a draw" : "White offers a draw")) {
11257             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11258             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11259         } else if (currentMove == cmailOldMove + 1) {
11260             char *offer = WhiteOnMove(cmailOldMove) ?
11261               "White offers a draw" : "Black offers a draw";
11262             AppendComment(currentMove, offer);
11263             DisplayComment(currentMove - 1, offer);
11264             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11265         } else {
11266             DisplayError(_("You must make your move before offering a draw"), 0);
11267             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11268         }
11269     } else if (first.offeredDraw) {
11270         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11271     } else {
11272         if (first.sendDrawOffers) {
11273             SendToProgram("draw\n", &first);
11274             userOfferedDraw = TRUE;
11275         }
11276     }
11277 }
11278
11279 void
11280 AdjournEvent()
11281 {
11282     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11283     
11284     if (appData.icsActive) {
11285         SendToICS(ics_prefix);
11286         SendToICS("adjourn\n");
11287     } else {
11288         /* Currently GNU Chess doesn't offer or accept Adjourns */
11289     }
11290 }
11291
11292
11293 void
11294 AbortEvent()
11295 {
11296     /* Offer Abort or accept pending Abort offer from opponent */
11297     
11298     if (appData.icsActive) {
11299         SendToICS(ics_prefix);
11300         SendToICS("abort\n");
11301     } else {
11302         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11303     }
11304 }
11305
11306 void
11307 ResignEvent()
11308 {
11309     /* Resign.  You can do this even if it's not your turn. */
11310     
11311     if (appData.icsActive) {
11312         SendToICS(ics_prefix);
11313         SendToICS("resign\n");
11314     } else {
11315         switch (gameMode) {
11316           case MachinePlaysWhite:
11317             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11318             break;
11319           case MachinePlaysBlack:
11320             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11321             break;
11322           case EditGame:
11323             if (cmailMsgLoaded) {
11324                 TruncateGame();
11325                 if (WhiteOnMove(cmailOldMove)) {
11326                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11327                 } else {
11328                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11329                 }
11330                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11331             }
11332             break;
11333           default:
11334             break;
11335         }
11336     }
11337 }
11338
11339
11340 void
11341 StopObservingEvent()
11342 {
11343     /* Stop observing current games */
11344     SendToICS(ics_prefix);
11345     SendToICS("unobserve\n");
11346 }
11347
11348 void
11349 StopExaminingEvent()
11350 {
11351     /* Stop observing current game */
11352     SendToICS(ics_prefix);
11353     SendToICS("unexamine\n");
11354 }
11355
11356 void
11357 ForwardInner(target)
11358      int target;
11359 {
11360     int limit;
11361
11362     if (appData.debugMode)
11363         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11364                 target, currentMove, forwardMostMove);
11365
11366     if (gameMode == EditPosition)
11367       return;
11368
11369     if (gameMode == PlayFromGameFile && !pausing)
11370       PauseEvent();
11371     
11372     if (gameMode == IcsExamining && pausing)
11373       limit = pauseExamForwardMostMove;
11374     else
11375       limit = forwardMostMove;
11376     
11377     if (target > limit) target = limit;
11378
11379     if (target > 0 && moveList[target - 1][0]) {
11380         int fromX, fromY, toX, toY;
11381         toX = moveList[target - 1][2] - AAA;
11382         toY = moveList[target - 1][3] - ONE;
11383         if (moveList[target - 1][1] == '@') {
11384             if (appData.highlightLastMove) {
11385                 SetHighlights(-1, -1, toX, toY);
11386             }
11387         } else {
11388             fromX = moveList[target - 1][0] - AAA;
11389             fromY = moveList[target - 1][1] - ONE;
11390             if (target == currentMove + 1) {
11391                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11392             }
11393             if (appData.highlightLastMove) {
11394                 SetHighlights(fromX, fromY, toX, toY);
11395             }
11396         }
11397     }
11398     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11399         gameMode == Training || gameMode == PlayFromGameFile || 
11400         gameMode == AnalyzeFile) {
11401         while (currentMove < target) {
11402             SendMoveToProgram(currentMove++, &first);
11403         }
11404     } else {
11405         currentMove = target;
11406     }
11407     
11408     if (gameMode == EditGame || gameMode == EndOfGame) {
11409         whiteTimeRemaining = timeRemaining[0][currentMove];
11410         blackTimeRemaining = timeRemaining[1][currentMove];
11411     }
11412     DisplayBothClocks();
11413     DisplayMove(currentMove - 1);
11414     DrawPosition(FALSE, boards[currentMove]);
11415     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11416     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11417         DisplayComment(currentMove - 1, commentList[currentMove]);
11418     }
11419 }
11420
11421
11422 void
11423 ForwardEvent()
11424 {
11425     if (gameMode == IcsExamining && !pausing) {
11426         SendToICS(ics_prefix);
11427         SendToICS("forward\n");
11428     } else {
11429         ForwardInner(currentMove + 1);
11430     }
11431 }
11432
11433 void
11434 ToEndEvent()
11435 {
11436     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11437         /* to optimze, we temporarily turn off analysis mode while we feed
11438          * the remaining moves to the engine. Otherwise we get analysis output
11439          * after each move.
11440          */ 
11441         if (first.analysisSupport) {
11442           SendToProgram("exit\nforce\n", &first);
11443           first.analyzing = FALSE;
11444         }
11445     }
11446         
11447     if (gameMode == IcsExamining && !pausing) {
11448         SendToICS(ics_prefix);
11449         SendToICS("forward 999999\n");
11450     } else {
11451         ForwardInner(forwardMostMove);
11452     }
11453
11454     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11455         /* we have fed all the moves, so reactivate analysis mode */
11456         SendToProgram("analyze\n", &first);
11457         first.analyzing = TRUE;
11458         /*first.maybeThinking = TRUE;*/
11459         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11460     }
11461 }
11462
11463 void
11464 BackwardInner(target)
11465      int target;
11466 {
11467     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11468
11469     if (appData.debugMode)
11470         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11471                 target, currentMove, forwardMostMove);
11472
11473     if (gameMode == EditPosition) return;
11474     if (currentMove <= backwardMostMove) {
11475         ClearHighlights();
11476         DrawPosition(full_redraw, boards[currentMove]);
11477         return;
11478     }
11479     if (gameMode == PlayFromGameFile && !pausing)
11480       PauseEvent();
11481     
11482     if (moveList[target][0]) {
11483         int fromX, fromY, toX, toY;
11484         toX = moveList[target][2] - AAA;
11485         toY = moveList[target][3] - ONE;
11486         if (moveList[target][1] == '@') {
11487             if (appData.highlightLastMove) {
11488                 SetHighlights(-1, -1, toX, toY);
11489             }
11490         } else {
11491             fromX = moveList[target][0] - AAA;
11492             fromY = moveList[target][1] - ONE;
11493             if (target == currentMove - 1) {
11494                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11495             }
11496             if (appData.highlightLastMove) {
11497                 SetHighlights(fromX, fromY, toX, toY);
11498             }
11499         }
11500     }
11501     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11502         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11503         while (currentMove > target) {
11504             SendToProgram("undo\n", &first);
11505             currentMove--;
11506         }
11507     } else {
11508         currentMove = target;
11509     }
11510     
11511     if (gameMode == EditGame || gameMode == EndOfGame) {
11512         whiteTimeRemaining = timeRemaining[0][currentMove];
11513         blackTimeRemaining = timeRemaining[1][currentMove];
11514     }
11515     DisplayBothClocks();
11516     DisplayMove(currentMove - 1);
11517     DrawPosition(full_redraw, boards[currentMove]);
11518     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11519     // [HGM] PV info: routine tests if comment empty
11520     DisplayComment(currentMove - 1, commentList[currentMove]);
11521 }
11522
11523 void
11524 BackwardEvent()
11525 {
11526     if (gameMode == IcsExamining && !pausing) {
11527         SendToICS(ics_prefix);
11528         SendToICS("backward\n");
11529     } else {
11530         BackwardInner(currentMove - 1);
11531     }
11532 }
11533
11534 void
11535 ToStartEvent()
11536 {
11537     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11538         /* to optimze, we temporarily turn off analysis mode while we undo
11539          * all the moves. Otherwise we get analysis output after each undo.
11540          */ 
11541         if (first.analysisSupport) {
11542           SendToProgram("exit\nforce\n", &first);
11543           first.analyzing = FALSE;
11544         }
11545     }
11546
11547     if (gameMode == IcsExamining && !pausing) {
11548         SendToICS(ics_prefix);
11549         SendToICS("backward 999999\n");
11550     } else {
11551         BackwardInner(backwardMostMove);
11552     }
11553
11554     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11555         /* we have fed all the moves, so reactivate analysis mode */
11556         SendToProgram("analyze\n", &first);
11557         first.analyzing = TRUE;
11558         /*first.maybeThinking = TRUE;*/
11559         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11560     }
11561 }
11562
11563 void
11564 ToNrEvent(int to)
11565 {
11566   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11567   if (to >= forwardMostMove) to = forwardMostMove;
11568   if (to <= backwardMostMove) to = backwardMostMove;
11569   if (to < currentMove) {
11570     BackwardInner(to);
11571   } else {
11572     ForwardInner(to);
11573   }
11574 }
11575
11576 void
11577 RevertEvent()
11578 {
11579     if (gameMode != IcsExamining) {
11580         DisplayError(_("You are not examining a game"), 0);
11581         return;
11582     }
11583     if (pausing) {
11584         DisplayError(_("You can't revert while pausing"), 0);
11585         return;
11586     }
11587     SendToICS(ics_prefix);
11588     SendToICS("revert\n");
11589 }
11590
11591 void
11592 RetractMoveEvent()
11593 {
11594     switch (gameMode) {
11595       case MachinePlaysWhite:
11596       case MachinePlaysBlack:
11597         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11598             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11599             return;
11600         }
11601         if (forwardMostMove < 2) return;
11602         currentMove = forwardMostMove = forwardMostMove - 2;
11603         whiteTimeRemaining = timeRemaining[0][currentMove];
11604         blackTimeRemaining = timeRemaining[1][currentMove];
11605         DisplayBothClocks();
11606         DisplayMove(currentMove - 1);
11607         ClearHighlights();/*!! could figure this out*/
11608         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11609         SendToProgram("remove\n", &first);
11610         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11611         break;
11612
11613       case BeginningOfGame:
11614       default:
11615         break;
11616
11617       case IcsPlayingWhite:
11618       case IcsPlayingBlack:
11619         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11620             SendToICS(ics_prefix);
11621             SendToICS("takeback 2\n");
11622         } else {
11623             SendToICS(ics_prefix);
11624             SendToICS("takeback 1\n");
11625         }
11626         break;
11627     }
11628 }
11629
11630 void
11631 MoveNowEvent()
11632 {
11633     ChessProgramState *cps;
11634
11635     switch (gameMode) {
11636       case MachinePlaysWhite:
11637         if (!WhiteOnMove(forwardMostMove)) {
11638             DisplayError(_("It is your turn"), 0);
11639             return;
11640         }
11641         cps = &first;
11642         break;
11643       case MachinePlaysBlack:
11644         if (WhiteOnMove(forwardMostMove)) {
11645             DisplayError(_("It is your turn"), 0);
11646             return;
11647         }
11648         cps = &first;
11649         break;
11650       case TwoMachinesPlay:
11651         if (WhiteOnMove(forwardMostMove) ==
11652             (first.twoMachinesColor[0] == 'w')) {
11653             cps = &first;
11654         } else {
11655             cps = &second;
11656         }
11657         break;
11658       case BeginningOfGame:
11659       default:
11660         return;
11661     }
11662     SendToProgram("?\n", cps);
11663 }
11664
11665 void
11666 TruncateGameEvent()
11667 {
11668     EditGameEvent();
11669     if (gameMode != EditGame) return;
11670     TruncateGame();
11671 }
11672
11673 void
11674 TruncateGame()
11675 {
11676     if (forwardMostMove > currentMove) {
11677         if (gameInfo.resultDetails != NULL) {
11678             free(gameInfo.resultDetails);
11679             gameInfo.resultDetails = NULL;
11680             gameInfo.result = GameUnfinished;
11681         }
11682         forwardMostMove = currentMove;
11683         HistorySet(parseList, backwardMostMove, forwardMostMove,
11684                    currentMove-1);
11685     }
11686 }
11687
11688 void
11689 HintEvent()
11690 {
11691     if (appData.noChessProgram) return;
11692     switch (gameMode) {
11693       case MachinePlaysWhite:
11694         if (WhiteOnMove(forwardMostMove)) {
11695             DisplayError(_("Wait until your turn"), 0);
11696             return;
11697         }
11698         break;
11699       case BeginningOfGame:
11700       case MachinePlaysBlack:
11701         if (!WhiteOnMove(forwardMostMove)) {
11702             DisplayError(_("Wait until your turn"), 0);
11703             return;
11704         }
11705         break;
11706       default:
11707         DisplayError(_("No hint available"), 0);
11708         return;
11709     }
11710     SendToProgram("hint\n", &first);
11711     hintRequested = TRUE;
11712 }
11713
11714 void
11715 BookEvent()
11716 {
11717     if (appData.noChessProgram) return;
11718     switch (gameMode) {
11719       case MachinePlaysWhite:
11720         if (WhiteOnMove(forwardMostMove)) {
11721             DisplayError(_("Wait until your turn"), 0);
11722             return;
11723         }
11724         break;
11725       case BeginningOfGame:
11726       case MachinePlaysBlack:
11727         if (!WhiteOnMove(forwardMostMove)) {
11728             DisplayError(_("Wait until your turn"), 0);
11729             return;
11730         }
11731         break;
11732       case EditPosition:
11733         EditPositionDone();
11734         break;
11735       case TwoMachinesPlay:
11736         return;
11737       default:
11738         break;
11739     }
11740     SendToProgram("bk\n", &first);
11741     bookOutput[0] = NULLCHAR;
11742     bookRequested = TRUE;
11743 }
11744
11745 void
11746 AboutGameEvent()
11747 {
11748     char *tags = PGNTags(&gameInfo);
11749     TagsPopUp(tags, CmailMsg());
11750     free(tags);
11751 }
11752
11753 /* end button procedures */
11754
11755 void
11756 PrintPosition(fp, move)
11757      FILE *fp;
11758      int move;
11759 {
11760     int i, j;
11761     
11762     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11763         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11764             char c = PieceToChar(boards[move][i][j]);
11765             fputc(c == 'x' ? '.' : c, fp);
11766             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11767         }
11768     }
11769     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11770       fprintf(fp, "white to play\n");
11771     else
11772       fprintf(fp, "black to play\n");
11773 }
11774
11775 void
11776 PrintOpponents(fp)
11777      FILE *fp;
11778 {
11779     if (gameInfo.white != NULL) {
11780         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11781     } else {
11782         fprintf(fp, "\n");
11783     }
11784 }
11785
11786 /* Find last component of program's own name, using some heuristics */
11787 void
11788 TidyProgramName(prog, host, buf)
11789      char *prog, *host, buf[MSG_SIZ];
11790 {
11791     char *p, *q;
11792     int local = (strcmp(host, "localhost") == 0);
11793     while (!local && (p = strchr(prog, ';')) != NULL) {
11794         p++;
11795         while (*p == ' ') p++;
11796         prog = p;
11797     }
11798     if (*prog == '"' || *prog == '\'') {
11799         q = strchr(prog + 1, *prog);
11800     } else {
11801         q = strchr(prog, ' ');
11802     }
11803     if (q == NULL) q = prog + strlen(prog);
11804     p = q;
11805     while (p >= prog && *p != '/' && *p != '\\') p--;
11806     p++;
11807     if(p == prog && *p == '"') p++;
11808     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11809     memcpy(buf, p, q - p);
11810     buf[q - p] = NULLCHAR;
11811     if (!local) {
11812         strcat(buf, "@");
11813         strcat(buf, host);
11814     }
11815 }
11816
11817 char *
11818 TimeControlTagValue()
11819 {
11820     char buf[MSG_SIZ];
11821     if (!appData.clockMode) {
11822         strcpy(buf, "-");
11823     } else if (movesPerSession > 0) {
11824         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11825     } else if (timeIncrement == 0) {
11826         sprintf(buf, "%ld", timeControl/1000);
11827     } else {
11828         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11829     }
11830     return StrSave(buf);
11831 }
11832
11833 void
11834 SetGameInfo()
11835 {
11836     /* This routine is used only for certain modes */
11837     VariantClass v = gameInfo.variant;
11838     ClearGameInfo(&gameInfo);
11839     gameInfo.variant = v;
11840
11841     switch (gameMode) {
11842       case MachinePlaysWhite:
11843         gameInfo.event = StrSave( appData.pgnEventHeader );
11844         gameInfo.site = StrSave(HostName());
11845         gameInfo.date = PGNDate();
11846         gameInfo.round = StrSave("-");
11847         gameInfo.white = StrSave(first.tidy);
11848         gameInfo.black = StrSave(UserName());
11849         gameInfo.timeControl = TimeControlTagValue();
11850         break;
11851
11852       case MachinePlaysBlack:
11853         gameInfo.event = StrSave( appData.pgnEventHeader );
11854         gameInfo.site = StrSave(HostName());
11855         gameInfo.date = PGNDate();
11856         gameInfo.round = StrSave("-");
11857         gameInfo.white = StrSave(UserName());
11858         gameInfo.black = StrSave(first.tidy);
11859         gameInfo.timeControl = TimeControlTagValue();
11860         break;
11861
11862       case TwoMachinesPlay:
11863         gameInfo.event = StrSave( appData.pgnEventHeader );
11864         gameInfo.site = StrSave(HostName());
11865         gameInfo.date = PGNDate();
11866         if (matchGame > 0) {
11867             char buf[MSG_SIZ];
11868             sprintf(buf, "%d", matchGame);
11869             gameInfo.round = StrSave(buf);
11870         } else {
11871             gameInfo.round = StrSave("-");
11872         }
11873         if (first.twoMachinesColor[0] == 'w') {
11874             gameInfo.white = StrSave(first.tidy);
11875             gameInfo.black = StrSave(second.tidy);
11876         } else {
11877             gameInfo.white = StrSave(second.tidy);
11878             gameInfo.black = StrSave(first.tidy);
11879         }
11880         gameInfo.timeControl = TimeControlTagValue();
11881         break;
11882
11883       case EditGame:
11884         gameInfo.event = StrSave("Edited game");
11885         gameInfo.site = StrSave(HostName());
11886         gameInfo.date = PGNDate();
11887         gameInfo.round = StrSave("-");
11888         gameInfo.white = StrSave("-");
11889         gameInfo.black = StrSave("-");
11890         break;
11891
11892       case EditPosition:
11893         gameInfo.event = StrSave("Edited position");
11894         gameInfo.site = StrSave(HostName());
11895         gameInfo.date = PGNDate();
11896         gameInfo.round = StrSave("-");
11897         gameInfo.white = StrSave("-");
11898         gameInfo.black = StrSave("-");
11899         break;
11900
11901       case IcsPlayingWhite:
11902       case IcsPlayingBlack:
11903       case IcsObserving:
11904       case IcsExamining:
11905         break;
11906
11907       case PlayFromGameFile:
11908         gameInfo.event = StrSave("Game from non-PGN file");
11909         gameInfo.site = StrSave(HostName());
11910         gameInfo.date = PGNDate();
11911         gameInfo.round = StrSave("-");
11912         gameInfo.white = StrSave("?");
11913         gameInfo.black = StrSave("?");
11914         break;
11915
11916       default:
11917         break;
11918     }
11919 }
11920
11921 void
11922 ReplaceComment(index, text)
11923      int index;
11924      char *text;
11925 {
11926     int len;
11927
11928     while (*text == '\n') text++;
11929     len = strlen(text);
11930     while (len > 0 && text[len - 1] == '\n') len--;
11931
11932     if (commentList[index] != NULL)
11933       free(commentList[index]);
11934
11935     if (len == 0) {
11936         commentList[index] = NULL;
11937         return;
11938     }
11939     commentList[index] = (char *) malloc(len + 2);
11940     strncpy(commentList[index], text, len);
11941     commentList[index][len] = '\n';
11942     commentList[index][len + 1] = NULLCHAR;
11943 }
11944
11945 void
11946 CrushCRs(text)
11947      char *text;
11948 {
11949   char *p = text;
11950   char *q = text;
11951   char ch;
11952
11953   do {
11954     ch = *p++;
11955     if (ch == '\r') continue;
11956     *q++ = ch;
11957   } while (ch != '\0');
11958 }
11959
11960 void
11961 AppendComment(index, text)
11962      int index;
11963      char *text;
11964 {
11965     int oldlen, len;
11966     char *old;
11967
11968     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11969
11970     CrushCRs(text);
11971     while (*text == '\n') text++;
11972     len = strlen(text);
11973     while (len > 0 && text[len - 1] == '\n') len--;
11974
11975     if (len == 0) return;
11976
11977     if (commentList[index] != NULL) {
11978         old = commentList[index];
11979         oldlen = strlen(old);
11980         commentList[index] = (char *) malloc(oldlen + len + 2);
11981         strcpy(commentList[index], old);
11982         free(old);
11983         strncpy(&commentList[index][oldlen], text, len);
11984         commentList[index][oldlen + len] = '\n';
11985         commentList[index][oldlen + len + 1] = NULLCHAR;
11986     } else {
11987         commentList[index] = (char *) malloc(len + 2);
11988         strncpy(commentList[index], text, len);
11989         commentList[index][len] = '\n';
11990         commentList[index][len + 1] = NULLCHAR;
11991     }
11992 }
11993
11994 static char * FindStr( char * text, char * sub_text )
11995 {
11996     char * result = strstr( text, sub_text );
11997
11998     if( result != NULL ) {
11999         result += strlen( sub_text );
12000     }
12001
12002     return result;
12003 }
12004
12005 /* [AS] Try to extract PV info from PGN comment */
12006 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12007 char *GetInfoFromComment( int index, char * text )
12008 {
12009     char * sep = text;
12010
12011     if( text != NULL && index > 0 ) {
12012         int score = 0;
12013         int depth = 0;
12014         int time = -1, sec = 0, deci;
12015         char * s_eval = FindStr( text, "[%eval " );
12016         char * s_emt = FindStr( text, "[%emt " );
12017
12018         if( s_eval != NULL || s_emt != NULL ) {
12019             /* New style */
12020             char delim;
12021
12022             if( s_eval != NULL ) {
12023                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12024                     return text;
12025                 }
12026
12027                 if( delim != ']' ) {
12028                     return text;
12029                 }
12030             }
12031
12032             if( s_emt != NULL ) {
12033             }
12034         }
12035         else {
12036             /* We expect something like: [+|-]nnn.nn/dd */
12037             int score_lo = 0;
12038
12039             sep = strchr( text, '/' );
12040             if( sep == NULL || sep < (text+4) ) {
12041                 return text;
12042             }
12043
12044             time = -1; sec = -1; deci = -1;
12045             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12046                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12047                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12048                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12049                 return text;
12050             }
12051
12052             if( score_lo < 0 || score_lo >= 100 ) {
12053                 return text;
12054             }
12055
12056             if(sec >= 0) time = 600*time + 10*sec; else
12057             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12058
12059             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12060
12061             /* [HGM] PV time: now locate end of PV info */
12062             while( *++sep >= '0' && *sep <= '9'); // strip depth
12063             if(time >= 0)
12064             while( *++sep >= '0' && *sep <= '9'); // strip time
12065             if(sec >= 0)
12066             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12067             if(deci >= 0)
12068             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12069             while(*sep == ' ') sep++;
12070         }
12071
12072         if( depth <= 0 ) {
12073             return text;
12074         }
12075
12076         if( time < 0 ) {
12077             time = -1;
12078         }
12079
12080         pvInfoList[index-1].depth = depth;
12081         pvInfoList[index-1].score = score;
12082         pvInfoList[index-1].time  = 10*time; // centi-sec
12083     }
12084     return sep;
12085 }
12086
12087 void
12088 SendToProgram(message, cps)
12089      char *message;
12090      ChessProgramState *cps;
12091 {
12092     int count, outCount, error;
12093     char buf[MSG_SIZ];
12094
12095     if (cps->pr == NULL) return;
12096     Attention(cps);
12097     
12098     if (appData.debugMode) {
12099         TimeMark now;
12100         GetTimeMark(&now);
12101         fprintf(debugFP, "%ld >%-6s: %s", 
12102                 SubtractTimeMarks(&now, &programStartTime),
12103                 cps->which, message);
12104     }
12105     
12106     count = strlen(message);
12107     outCount = OutputToProcess(cps->pr, message, count, &error);
12108     if (outCount < count && !exiting 
12109                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12110         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12111         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12112             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12113                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12114                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12115             } else {
12116                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12117             }
12118             gameInfo.resultDetails = buf;
12119         }
12120         DisplayFatalError(buf, error, 1);
12121     }
12122 }
12123
12124 void
12125 ReceiveFromProgram(isr, closure, message, count, error)
12126      InputSourceRef isr;
12127      VOIDSTAR closure;
12128      char *message;
12129      int count;
12130      int error;
12131 {
12132     char *end_str;
12133     char buf[MSG_SIZ];
12134     ChessProgramState *cps = (ChessProgramState *)closure;
12135
12136     if (isr != cps->isr) return; /* Killed intentionally */
12137     if (count <= 0) {
12138         if (count == 0) {
12139             sprintf(buf,
12140                     _("Error: %s chess program (%s) exited unexpectedly"),
12141                     cps->which, cps->program);
12142         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12143                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12144                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12145                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12146                 } else {
12147                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12148                 }
12149                 gameInfo.resultDetails = buf;
12150             }
12151             RemoveInputSource(cps->isr);
12152             DisplayFatalError(buf, 0, 1);
12153         } else {
12154             sprintf(buf,
12155                     _("Error reading from %s chess program (%s)"),
12156                     cps->which, cps->program);
12157             RemoveInputSource(cps->isr);
12158
12159             /* [AS] Program is misbehaving badly... kill it */
12160             if( count == -2 ) {
12161                 DestroyChildProcess( cps->pr, 9 );
12162                 cps->pr = NoProc;
12163             }
12164
12165             DisplayFatalError(buf, error, 1);
12166         }
12167         return;
12168     }
12169     
12170     if ((end_str = strchr(message, '\r')) != NULL)
12171       *end_str = NULLCHAR;
12172     if ((end_str = strchr(message, '\n')) != NULL)
12173       *end_str = NULLCHAR;
12174     
12175     if (appData.debugMode) {
12176         TimeMark now; int print = 1;
12177         char *quote = ""; char c; int i;
12178
12179         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12180                 char start = message[0];
12181                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12182                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12183                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12184                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12185                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12186                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12187                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12188                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12189                         { quote = "# "; print = (appData.engineComments == 2); }
12190                 message[0] = start; // restore original message
12191         }
12192         if(print) {
12193                 GetTimeMark(&now);
12194                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12195                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12196                         quote,
12197                         message);
12198         }
12199     }
12200
12201     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12202     if (appData.icsEngineAnalyze) {
12203         if (strstr(message, "whisper") != NULL ||
12204              strstr(message, "kibitz") != NULL || 
12205             strstr(message, "tellics") != NULL) return;
12206     }
12207
12208     HandleMachineMove(message, cps);
12209 }
12210
12211
12212 void
12213 SendTimeControl(cps, mps, tc, inc, sd, st)
12214      ChessProgramState *cps;
12215      int mps, inc, sd, st;
12216      long tc;
12217 {
12218     char buf[MSG_SIZ];
12219     int seconds;
12220
12221     if( timeControl_2 > 0 ) {
12222         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12223             tc = timeControl_2;
12224         }
12225     }
12226     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12227     inc /= cps->timeOdds;
12228     st  /= cps->timeOdds;
12229
12230     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12231
12232     if (st > 0) {
12233       /* Set exact time per move, normally using st command */
12234       if (cps->stKludge) {
12235         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12236         seconds = st % 60;
12237         if (seconds == 0) {
12238           sprintf(buf, "level 1 %d\n", st/60);
12239         } else {
12240           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12241         }
12242       } else {
12243         sprintf(buf, "st %d\n", st);
12244       }
12245     } else {
12246       /* Set conventional or incremental time control, using level command */
12247       if (seconds == 0) {
12248         /* Note old gnuchess bug -- minutes:seconds used to not work.
12249            Fixed in later versions, but still avoid :seconds
12250            when seconds is 0. */
12251         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12252       } else {
12253         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12254                 seconds, inc/1000);
12255       }
12256     }
12257     SendToProgram(buf, cps);
12258
12259     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12260     /* Orthogonally, limit search to given depth */
12261     if (sd > 0) {
12262       if (cps->sdKludge) {
12263         sprintf(buf, "depth\n%d\n", sd);
12264       } else {
12265         sprintf(buf, "sd %d\n", sd);
12266       }
12267       SendToProgram(buf, cps);
12268     }
12269
12270     if(cps->nps > 0) { /* [HGM] nps */
12271         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12272         else {
12273                 sprintf(buf, "nps %d\n", cps->nps);
12274               SendToProgram(buf, cps);
12275         }
12276     }
12277 }
12278
12279 ChessProgramState *WhitePlayer()
12280 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12281 {
12282     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12283        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12284         return &second;
12285     return &first;
12286 }
12287
12288 void
12289 SendTimeRemaining(cps, machineWhite)
12290      ChessProgramState *cps;
12291      int /*boolean*/ machineWhite;
12292 {
12293     char message[MSG_SIZ];
12294     long time, otime;
12295
12296     /* Note: this routine must be called when the clocks are stopped
12297        or when they have *just* been set or switched; otherwise
12298        it will be off by the time since the current tick started.
12299     */
12300     if (machineWhite) {
12301         time = whiteTimeRemaining / 10;
12302         otime = blackTimeRemaining / 10;
12303     } else {
12304         time = blackTimeRemaining / 10;
12305         otime = whiteTimeRemaining / 10;
12306     }
12307     /* [HGM] translate opponent's time by time-odds factor */
12308     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12309     if (appData.debugMode) {
12310         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12311     }
12312
12313     if (time <= 0) time = 1;
12314     if (otime <= 0) otime = 1;
12315     
12316     sprintf(message, "time %ld\n", time);
12317     SendToProgram(message, cps);
12318
12319     sprintf(message, "otim %ld\n", otime);
12320     SendToProgram(message, cps);
12321 }
12322
12323 int
12324 BoolFeature(p, name, loc, cps)
12325      char **p;
12326      char *name;
12327      int *loc;
12328      ChessProgramState *cps;
12329 {
12330   char buf[MSG_SIZ];
12331   int len = strlen(name);
12332   int val;
12333   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12334     (*p) += len + 1;
12335     sscanf(*p, "%d", &val);
12336     *loc = (val != 0);
12337     while (**p && **p != ' ') (*p)++;
12338     sprintf(buf, "accepted %s\n", name);
12339     SendToProgram(buf, cps);
12340     return TRUE;
12341   }
12342   return FALSE;
12343 }
12344
12345 int
12346 IntFeature(p, name, loc, cps)
12347      char **p;
12348      char *name;
12349      int *loc;
12350      ChessProgramState *cps;
12351 {
12352   char buf[MSG_SIZ];
12353   int len = strlen(name);
12354   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12355     (*p) += len + 1;
12356     sscanf(*p, "%d", loc);
12357     while (**p && **p != ' ') (*p)++;
12358     sprintf(buf, "accepted %s\n", name);
12359     SendToProgram(buf, cps);
12360     return TRUE;
12361   }
12362   return FALSE;
12363 }
12364
12365 int
12366 StringFeature(p, name, loc, cps)
12367      char **p;
12368      char *name;
12369      char loc[];
12370      ChessProgramState *cps;
12371 {
12372   char buf[MSG_SIZ];
12373   int len = strlen(name);
12374   if (strncmp((*p), name, len) == 0
12375       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12376     (*p) += len + 2;
12377     sscanf(*p, "%[^\"]", loc);
12378     while (**p && **p != '\"') (*p)++;
12379     if (**p == '\"') (*p)++;
12380     sprintf(buf, "accepted %s\n", name);
12381     SendToProgram(buf, cps);
12382     return TRUE;
12383   }
12384   return FALSE;
12385 }
12386
12387 int 
12388 ParseOption(Option *opt, ChessProgramState *cps)
12389 // [HGM] options: process the string that defines an engine option, and determine
12390 // name, type, default value, and allowed value range
12391 {
12392         char *p, *q, buf[MSG_SIZ];
12393         int n, min = (-1)<<31, max = 1<<31, def;
12394
12395         if(p = strstr(opt->name, " -spin ")) {
12396             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12397             if(max < min) max = min; // enforce consistency
12398             if(def < min) def = min;
12399             if(def > max) def = max;
12400             opt->value = def;
12401             opt->min = min;
12402             opt->max = max;
12403             opt->type = Spin;
12404         } else if((p = strstr(opt->name, " -slider "))) {
12405             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12406             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12407             if(max < min) max = min; // enforce consistency
12408             if(def < min) def = min;
12409             if(def > max) def = max;
12410             opt->value = def;
12411             opt->min = min;
12412             opt->max = max;
12413             opt->type = Spin; // Slider;
12414         } else if((p = strstr(opt->name, " -string "))) {
12415             opt->textValue = p+9;
12416             opt->type = TextBox;
12417         } else if((p = strstr(opt->name, " -file "))) {
12418             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12419             opt->textValue = p+7;
12420             opt->type = TextBox; // FileName;
12421         } else if((p = strstr(opt->name, " -path "))) {
12422             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12423             opt->textValue = p+7;
12424             opt->type = TextBox; // PathName;
12425         } else if(p = strstr(opt->name, " -check ")) {
12426             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12427             opt->value = (def != 0);
12428             opt->type = CheckBox;
12429         } else if(p = strstr(opt->name, " -combo ")) {
12430             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12431             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12432             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12433             opt->value = n = 0;
12434             while(q = StrStr(q, " /// ")) {
12435                 n++; *q = 0;    // count choices, and null-terminate each of them
12436                 q += 5;
12437                 if(*q == '*') { // remember default, which is marked with * prefix
12438                     q++;
12439                     opt->value = n;
12440                 }
12441                 cps->comboList[cps->comboCnt++] = q;
12442             }
12443             cps->comboList[cps->comboCnt++] = NULL;
12444             opt->max = n + 1;
12445             opt->type = ComboBox;
12446         } else if(p = strstr(opt->name, " -button")) {
12447             opt->type = Button;
12448         } else if(p = strstr(opt->name, " -save")) {
12449             opt->type = SaveButton;
12450         } else return FALSE;
12451         *p = 0; // terminate option name
12452         // now look if the command-line options define a setting for this engine option.
12453         if(cps->optionSettings && cps->optionSettings[0])
12454             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12455         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12456                 sprintf(buf, "option %s", p);
12457                 if(p = strstr(buf, ",")) *p = 0;
12458                 strcat(buf, "\n");
12459                 SendToProgram(buf, cps);
12460         }
12461         return TRUE;
12462 }
12463
12464 void
12465 FeatureDone(cps, val)
12466      ChessProgramState* cps;
12467      int val;
12468 {
12469   DelayedEventCallback cb = GetDelayedEvent();
12470   if ((cb == InitBackEnd3 && cps == &first) ||
12471       (cb == TwoMachinesEventIfReady && cps == &second)) {
12472     CancelDelayedEvent();
12473     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12474   }
12475   cps->initDone = val;
12476 }
12477
12478 /* Parse feature command from engine */
12479 void
12480 ParseFeatures(args, cps)
12481      char* args;
12482      ChessProgramState *cps;  
12483 {
12484   char *p = args;
12485   char *q;
12486   int val;
12487   char buf[MSG_SIZ];
12488
12489   for (;;) {
12490     while (*p == ' ') p++;
12491     if (*p == NULLCHAR) return;
12492
12493     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12494     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12495     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12496     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12497     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12498     if (BoolFeature(&p, "reuse", &val, cps)) {
12499       /* Engine can disable reuse, but can't enable it if user said no */
12500       if (!val) cps->reuse = FALSE;
12501       continue;
12502     }
12503     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12504     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12505       if (gameMode == TwoMachinesPlay) {
12506         DisplayTwoMachinesTitle();
12507       } else {
12508         DisplayTitle("");
12509       }
12510       continue;
12511     }
12512     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12513     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12514     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12515     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12516     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12517     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12518     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12519     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12520     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12521     if (IntFeature(&p, "done", &val, cps)) {
12522       FeatureDone(cps, val);
12523       continue;
12524     }
12525     /* Added by Tord: */
12526     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12527     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12528     /* End of additions by Tord */
12529
12530     /* [HGM] added features: */
12531     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12532     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12533     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12534     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12535     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12536     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12537     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12538         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12539             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12540             SendToProgram(buf, cps);
12541             continue;
12542         }
12543         if(cps->nrOptions >= MAX_OPTIONS) {
12544             cps->nrOptions--;
12545             sprintf(buf, "%s engine has too many options\n", cps->which);
12546             DisplayError(buf, 0);
12547         }
12548         continue;
12549     }
12550     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12551     /* End of additions by HGM */
12552
12553     /* unknown feature: complain and skip */
12554     q = p;
12555     while (*q && *q != '=') q++;
12556     sprintf(buf, "rejected %.*s\n", q-p, p);
12557     SendToProgram(buf, cps);
12558     p = q;
12559     if (*p == '=') {
12560       p++;
12561       if (*p == '\"') {
12562         p++;
12563         while (*p && *p != '\"') p++;
12564         if (*p == '\"') p++;
12565       } else {
12566         while (*p && *p != ' ') p++;
12567       }
12568     }
12569   }
12570
12571 }
12572
12573 void
12574 PeriodicUpdatesEvent(newState)
12575      int newState;
12576 {
12577     if (newState == appData.periodicUpdates)
12578       return;
12579
12580     appData.periodicUpdates=newState;
12581
12582     /* Display type changes, so update it now */
12583 //    DisplayAnalysis();
12584
12585     /* Get the ball rolling again... */
12586     if (newState) {
12587         AnalysisPeriodicEvent(1);
12588         StartAnalysisClock();
12589     }
12590 }
12591
12592 void
12593 PonderNextMoveEvent(newState)
12594      int newState;
12595 {
12596     if (newState == appData.ponderNextMove) return;
12597     if (gameMode == EditPosition) EditPositionDone();
12598     if (newState) {
12599         SendToProgram("hard\n", &first);
12600         if (gameMode == TwoMachinesPlay) {
12601             SendToProgram("hard\n", &second);
12602         }
12603     } else {
12604         SendToProgram("easy\n", &first);
12605         thinkOutput[0] = NULLCHAR;
12606         if (gameMode == TwoMachinesPlay) {
12607             SendToProgram("easy\n", &second);
12608         }
12609     }
12610     appData.ponderNextMove = newState;
12611 }
12612
12613 void
12614 NewSettingEvent(option, command, value)
12615      char *command;
12616      int option, value;
12617 {
12618     char buf[MSG_SIZ];
12619
12620     if (gameMode == EditPosition) EditPositionDone();
12621     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12622     SendToProgram(buf, &first);
12623     if (gameMode == TwoMachinesPlay) {
12624         SendToProgram(buf, &second);
12625     }
12626 }
12627
12628 void
12629 ShowThinkingEvent()
12630 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12631 {
12632     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12633     int newState = appData.showThinking
12634         // [HGM] thinking: other features now need thinking output as well
12635         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12636     
12637     if (oldState == newState) return;
12638     oldState = newState;
12639     if (gameMode == EditPosition) EditPositionDone();
12640     if (oldState) {
12641         SendToProgram("post\n", &first);
12642         if (gameMode == TwoMachinesPlay) {
12643             SendToProgram("post\n", &second);
12644         }
12645     } else {
12646         SendToProgram("nopost\n", &first);
12647         thinkOutput[0] = NULLCHAR;
12648         if (gameMode == TwoMachinesPlay) {
12649             SendToProgram("nopost\n", &second);
12650         }
12651     }
12652 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12653 }
12654
12655 void
12656 AskQuestionEvent(title, question, replyPrefix, which)
12657      char *title; char *question; char *replyPrefix; char *which;
12658 {
12659   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12660   if (pr == NoProc) return;
12661   AskQuestion(title, question, replyPrefix, pr);
12662 }
12663
12664 void
12665 DisplayMove(moveNumber)
12666      int moveNumber;
12667 {
12668     char message[MSG_SIZ];
12669     char res[MSG_SIZ];
12670     char cpThinkOutput[MSG_SIZ];
12671
12672     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12673     
12674     if (moveNumber == forwardMostMove - 1 || 
12675         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12676
12677         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12678
12679         if (strchr(cpThinkOutput, '\n')) {
12680             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12681         }
12682     } else {
12683         *cpThinkOutput = NULLCHAR;
12684     }
12685
12686     /* [AS] Hide thinking from human user */
12687     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12688         *cpThinkOutput = NULLCHAR;
12689         if( thinkOutput[0] != NULLCHAR ) {
12690             int i;
12691
12692             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12693                 cpThinkOutput[i] = '.';
12694             }
12695             cpThinkOutput[i] = NULLCHAR;
12696             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12697         }
12698     }
12699
12700     if (moveNumber == forwardMostMove - 1 &&
12701         gameInfo.resultDetails != NULL) {
12702         if (gameInfo.resultDetails[0] == NULLCHAR) {
12703             sprintf(res, " %s", PGNResult(gameInfo.result));
12704         } else {
12705             sprintf(res, " {%s} %s",
12706                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12707         }
12708     } else {
12709         res[0] = NULLCHAR;
12710     }
12711
12712     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12713         DisplayMessage(res, cpThinkOutput);
12714     } else {
12715         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12716                 WhiteOnMove(moveNumber) ? " " : ".. ",
12717                 parseList[moveNumber], res);
12718         DisplayMessage(message, cpThinkOutput);
12719     }
12720 }
12721
12722 void
12723 DisplayComment(moveNumber, text)
12724      int moveNumber;
12725      char *text;
12726 {
12727     char title[MSG_SIZ];
12728     char buf[8000]; // comment can be long!
12729     int score, depth;
12730
12731     if( appData.autoDisplayComment ) {
12732         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12733             strcpy(title, "Comment");
12734         } else {
12735             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12736                     WhiteOnMove(moveNumber) ? " " : ".. ",
12737                     parseList[moveNumber]);
12738         }
12739         // [HGM] PV info: display PV info together with (or as) comment
12740         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12741             if(text == NULL) text = "";                                           
12742             score = pvInfoList[moveNumber].score;
12743             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12744                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12745             text = buf;
12746         }
12747     } else title[0] = 0;
12748
12749     if (text != NULL)
12750         CommentPopUp(title, text);
12751 }
12752
12753 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12754  * might be busy thinking or pondering.  It can be omitted if your
12755  * gnuchess is configured to stop thinking immediately on any user
12756  * input.  However, that gnuchess feature depends on the FIONREAD
12757  * ioctl, which does not work properly on some flavors of Unix.
12758  */
12759 void
12760 Attention(cps)
12761      ChessProgramState *cps;
12762 {
12763 #if ATTENTION
12764     if (!cps->useSigint) return;
12765     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12766     switch (gameMode) {
12767       case MachinePlaysWhite:
12768       case MachinePlaysBlack:
12769       case TwoMachinesPlay:
12770       case IcsPlayingWhite:
12771       case IcsPlayingBlack:
12772       case AnalyzeMode:
12773       case AnalyzeFile:
12774         /* Skip if we know it isn't thinking */
12775         if (!cps->maybeThinking) return;
12776         if (appData.debugMode)
12777           fprintf(debugFP, "Interrupting %s\n", cps->which);
12778         InterruptChildProcess(cps->pr);
12779         cps->maybeThinking = FALSE;
12780         break;
12781       default:
12782         break;
12783     }
12784 #endif /*ATTENTION*/
12785 }
12786
12787 int
12788 CheckFlags()
12789 {
12790     if (whiteTimeRemaining <= 0) {
12791         if (!whiteFlag) {
12792             whiteFlag = TRUE;
12793             if (appData.icsActive) {
12794                 if (appData.autoCallFlag &&
12795                     gameMode == IcsPlayingBlack && !blackFlag) {
12796                   SendToICS(ics_prefix);
12797                   SendToICS("flag\n");
12798                 }
12799             } else {
12800                 if (blackFlag) {
12801                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12802                 } else {
12803                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12804                     if (appData.autoCallFlag) {
12805                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12806                         return TRUE;
12807                     }
12808                 }
12809             }
12810         }
12811     }
12812     if (blackTimeRemaining <= 0) {
12813         if (!blackFlag) {
12814             blackFlag = TRUE;
12815             if (appData.icsActive) {
12816                 if (appData.autoCallFlag &&
12817                     gameMode == IcsPlayingWhite && !whiteFlag) {
12818                   SendToICS(ics_prefix);
12819                   SendToICS("flag\n");
12820                 }
12821             } else {
12822                 if (whiteFlag) {
12823                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12824                 } else {
12825                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12826                     if (appData.autoCallFlag) {
12827                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12828                         return TRUE;
12829                     }
12830                 }
12831             }
12832         }
12833     }
12834     return FALSE;
12835 }
12836
12837 void
12838 CheckTimeControl()
12839 {
12840     if (!appData.clockMode || appData.icsActive ||
12841         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12842
12843     /*
12844      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12845      */
12846     if ( !WhiteOnMove(forwardMostMove) )
12847         /* White made time control */
12848         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12849         /* [HGM] time odds: correct new time quota for time odds! */
12850                                             / WhitePlayer()->timeOdds;
12851       else
12852         /* Black made time control */
12853         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12854                                             / WhitePlayer()->other->timeOdds;
12855 }
12856
12857 void
12858 DisplayBothClocks()
12859 {
12860     int wom = gameMode == EditPosition ?
12861       !blackPlaysFirst : WhiteOnMove(currentMove);
12862     DisplayWhiteClock(whiteTimeRemaining, wom);
12863     DisplayBlackClock(blackTimeRemaining, !wom);
12864 }
12865
12866
12867 /* Timekeeping seems to be a portability nightmare.  I think everyone
12868    has ftime(), but I'm really not sure, so I'm including some ifdefs
12869    to use other calls if you don't.  Clocks will be less accurate if
12870    you have neither ftime nor gettimeofday.
12871 */
12872
12873 /* VS 2008 requires the #include outside of the function */
12874 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12875 #include <sys/timeb.h>
12876 #endif
12877
12878 /* Get the current time as a TimeMark */
12879 void
12880 GetTimeMark(tm)
12881      TimeMark *tm;
12882 {
12883 #if HAVE_GETTIMEOFDAY
12884
12885     struct timeval timeVal;
12886     struct timezone timeZone;
12887
12888     gettimeofday(&timeVal, &timeZone);
12889     tm->sec = (long) timeVal.tv_sec; 
12890     tm->ms = (int) (timeVal.tv_usec / 1000L);
12891
12892 #else /*!HAVE_GETTIMEOFDAY*/
12893 #if HAVE_FTIME
12894
12895 // include <sys/timeb.h> / moved to just above start of function
12896     struct timeb timeB;
12897
12898     ftime(&timeB);
12899     tm->sec = (long) timeB.time;
12900     tm->ms = (int) timeB.millitm;
12901
12902 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12903     tm->sec = (long) time(NULL);
12904     tm->ms = 0;
12905 #endif
12906 #endif
12907 }
12908
12909 /* Return the difference in milliseconds between two
12910    time marks.  We assume the difference will fit in a long!
12911 */
12912 long
12913 SubtractTimeMarks(tm2, tm1)
12914      TimeMark *tm2, *tm1;
12915 {
12916     return 1000L*(tm2->sec - tm1->sec) +
12917            (long) (tm2->ms - tm1->ms);
12918 }
12919
12920
12921 /*
12922  * Code to manage the game clocks.
12923  *
12924  * In tournament play, black starts the clock and then white makes a move.
12925  * We give the human user a slight advantage if he is playing white---the
12926  * clocks don't run until he makes his first move, so it takes zero time.
12927  * Also, we don't account for network lag, so we could get out of sync
12928  * with GNU Chess's clock -- but then, referees are always right.  
12929  */
12930
12931 static TimeMark tickStartTM;
12932 static long intendedTickLength;
12933
12934 long
12935 NextTickLength(timeRemaining)
12936      long timeRemaining;
12937 {
12938     long nominalTickLength, nextTickLength;
12939
12940     if (timeRemaining > 0L && timeRemaining <= 10000L)
12941       nominalTickLength = 100L;
12942     else
12943       nominalTickLength = 1000L;
12944     nextTickLength = timeRemaining % nominalTickLength;
12945     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12946
12947     return nextTickLength;
12948 }
12949
12950 /* Adjust clock one minute up or down */
12951 void
12952 AdjustClock(Boolean which, int dir)
12953 {
12954     if(which) blackTimeRemaining += 60000*dir;
12955     else      whiteTimeRemaining += 60000*dir;
12956     DisplayBothClocks();
12957 }
12958
12959 /* Stop clocks and reset to a fresh time control */
12960 void
12961 ResetClocks() 
12962 {
12963     (void) StopClockTimer();
12964     if (appData.icsActive) {
12965         whiteTimeRemaining = blackTimeRemaining = 0;
12966     } else { /* [HGM] correct new time quote for time odds */
12967         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
12968         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
12969     }
12970     if (whiteFlag || blackFlag) {
12971         DisplayTitle("");
12972         whiteFlag = blackFlag = FALSE;
12973     }
12974     DisplayBothClocks();
12975 }
12976
12977 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
12978
12979 /* Decrement running clock by amount of time that has passed */
12980 void
12981 DecrementClocks()
12982 {
12983     long timeRemaining;
12984     long lastTickLength, fudge;
12985     TimeMark now;
12986
12987     if (!appData.clockMode) return;
12988     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
12989         
12990     GetTimeMark(&now);
12991
12992     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
12993
12994     /* Fudge if we woke up a little too soon */
12995     fudge = intendedTickLength - lastTickLength;
12996     if (fudge < 0 || fudge > FUDGE) fudge = 0;
12997
12998     if (WhiteOnMove(forwardMostMove)) {
12999         if(whiteNPS >= 0) lastTickLength = 0;
13000         timeRemaining = whiteTimeRemaining -= lastTickLength;
13001         DisplayWhiteClock(whiteTimeRemaining - fudge,
13002                           WhiteOnMove(currentMove));
13003     } else {
13004         if(blackNPS >= 0) lastTickLength = 0;
13005         timeRemaining = blackTimeRemaining -= lastTickLength;
13006         DisplayBlackClock(blackTimeRemaining - fudge,
13007                           !WhiteOnMove(currentMove));
13008     }
13009
13010     if (CheckFlags()) return;
13011         
13012     tickStartTM = now;
13013     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13014     StartClockTimer(intendedTickLength);
13015
13016     /* if the time remaining has fallen below the alarm threshold, sound the
13017      * alarm. if the alarm has sounded and (due to a takeback or time control
13018      * with increment) the time remaining has increased to a level above the
13019      * threshold, reset the alarm so it can sound again. 
13020      */
13021     
13022     if (appData.icsActive && appData.icsAlarm) {
13023
13024         /* make sure we are dealing with the user's clock */
13025         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13026                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13027            )) return;
13028
13029         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13030             alarmSounded = FALSE;
13031         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13032             PlayAlarmSound();
13033             alarmSounded = TRUE;
13034         }
13035     }
13036 }
13037
13038
13039 /* A player has just moved, so stop the previously running
13040    clock and (if in clock mode) start the other one.
13041    We redisplay both clocks in case we're in ICS mode, because
13042    ICS gives us an update to both clocks after every move.
13043    Note that this routine is called *after* forwardMostMove
13044    is updated, so the last fractional tick must be subtracted
13045    from the color that is *not* on move now.
13046 */
13047 void
13048 SwitchClocks()
13049 {
13050     long lastTickLength;
13051     TimeMark now;
13052     int flagged = FALSE;
13053
13054     GetTimeMark(&now);
13055
13056     if (StopClockTimer() && appData.clockMode) {
13057         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13058         if (WhiteOnMove(forwardMostMove)) {
13059             if(blackNPS >= 0) lastTickLength = 0;
13060             blackTimeRemaining -= lastTickLength;
13061            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13062 //         if(pvInfoList[forwardMostMove-1].time == -1)
13063                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13064                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13065         } else {
13066            if(whiteNPS >= 0) lastTickLength = 0;
13067            whiteTimeRemaining -= lastTickLength;
13068            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13069 //         if(pvInfoList[forwardMostMove-1].time == -1)
13070                  pvInfoList[forwardMostMove-1].time = 
13071                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13072         }
13073         flagged = CheckFlags();
13074     }
13075     CheckTimeControl();
13076
13077     if (flagged || !appData.clockMode) return;
13078
13079     switch (gameMode) {
13080       case MachinePlaysBlack:
13081       case MachinePlaysWhite:
13082       case BeginningOfGame:
13083         if (pausing) return;
13084         break;
13085
13086       case EditGame:
13087       case PlayFromGameFile:
13088       case IcsExamining:
13089         return;
13090
13091       default:
13092         break;
13093     }
13094
13095     tickStartTM = now;
13096     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13097       whiteTimeRemaining : blackTimeRemaining);
13098     StartClockTimer(intendedTickLength);
13099 }
13100         
13101
13102 /* Stop both clocks */
13103 void
13104 StopClocks()
13105 {       
13106     long lastTickLength;
13107     TimeMark now;
13108
13109     if (!StopClockTimer()) return;
13110     if (!appData.clockMode) return;
13111
13112     GetTimeMark(&now);
13113
13114     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13115     if (WhiteOnMove(forwardMostMove)) {
13116         if(whiteNPS >= 0) lastTickLength = 0;
13117         whiteTimeRemaining -= lastTickLength;
13118         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13119     } else {
13120         if(blackNPS >= 0) lastTickLength = 0;
13121         blackTimeRemaining -= lastTickLength;
13122         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13123     }
13124     CheckFlags();
13125 }
13126         
13127 /* Start clock of player on move.  Time may have been reset, so
13128    if clock is already running, stop and restart it. */
13129 void
13130 StartClocks()
13131 {
13132     (void) StopClockTimer(); /* in case it was running already */
13133     DisplayBothClocks();
13134     if (CheckFlags()) return;
13135
13136     if (!appData.clockMode) return;
13137     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13138
13139     GetTimeMark(&tickStartTM);
13140     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13141       whiteTimeRemaining : blackTimeRemaining);
13142
13143    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13144     whiteNPS = blackNPS = -1; 
13145     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13146        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13147         whiteNPS = first.nps;
13148     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13149        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13150         blackNPS = first.nps;
13151     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13152         whiteNPS = second.nps;
13153     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13154         blackNPS = second.nps;
13155     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13156
13157     StartClockTimer(intendedTickLength);
13158 }
13159
13160 char *
13161 TimeString(ms)
13162      long ms;
13163 {
13164     long second, minute, hour, day;
13165     char *sign = "";
13166     static char buf[32];
13167     
13168     if (ms > 0 && ms <= 9900) {
13169       /* convert milliseconds to tenths, rounding up */
13170       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13171
13172       sprintf(buf, " %03.1f ", tenths/10.0);
13173       return buf;
13174     }
13175
13176     /* convert milliseconds to seconds, rounding up */
13177     /* use floating point to avoid strangeness of integer division
13178        with negative dividends on many machines */
13179     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13180
13181     if (second < 0) {
13182         sign = "-";
13183         second = -second;
13184     }
13185     
13186     day = second / (60 * 60 * 24);
13187     second = second % (60 * 60 * 24);
13188     hour = second / (60 * 60);
13189     second = second % (60 * 60);
13190     minute = second / 60;
13191     second = second % 60;
13192     
13193     if (day > 0)
13194       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13195               sign, day, hour, minute, second);
13196     else if (hour > 0)
13197       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13198     else
13199       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13200     
13201     return buf;
13202 }
13203
13204
13205 /*
13206  * This is necessary because some C libraries aren't ANSI C compliant yet.
13207  */
13208 char *
13209 StrStr(string, match)
13210      char *string, *match;
13211 {
13212     int i, length;
13213     
13214     length = strlen(match);
13215     
13216     for (i = strlen(string) - length; i >= 0; i--, string++)
13217       if (!strncmp(match, string, length))
13218         return string;
13219     
13220     return NULL;
13221 }
13222
13223 char *
13224 StrCaseStr(string, match)
13225      char *string, *match;
13226 {
13227     int i, j, length;
13228     
13229     length = strlen(match);
13230     
13231     for (i = strlen(string) - length; i >= 0; i--, string++) {
13232         for (j = 0; j < length; j++) {
13233             if (ToLower(match[j]) != ToLower(string[j]))
13234               break;
13235         }
13236         if (j == length) return string;
13237     }
13238
13239     return NULL;
13240 }
13241
13242 #ifndef _amigados
13243 int
13244 StrCaseCmp(s1, s2)
13245      char *s1, *s2;
13246 {
13247     char c1, c2;
13248     
13249     for (;;) {
13250         c1 = ToLower(*s1++);
13251         c2 = ToLower(*s2++);
13252         if (c1 > c2) return 1;
13253         if (c1 < c2) return -1;
13254         if (c1 == NULLCHAR) return 0;
13255     }
13256 }
13257
13258
13259 int
13260 ToLower(c)
13261      int c;
13262 {
13263     return isupper(c) ? tolower(c) : c;
13264 }
13265
13266
13267 int
13268 ToUpper(c)
13269      int c;
13270 {
13271     return islower(c) ? toupper(c) : c;
13272 }
13273 #endif /* !_amigados    */
13274
13275 char *
13276 StrSave(s)
13277      char *s;
13278 {
13279     char *ret;
13280
13281     if ((ret = (char *) malloc(strlen(s) + 1))) {
13282         strcpy(ret, s);
13283     }
13284     return ret;
13285 }
13286
13287 char *
13288 StrSavePtr(s, savePtr)
13289      char *s, **savePtr;
13290 {
13291     if (*savePtr) {
13292         free(*savePtr);
13293     }
13294     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13295         strcpy(*savePtr, s);
13296     }
13297     return(*savePtr);
13298 }
13299
13300 char *
13301 PGNDate()
13302 {
13303     time_t clock;
13304     struct tm *tm;
13305     char buf[MSG_SIZ];
13306
13307     clock = time((time_t *)NULL);
13308     tm = localtime(&clock);
13309     sprintf(buf, "%04d.%02d.%02d",
13310             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13311     return StrSave(buf);
13312 }
13313
13314
13315 char *
13316 PositionToFEN(move, overrideCastling)
13317      int move;
13318      char *overrideCastling;
13319 {
13320     int i, j, fromX, fromY, toX, toY;
13321     int whiteToPlay;
13322     char buf[128];
13323     char *p, *q;
13324     int emptycount;
13325     ChessSquare piece;
13326
13327     whiteToPlay = (gameMode == EditPosition) ?
13328       !blackPlaysFirst : (move % 2 == 0);
13329     p = buf;
13330
13331     /* Piece placement data */
13332     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13333         emptycount = 0;
13334         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13335             if (boards[move][i][j] == EmptySquare) {
13336                 emptycount++;
13337             } else { ChessSquare piece = boards[move][i][j];
13338                 if (emptycount > 0) {
13339                     if(emptycount<10) /* [HGM] can be >= 10 */
13340                         *p++ = '0' + emptycount;
13341                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13342                     emptycount = 0;
13343                 }
13344                 if(PieceToChar(piece) == '+') {
13345                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13346                     *p++ = '+';
13347                     piece = (ChessSquare)(DEMOTED piece);
13348                 } 
13349                 *p++ = PieceToChar(piece);
13350                 if(p[-1] == '~') {
13351                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13352                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13353                     *p++ = '~';
13354                 }
13355             }
13356         }
13357         if (emptycount > 0) {
13358             if(emptycount<10) /* [HGM] can be >= 10 */
13359                 *p++ = '0' + emptycount;
13360             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13361             emptycount = 0;
13362         }
13363         *p++ = '/';
13364     }
13365     *(p - 1) = ' ';
13366
13367     /* [HGM] print Crazyhouse or Shogi holdings */
13368     if( gameInfo.holdingsWidth ) {
13369         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13370         q = p;
13371         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13372             piece = boards[move][i][BOARD_WIDTH-1];
13373             if( piece != EmptySquare )
13374               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13375                   *p++ = PieceToChar(piece);
13376         }
13377         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13378             piece = boards[move][BOARD_HEIGHT-i-1][0];
13379             if( piece != EmptySquare )
13380               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13381                   *p++ = PieceToChar(piece);
13382         }
13383
13384         if( q == p ) *p++ = '-';
13385         *p++ = ']';
13386         *p++ = ' ';
13387     }
13388
13389     /* Active color */
13390     *p++ = whiteToPlay ? 'w' : 'b';
13391     *p++ = ' ';
13392
13393   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13394     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13395   } else {
13396   if(nrCastlingRights) {
13397      q = p;
13398      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13399        /* [HGM] write directly from rights */
13400            if(castlingRights[move][2] >= 0 &&
13401               castlingRights[move][0] >= 0   )
13402                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13403            if(castlingRights[move][2] >= 0 &&
13404               castlingRights[move][1] >= 0   )
13405                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13406            if(castlingRights[move][5] >= 0 &&
13407               castlingRights[move][3] >= 0   )
13408                 *p++ = castlingRights[move][3] + AAA;
13409            if(castlingRights[move][5] >= 0 &&
13410               castlingRights[move][4] >= 0   )
13411                 *p++ = castlingRights[move][4] + AAA;
13412      } else {
13413
13414         /* [HGM] write true castling rights */
13415         if( nrCastlingRights == 6 ) {
13416             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13417                castlingRights[move][2] >= 0  ) *p++ = 'K';
13418             if(castlingRights[move][1] == BOARD_LEFT &&
13419                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13420             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13421                castlingRights[move][5] >= 0  ) *p++ = 'k';
13422             if(castlingRights[move][4] == BOARD_LEFT &&
13423                castlingRights[move][5] >= 0  ) *p++ = 'q';
13424         }
13425      }
13426      if (q == p) *p++ = '-'; /* No castling rights */
13427      *p++ = ' ';
13428   }
13429
13430   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13431      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13432     /* En passant target square */
13433     if (move > backwardMostMove) {
13434         fromX = moveList[move - 1][0] - AAA;
13435         fromY = moveList[move - 1][1] - ONE;
13436         toX = moveList[move - 1][2] - AAA;
13437         toY = moveList[move - 1][3] - ONE;
13438         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13439             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13440             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13441             fromX == toX) {
13442             /* 2-square pawn move just happened */
13443             *p++ = toX + AAA;
13444             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13445         } else {
13446             *p++ = '-';
13447         }
13448     } else if(move == backwardMostMove) {
13449         // [HGM] perhaps we should always do it like this, and forget the above?
13450         if(epStatus[move] >= 0) {
13451             *p++ = epStatus[move] + AAA;
13452             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13453         } else {
13454             *p++ = '-';
13455         }
13456     } else {
13457         *p++ = '-';
13458     }
13459     *p++ = ' ';
13460   }
13461   }
13462
13463     /* [HGM] find reversible plies */
13464     {   int i = 0, j=move;
13465
13466         if (appData.debugMode) { int k;
13467             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13468             for(k=backwardMostMove; k<=forwardMostMove; k++)
13469                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13470
13471         }
13472
13473         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13474         if( j == backwardMostMove ) i += initialRulePlies;
13475         sprintf(p, "%d ", i);
13476         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13477     }
13478     /* Fullmove number */
13479     sprintf(p, "%d", (move / 2) + 1);
13480     
13481     return StrSave(buf);
13482 }
13483
13484 Boolean
13485 ParseFEN(board, blackPlaysFirst, fen)
13486     Board board;
13487      int *blackPlaysFirst;
13488      char *fen;
13489 {
13490     int i, j;
13491     char *p;
13492     int emptycount;
13493     ChessSquare piece;
13494
13495     p = fen;
13496
13497     /* [HGM] by default clear Crazyhouse holdings, if present */
13498     if(gameInfo.holdingsWidth) {
13499        for(i=0; i<BOARD_HEIGHT; i++) {
13500            board[i][0]             = EmptySquare; /* black holdings */
13501            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13502            board[i][1]             = (ChessSquare) 0; /* black counts */
13503            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13504        }
13505     }
13506
13507     /* Piece placement data */
13508     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13509         j = 0;
13510         for (;;) {
13511             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13512                 if (*p == '/') p++;
13513                 emptycount = gameInfo.boardWidth - j;
13514                 while (emptycount--)
13515                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13516                 break;
13517 #if(BOARD_SIZE >= 10)
13518             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13519                 p++; emptycount=10;
13520                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13521                 while (emptycount--)
13522                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13523 #endif
13524             } else if (isdigit(*p)) {
13525                 emptycount = *p++ - '0';
13526                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13527                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13528                 while (emptycount--)
13529                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13530             } else if (*p == '+' || isalpha(*p)) {
13531                 if (j >= gameInfo.boardWidth) return FALSE;
13532                 if(*p=='+') {
13533                     piece = CharToPiece(*++p);
13534                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13535                     piece = (ChessSquare) (PROMOTED piece ); p++;
13536                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13537                 } else piece = CharToPiece(*p++);
13538
13539                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13540                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13541                     piece = (ChessSquare) (PROMOTED piece);
13542                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13543                     p++;
13544                 }
13545                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13546             } else {
13547                 return FALSE;
13548             }
13549         }
13550     }
13551     while (*p == '/' || *p == ' ') p++;
13552
13553     /* [HGM] look for Crazyhouse holdings here */
13554     while(*p==' ') p++;
13555     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13556         if(*p == '[') p++;
13557         if(*p == '-' ) *p++; /* empty holdings */ else {
13558             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13559             /* if we would allow FEN reading to set board size, we would   */
13560             /* have to add holdings and shift the board read so far here   */
13561             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13562                 *p++;
13563                 if((int) piece >= (int) BlackPawn ) {
13564                     i = (int)piece - (int)BlackPawn;
13565                     i = PieceToNumber((ChessSquare)i);
13566                     if( i >= gameInfo.holdingsSize ) return FALSE;
13567                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13568                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13569                 } else {
13570                     i = (int)piece - (int)WhitePawn;
13571                     i = PieceToNumber((ChessSquare)i);
13572                     if( i >= gameInfo.holdingsSize ) return FALSE;
13573                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13574                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13575                 }
13576             }
13577         }
13578         if(*p == ']') *p++;
13579     }
13580
13581     while(*p == ' ') p++;
13582
13583     /* Active color */
13584     switch (*p++) {
13585       case 'w':
13586         *blackPlaysFirst = FALSE;
13587         break;
13588       case 'b': 
13589         *blackPlaysFirst = TRUE;
13590         break;
13591       default:
13592         return FALSE;
13593     }
13594
13595     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13596     /* return the extra info in global variiables             */
13597
13598     /* set defaults in case FEN is incomplete */
13599     FENepStatus = EP_UNKNOWN;
13600     for(i=0; i<nrCastlingRights; i++ ) {
13601         FENcastlingRights[i] =
13602             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13603     }   /* assume possible unless obviously impossible */
13604     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13605     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13606     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13607     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13608     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13609     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13610     FENrulePlies = 0;
13611
13612     while(*p==' ') p++;
13613     if(nrCastlingRights) {
13614       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13615           /* castling indicator present, so default becomes no castlings */
13616           for(i=0; i<nrCastlingRights; i++ ) {
13617                  FENcastlingRights[i] = -1;
13618           }
13619       }
13620       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13621              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13622              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13623              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13624         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13625
13626         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13627             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13628             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13629         }
13630         switch(c) {
13631           case'K':
13632               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13633               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13634               FENcastlingRights[2] = whiteKingFile;
13635               break;
13636           case'Q':
13637               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13638               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13639               FENcastlingRights[2] = whiteKingFile;
13640               break;
13641           case'k':
13642               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13643               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13644               FENcastlingRights[5] = blackKingFile;
13645               break;
13646           case'q':
13647               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13648               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13649               FENcastlingRights[5] = blackKingFile;
13650           case '-':
13651               break;
13652           default: /* FRC castlings */
13653               if(c >= 'a') { /* black rights */
13654                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13655                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13656                   if(i == BOARD_RGHT) break;
13657                   FENcastlingRights[5] = i;
13658                   c -= AAA;
13659                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13660                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13661                   if(c > i)
13662                       FENcastlingRights[3] = c;
13663                   else
13664                       FENcastlingRights[4] = c;
13665               } else { /* white rights */
13666                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13667                     if(board[0][i] == WhiteKing) break;
13668                   if(i == BOARD_RGHT) break;
13669                   FENcastlingRights[2] = i;
13670                   c -= AAA - 'a' + 'A';
13671                   if(board[0][c] >= WhiteKing) break;
13672                   if(c > i)
13673                       FENcastlingRights[0] = c;
13674                   else
13675                       FENcastlingRights[1] = c;
13676               }
13677         }
13678       }
13679     if (appData.debugMode) {
13680         fprintf(debugFP, "FEN castling rights:");
13681         for(i=0; i<nrCastlingRights; i++)
13682         fprintf(debugFP, " %d", FENcastlingRights[i]);
13683         fprintf(debugFP, "\n");
13684     }
13685
13686       while(*p==' ') p++;
13687     }
13688
13689     /* read e.p. field in games that know e.p. capture */
13690     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13691        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13692       if(*p=='-') {
13693         p++; FENepStatus = EP_NONE;
13694       } else {
13695          char c = *p++ - AAA;
13696
13697          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13698          if(*p >= '0' && *p <='9') *p++;
13699          FENepStatus = c;
13700       }
13701     }
13702
13703
13704     if(sscanf(p, "%d", &i) == 1) {
13705         FENrulePlies = i; /* 50-move ply counter */
13706         /* (The move number is still ignored)    */
13707     }
13708
13709     return TRUE;
13710 }
13711       
13712 void
13713 EditPositionPasteFEN(char *fen)
13714 {
13715   if (fen != NULL) {
13716     Board initial_position;
13717
13718     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13719       DisplayError(_("Bad FEN position in clipboard"), 0);
13720       return ;
13721     } else {
13722       int savedBlackPlaysFirst = blackPlaysFirst;
13723       EditPositionEvent();
13724       blackPlaysFirst = savedBlackPlaysFirst;
13725       CopyBoard(boards[0], initial_position);
13726           /* [HGM] copy FEN attributes as well */
13727           {   int i;
13728               initialRulePlies = FENrulePlies;
13729               epStatus[0] = FENepStatus;
13730               for( i=0; i<nrCastlingRights; i++ )
13731                   castlingRights[0][i] = FENcastlingRights[i];
13732           }
13733       EditPositionDone();
13734       DisplayBothClocks();
13735       DrawPosition(FALSE, boards[currentMove]);
13736     }
13737   }
13738 }