fixed engingeoutput routine
[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] = "";
2026 static int player1Rating = -1;
2027 static int player2Rating = -1;
2028 /*----------------------------*/
2029
2030 ColorClass curColor = ColorNormal;
2031 int suppressKibitz = 0;
2032
2033 void
2034 read_from_ics(isr, closure, data, count, error)
2035      InputSourceRef isr;
2036      VOIDSTAR closure;
2037      char *data;
2038      int count;
2039      int error;
2040 {
2041 #define BUF_SIZE 8192
2042 #define STARTED_NONE 0
2043 #define STARTED_MOVES 1
2044 #define STARTED_BOARD 2
2045 #define STARTED_OBSERVE 3
2046 #define STARTED_HOLDINGS 4
2047 #define STARTED_CHATTER 5
2048 #define STARTED_COMMENT 6
2049 #define STARTED_MOVES_NOHIDE 7
2050     
2051     static int started = STARTED_NONE;
2052     static char parse[20000];
2053     static int parse_pos = 0;
2054     static char buf[BUF_SIZE + 1];
2055     static int firstTime = TRUE, intfSet = FALSE;
2056     static ColorClass prevColor = ColorNormal;
2057     static int savingComment = FALSE;
2058     char str[500];
2059     int i, oldi;
2060     int buf_len;
2061     int next_out;
2062     int tkind;
2063     int backup;    /* [DM] For zippy color lines */
2064     char *p;
2065     char talker[MSG_SIZ]; // [HGM] chat
2066     int channel;
2067
2068     if (appData.debugMode) {
2069       if (!error) {
2070         fprintf(debugFP, "<ICS: ");
2071         show_bytes(debugFP, data, count);
2072         fprintf(debugFP, "\n");
2073       }
2074     }
2075
2076     if (appData.debugMode) { int f = forwardMostMove;
2077         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2078                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2079     }
2080     if (count > 0) {
2081         /* If last read ended with a partial line that we couldn't parse,
2082            prepend it to the new read and try again. */
2083         if (leftover_len > 0) {
2084             for (i=0; i<leftover_len; i++)
2085               buf[i] = buf[leftover_start + i];
2086         }
2087
2088         /* Copy in new characters, removing nulls and \r's */
2089         buf_len = leftover_len;
2090         for (i = 0; i < count; i++) {
2091             if (data[i] != NULLCHAR && data[i] != '\r')
2092               buf[buf_len++] = data[i];
2093             if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2094                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2095                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2096                 if(buf_len == 0 || buf[buf_len-1] != ' ')
2097                    buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2098             }
2099         }
2100
2101         buf[buf_len] = NULLCHAR;
2102         next_out = leftover_len;
2103         leftover_start = 0;
2104         
2105         i = 0;
2106         while (i < buf_len) {
2107             /* Deal with part of the TELNET option negotiation
2108                protocol.  We refuse to do anything beyond the
2109                defaults, except that we allow the WILL ECHO option,
2110                which ICS uses to turn off password echoing when we are
2111                directly connected to it.  We reject this option
2112                if localLineEditing mode is on (always on in xboard)
2113                and we are talking to port 23, which might be a real
2114                telnet server that will try to keep WILL ECHO on permanently.
2115              */
2116             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2117                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2118                 unsigned char option;
2119                 oldi = i;
2120                 switch ((unsigned char) buf[++i]) {
2121                   case TN_WILL:
2122                     if (appData.debugMode)
2123                       fprintf(debugFP, "\n<WILL ");
2124                     switch (option = (unsigned char) buf[++i]) {
2125                       case TN_ECHO:
2126                         if (appData.debugMode)
2127                           fprintf(debugFP, "ECHO ");
2128                         /* Reply only if this is a change, according
2129                            to the protocol rules. */
2130                         if (remoteEchoOption) break;
2131                         if (appData.localLineEditing &&
2132                             atoi(appData.icsPort) == TN_PORT) {
2133                             TelnetRequest(TN_DONT, TN_ECHO);
2134                         } else {
2135                             EchoOff();
2136                             TelnetRequest(TN_DO, TN_ECHO);
2137                             remoteEchoOption = TRUE;
2138                         }
2139                         break;
2140                       default:
2141                         if (appData.debugMode)
2142                           fprintf(debugFP, "%d ", option);
2143                         /* Whatever this is, we don't want it. */
2144                         TelnetRequest(TN_DONT, option);
2145                         break;
2146                     }
2147                     break;
2148                   case TN_WONT:
2149                     if (appData.debugMode)
2150                       fprintf(debugFP, "\n<WONT ");
2151                     switch (option = (unsigned char) buf[++i]) {
2152                       case TN_ECHO:
2153                         if (appData.debugMode)
2154                           fprintf(debugFP, "ECHO ");
2155                         /* Reply only if this is a change, according
2156                            to the protocol rules. */
2157                         if (!remoteEchoOption) break;
2158                         EchoOn();
2159                         TelnetRequest(TN_DONT, TN_ECHO);
2160                         remoteEchoOption = FALSE;
2161                         break;
2162                       default:
2163                         if (appData.debugMode)
2164                           fprintf(debugFP, "%d ", (unsigned char) option);
2165                         /* Whatever this is, it must already be turned
2166                            off, because we never agree to turn on
2167                            anything non-default, so according to the
2168                            protocol rules, we don't reply. */
2169                         break;
2170                     }
2171                     break;
2172                   case TN_DO:
2173                     if (appData.debugMode)
2174                       fprintf(debugFP, "\n<DO ");
2175                     switch (option = (unsigned char) buf[++i]) {
2176                       default:
2177                         /* Whatever this is, we refuse to do it. */
2178                         if (appData.debugMode)
2179                           fprintf(debugFP, "%d ", option);
2180                         TelnetRequest(TN_WONT, option);
2181                         break;
2182                     }
2183                     break;
2184                   case TN_DONT:
2185                     if (appData.debugMode)
2186                       fprintf(debugFP, "\n<DONT ");
2187                     switch (option = (unsigned char) buf[++i]) {
2188                       default:
2189                         if (appData.debugMode)
2190                           fprintf(debugFP, "%d ", option);
2191                         /* Whatever this is, we are already not doing
2192                            it, because we never agree to do anything
2193                            non-default, so according to the protocol
2194                            rules, we don't reply. */
2195                         break;
2196                     }
2197                     break;
2198                   case TN_IAC:
2199                     if (appData.debugMode)
2200                       fprintf(debugFP, "\n<IAC ");
2201                     /* Doubled IAC; pass it through */
2202                     i--;
2203                     break;
2204                   default:
2205                     if (appData.debugMode)
2206                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2207                     /* Drop all other telnet commands on the floor */
2208                     break;
2209                 }
2210                 if (oldi > next_out)
2211                   SendToPlayer(&buf[next_out], oldi - next_out);
2212                 if (++i > next_out)
2213                   next_out = i;
2214                 continue;
2215             }
2216                 
2217             /* OK, this at least will *usually* work */
2218             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2219                 loggedOn = TRUE;
2220             }
2221             
2222             if (loggedOn && !intfSet) {
2223                 if (ics_type == ICS_ICC) {
2224                   sprintf(str,
2225                           "/set-quietly interface %s\n/set-quietly style 12\n",
2226                           programVersion);
2227           if (!appData.noJoin)
2228               strcat(str, "/set-quietly wrap 0\n");
2229                 } else if (ics_type == ICS_CHESSNET) {
2230                   sprintf(str, "/style 12\n");
2231                 } else {
2232                   strcpy(str, "alias $ @\n$set interface ");
2233                   strcat(str, programVersion);
2234                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2235 #ifdef WIN32
2236                   strcat(str, "$iset nohighlight 1\n");
2237 #endif
2238           if (!appData.noJoin)
2239               strcat(str, "$iset nowrap 1\n");
2240                   strcat(str, "$iset lock 1\n$style 12\n");
2241                 }
2242                 SendToICS(str);
2243                 NotifyFrontendLogin();
2244                 intfSet = TRUE;
2245             }
2246
2247             if (started == STARTED_COMMENT) {
2248                 /* Accumulate characters in comment */
2249                 parse[parse_pos++] = buf[i];
2250                 if (buf[i] == '\n') {
2251                     parse[parse_pos] = NULLCHAR;
2252                     if(chattingPartner>=0) {
2253                         char mess[MSG_SIZ];
2254                         sprintf(mess, "%s%s", talker, parse);
2255                         OutputChatMessage(chattingPartner, mess);
2256                         chattingPartner = -1;
2257                     } else
2258                     if(!suppressKibitz) // [HGM] kibitz
2259                         AppendComment(forwardMostMove, StripHighlight(parse));
2260                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2261                         int nrDigit = 0, nrAlph = 0, i;
2262                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2263                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2264                         parse[parse_pos] = NULLCHAR;
2265                         // try to be smart: if it does not look like search info, it should go to
2266                         // ICS interaction window after all, not to engine-output window.
2267                         for(i=0; i<parse_pos; i++) { // count letters and digits
2268                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2269                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2270                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2271                         }
2272                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2273                             int depth=0; float score;
2274                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2275                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2276                                 pvInfoList[forwardMostMove-1].depth = depth;
2277                                 pvInfoList[forwardMostMove-1].score = 100*score;
2278                             }
2279                             OutputKibitz(suppressKibitz, parse);
2280                         } else {
2281                             char tmp[MSG_SIZ];
2282                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2283                             SendToPlayer(tmp, strlen(tmp));
2284                         }
2285                     }
2286                     started = STARTED_NONE;
2287                 } else {
2288                     /* Don't match patterns against characters in chatter */
2289                     i++;
2290                     continue;
2291                 }
2292             }
2293             if (started == STARTED_CHATTER) {
2294                 if (buf[i] != '\n') {
2295                     /* Don't match patterns against characters in chatter */
2296                     i++;
2297                     continue;
2298                 }
2299                 started = STARTED_NONE;
2300             }
2301
2302             /* Kludge to deal with rcmd protocol */
2303             if (firstTime && looking_at(buf, &i, "\001*")) {
2304                 DisplayFatalError(&buf[1], 0, 1);
2305                 continue;
2306             } else {
2307                 firstTime = FALSE;
2308             }
2309
2310             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2311                 ics_type = ICS_ICC;
2312                 ics_prefix = "/";
2313                 if (appData.debugMode)
2314                   fprintf(debugFP, "ics_type %d\n", ics_type);
2315                 continue;
2316             }
2317             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2318                 ics_type = ICS_FICS;
2319                 ics_prefix = "$";
2320                 if (appData.debugMode)
2321                   fprintf(debugFP, "ics_type %d\n", ics_type);
2322                 continue;
2323             }
2324             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2325                 ics_type = ICS_CHESSNET;
2326                 ics_prefix = "/";
2327                 if (appData.debugMode)
2328                   fprintf(debugFP, "ics_type %d\n", ics_type);
2329                 continue;
2330             }
2331
2332             if (!loggedOn &&
2333                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2334                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2335                  looking_at(buf, &i, "will be \"*\""))) {
2336               strcpy(ics_handle, star_match[0]);
2337               continue;
2338             }
2339
2340             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2341               char buf[MSG_SIZ];
2342               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2343               DisplayIcsInteractionTitle(buf);
2344               have_set_title = TRUE;
2345             }
2346
2347             /* skip finger notes */
2348             if (started == STARTED_NONE &&
2349                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2350                  (buf[i] == '1' && buf[i+1] == '0')) &&
2351                 buf[i+2] == ':' && buf[i+3] == ' ') {
2352               started = STARTED_CHATTER;
2353               i += 3;
2354               continue;
2355             }
2356
2357             /* skip formula vars */
2358             if (started == STARTED_NONE &&
2359                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2360               started = STARTED_CHATTER;
2361               i += 3;
2362               continue;
2363             }
2364
2365             oldi = i;
2366             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2367             if (appData.autoKibitz && started == STARTED_NONE && 
2368                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2369                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2370                 if(looking_at(buf, &i, "* kibitzes: ") &&
2371                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2372                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2373                         suppressKibitz = TRUE;
2374                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2375                                 && (gameMode == IcsPlayingWhite)) ||
2376                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2377                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2378                             started = STARTED_CHATTER; // own kibitz we simply discard
2379                         else {
2380                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2381                             parse_pos = 0; parse[0] = NULLCHAR;
2382                             savingComment = TRUE;
2383                             suppressKibitz = gameMode != IcsObserving ? 2 :
2384                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2385                         } 
2386                         continue;
2387                 } else
2388                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2389                     started = STARTED_CHATTER;
2390                     suppressKibitz = TRUE;
2391                 }
2392             } // [HGM] kibitz: end of patch
2393
2394 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2395
2396             // [HGM] chat: intercept tells by users for which we have an open chat window
2397             channel = -1;
2398             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2399                                            looking_at(buf, &i, "* whispers:") ||
2400                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2401                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2402                 int p;
2403                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2404                 chattingPartner = -1;
2405
2406                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2407                 for(p=0; p<MAX_CHAT; p++) {
2408                     if(channel == atoi(chatPartner[p])) {
2409                     talker[0] = '['; strcat(talker, "]");
2410                     chattingPartner = p; break;
2411                     }
2412                 } else
2413                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2414                 for(p=0; p<MAX_CHAT; p++) {
2415                     if(!strcmp("WHISPER", chatPartner[p])) {
2416                         talker[0] = '['; strcat(talker, "]");
2417                         chattingPartner = p; break;
2418                     }
2419                 }
2420                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2421                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2422                     talker[0] = 0;
2423                     chattingPartner = p; break;
2424                 }
2425                 if(chattingPartner<0) i = oldi; else {
2426                     started = STARTED_COMMENT;
2427                     parse_pos = 0; parse[0] = NULLCHAR;
2428                     savingComment = TRUE;
2429                     suppressKibitz = TRUE;
2430                 }
2431             } // [HGM] chat: end of patch
2432
2433             if (appData.zippyTalk || appData.zippyPlay) {
2434                 /* [DM] Backup address for color zippy lines */
2435                 backup = i;
2436 #if ZIPPY
2437        #ifdef WIN32
2438                if (loggedOn == TRUE)
2439                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2440                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2441        #else
2442                 if (ZippyControl(buf, &i) ||
2443                     ZippyConverse(buf, &i) ||
2444                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2445                       loggedOn = TRUE;
2446                       if (!appData.colorize) continue;
2447                 }
2448        #endif
2449 #endif
2450             } // [DM] 'else { ' deleted
2451                 if (
2452                     /* Regular tells and says */
2453                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2454                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2455                     looking_at(buf, &i, "* says: ") ||
2456                     /* Don't color "message" or "messages" output */
2457                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2458                     looking_at(buf, &i, "*. * at *:*: ") ||
2459                     looking_at(buf, &i, "--* (*:*): ") ||
2460                     /* Message notifications (same color as tells) */
2461                     looking_at(buf, &i, "* has left a message ") ||
2462                     looking_at(buf, &i, "* just sent you a message:\n") ||
2463                     /* Whispers and kibitzes */
2464                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2465                     looking_at(buf, &i, "* kibitzes: ") ||
2466                     /* Channel tells */
2467                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2468
2469                   if (tkind == 1 && strchr(star_match[0], ':')) {
2470                       /* Avoid "tells you:" spoofs in channels */
2471                      tkind = 3;
2472                   }
2473                   if (star_match[0][0] == NULLCHAR ||
2474                       strchr(star_match[0], ' ') ||
2475                       (tkind == 3 && strchr(star_match[1], ' '))) {
2476                     /* Reject bogus matches */
2477                     i = oldi;
2478                   } else {
2479                     if (appData.colorize) {
2480                       if (oldi > next_out) {
2481                         SendToPlayer(&buf[next_out], oldi - next_out);
2482                         next_out = oldi;
2483                       }
2484                       switch (tkind) {
2485                       case 1:
2486                         Colorize(ColorTell, FALSE);
2487                         curColor = ColorTell;
2488                         break;
2489                       case 2:
2490                         Colorize(ColorKibitz, FALSE);
2491                         curColor = ColorKibitz;
2492                         break;
2493                       case 3:
2494                         p = strrchr(star_match[1], '(');
2495                         if (p == NULL) {
2496                           p = star_match[1];
2497                         } else {
2498                           p++;
2499                         }
2500                         if (atoi(p) == 1) {
2501                           Colorize(ColorChannel1, FALSE);
2502                           curColor = ColorChannel1;
2503                         } else {
2504                           Colorize(ColorChannel, FALSE);
2505                           curColor = ColorChannel;
2506                         }
2507                         break;
2508                       case 5:
2509                         curColor = ColorNormal;
2510                         break;
2511                       }
2512                     }
2513                     if (started == STARTED_NONE && appData.autoComment &&
2514                         (gameMode == IcsObserving ||
2515                          gameMode == IcsPlayingWhite ||
2516                          gameMode == IcsPlayingBlack)) {
2517                       parse_pos = i - oldi;
2518                       memcpy(parse, &buf[oldi], parse_pos);
2519                       parse[parse_pos] = NULLCHAR;
2520                       started = STARTED_COMMENT;
2521                       savingComment = TRUE;
2522                     } else {
2523                       started = STARTED_CHATTER;
2524                       savingComment = FALSE;
2525                     }
2526                     loggedOn = TRUE;
2527                     continue;
2528                   }
2529                 }
2530
2531                 if (looking_at(buf, &i, "* s-shouts: ") ||
2532                     looking_at(buf, &i, "* c-shouts: ")) {
2533                     if (appData.colorize) {
2534                         if (oldi > next_out) {
2535                             SendToPlayer(&buf[next_out], oldi - next_out);
2536                             next_out = oldi;
2537                         }
2538                         Colorize(ColorSShout, FALSE);
2539                         curColor = ColorSShout;
2540                     }
2541                     loggedOn = TRUE;
2542                     started = STARTED_CHATTER;
2543                     continue;
2544                 }
2545
2546                 if (looking_at(buf, &i, "--->")) {
2547                     loggedOn = TRUE;
2548                     continue;
2549                 }
2550
2551                 if (looking_at(buf, &i, "* shouts: ") ||
2552                     looking_at(buf, &i, "--> ")) {
2553                     if (appData.colorize) {
2554                         if (oldi > next_out) {
2555                             SendToPlayer(&buf[next_out], oldi - next_out);
2556                             next_out = oldi;
2557                         }
2558                         Colorize(ColorShout, FALSE);
2559                         curColor = ColorShout;
2560                     }
2561                     loggedOn = TRUE;
2562                     started = STARTED_CHATTER;
2563                     continue;
2564                 }
2565
2566                 if (looking_at( buf, &i, "Challenge:")) {
2567                     if (appData.colorize) {
2568                         if (oldi > next_out) {
2569                             SendToPlayer(&buf[next_out], oldi - next_out);
2570                             next_out = oldi;
2571                         }
2572                         Colorize(ColorChallenge, FALSE);
2573                         curColor = ColorChallenge;
2574                     }
2575                     loggedOn = TRUE;
2576                     continue;
2577                 }
2578
2579                 if (looking_at(buf, &i, "* offers you") ||
2580                     looking_at(buf, &i, "* offers to be") ||
2581                     looking_at(buf, &i, "* would like to") ||
2582                     looking_at(buf, &i, "* requests to") ||
2583                     looking_at(buf, &i, "Your opponent offers") ||
2584                     looking_at(buf, &i, "Your opponent requests")) {
2585
2586                     if (appData.colorize) {
2587                         if (oldi > next_out) {
2588                             SendToPlayer(&buf[next_out], oldi - next_out);
2589                             next_out = oldi;
2590                         }
2591                         Colorize(ColorRequest, FALSE);
2592                         curColor = ColorRequest;
2593                     }
2594                     continue;
2595                 }
2596
2597                 if (looking_at(buf, &i, "* (*) seeking")) {
2598                     if (appData.colorize) {
2599                         if (oldi > next_out) {
2600                             SendToPlayer(&buf[next_out], oldi - next_out);
2601                             next_out = oldi;
2602                         }
2603                         Colorize(ColorSeek, FALSE);
2604                         curColor = ColorSeek;
2605                     }
2606                     continue;
2607             }
2608
2609             if (looking_at(buf, &i, "\\   ")) {
2610                 if (prevColor != ColorNormal) {
2611                     if (oldi > next_out) {
2612                         SendToPlayer(&buf[next_out], oldi - next_out);
2613                         next_out = oldi;
2614                     }
2615                     Colorize(prevColor, TRUE);
2616                     curColor = prevColor;
2617                 }
2618                 if (savingComment) {
2619                     parse_pos = i - oldi;
2620                     memcpy(parse, &buf[oldi], parse_pos);
2621                     parse[parse_pos] = NULLCHAR;
2622                     started = STARTED_COMMENT;
2623                 } else {
2624                     started = STARTED_CHATTER;
2625                 }
2626                 continue;
2627             }
2628
2629             if (looking_at(buf, &i, "Black Strength :") ||
2630                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2631                 looking_at(buf, &i, "<10>") ||
2632                 looking_at(buf, &i, "#@#")) {
2633                 /* Wrong board style */
2634                 loggedOn = TRUE;
2635                 SendToICS(ics_prefix);
2636                 SendToICS("set style 12\n");
2637                 SendToICS(ics_prefix);
2638                 SendToICS("refresh\n");
2639                 continue;
2640             }
2641             
2642             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2643                 ICSInitScript();
2644                 have_sent_ICS_logon = 1;
2645                 continue;
2646             }
2647               
2648             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2649                 (looking_at(buf, &i, "\n<12> ") ||
2650                  looking_at(buf, &i, "<12> "))) {
2651                 loggedOn = TRUE;
2652                 if (oldi > next_out) {
2653                     SendToPlayer(&buf[next_out], oldi - next_out);
2654                 }
2655                 next_out = i;
2656                 started = STARTED_BOARD;
2657                 parse_pos = 0;
2658                 continue;
2659             }
2660
2661             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2662                 looking_at(buf, &i, "<b1> ")) {
2663                 if (oldi > next_out) {
2664                     SendToPlayer(&buf[next_out], oldi - next_out);
2665                 }
2666                 next_out = i;
2667                 started = STARTED_HOLDINGS;
2668                 parse_pos = 0;
2669                 continue;
2670             }
2671
2672             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2673                 loggedOn = TRUE;
2674                 /* Header for a move list -- first line */
2675
2676                 switch (ics_getting_history) {
2677                   case H_FALSE:
2678                     switch (gameMode) {
2679                       case IcsIdle:
2680                       case BeginningOfGame:
2681                         /* User typed "moves" or "oldmoves" while we
2682                            were idle.  Pretend we asked for these
2683                            moves and soak them up so user can step
2684                            through them and/or save them.
2685                            */
2686                         Reset(FALSE, TRUE);
2687                         gameMode = IcsObserving;
2688                         ModeHighlight();
2689                         ics_gamenum = -1;
2690                         ics_getting_history = H_GOT_UNREQ_HEADER;
2691                         break;
2692                       case EditGame: /*?*/
2693                       case EditPosition: /*?*/
2694                         /* Should above feature work in these modes too? */
2695                         /* For now it doesn't */
2696                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2697                         break;
2698                       default:
2699                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2700                         break;
2701                     }
2702                     break;
2703                   case H_REQUESTED:
2704                     /* Is this the right one? */
2705                     if (gameInfo.white && gameInfo.black &&
2706                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2707                         strcmp(gameInfo.black, star_match[2]) == 0) {
2708                         /* All is well */
2709                         ics_getting_history = H_GOT_REQ_HEADER;
2710                     }
2711                     break;
2712                   case H_GOT_REQ_HEADER:
2713                   case H_GOT_UNREQ_HEADER:
2714                   case H_GOT_UNWANTED_HEADER:
2715                   case H_GETTING_MOVES:
2716                     /* Should not happen */
2717                     DisplayError(_("Error gathering move list: two headers"), 0);
2718                     ics_getting_history = H_FALSE;
2719                     break;
2720                 }
2721
2722                 /* Save player ratings into gameInfo if needed */
2723                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2724                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2725                     (gameInfo.whiteRating == -1 ||
2726                      gameInfo.blackRating == -1)) {
2727
2728                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2729                     gameInfo.blackRating = string_to_rating(star_match[3]);
2730                     if (appData.debugMode)
2731                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2732                               gameInfo.whiteRating, gameInfo.blackRating);
2733                 }
2734                 continue;
2735             }
2736
2737             if (looking_at(buf, &i,
2738               "* * match, initial time: * minute*, increment: * second")) {
2739                 /* Header for a move list -- second line */
2740                 /* Initial board will follow if this is a wild game */
2741                 if (gameInfo.event != NULL) free(gameInfo.event);
2742                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2743                 gameInfo.event = StrSave(str);
2744                 /* [HGM] we switched variant. Translate boards if needed. */
2745                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2746                 continue;
2747             }
2748
2749             if (looking_at(buf, &i, "Move  ")) {
2750                 /* Beginning of a move list */
2751                 switch (ics_getting_history) {
2752                   case H_FALSE:
2753                     /* Normally should not happen */
2754                     /* Maybe user hit reset while we were parsing */
2755                     break;
2756                   case H_REQUESTED:
2757                     /* Happens if we are ignoring a move list that is not
2758                      * the one we just requested.  Common if the user
2759                      * tries to observe two games without turning off
2760                      * getMoveList */
2761                     break;
2762                   case H_GETTING_MOVES:
2763                     /* Should not happen */
2764                     DisplayError(_("Error gathering move list: nested"), 0);
2765                     ics_getting_history = H_FALSE;
2766                     break;
2767                   case H_GOT_REQ_HEADER:
2768                     ics_getting_history = H_GETTING_MOVES;
2769                     started = STARTED_MOVES;
2770                     parse_pos = 0;
2771                     if (oldi > next_out) {
2772                         SendToPlayer(&buf[next_out], oldi - next_out);
2773                     }
2774                     break;
2775                   case H_GOT_UNREQ_HEADER:
2776                     ics_getting_history = H_GETTING_MOVES;
2777                     started = STARTED_MOVES_NOHIDE;
2778                     parse_pos = 0;
2779                     break;
2780                   case H_GOT_UNWANTED_HEADER:
2781                     ics_getting_history = H_FALSE;
2782                     break;
2783                 }
2784                 continue;
2785             }                           
2786             
2787             if (looking_at(buf, &i, "% ") ||
2788                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2789                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2790                 savingComment = FALSE;
2791                 switch (started) {
2792                   case STARTED_MOVES:
2793                   case STARTED_MOVES_NOHIDE:
2794                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2795                     parse[parse_pos + i - oldi] = NULLCHAR;
2796                     ParseGameHistory(parse);
2797 #if ZIPPY
2798                     if (appData.zippyPlay && first.initDone) {
2799                         FeedMovesToProgram(&first, forwardMostMove);
2800                         if (gameMode == IcsPlayingWhite) {
2801                             if (WhiteOnMove(forwardMostMove)) {
2802                                 if (first.sendTime) {
2803                                   if (first.useColors) {
2804                                     SendToProgram("black\n", &first); 
2805                                   }
2806                                   SendTimeRemaining(&first, TRUE);
2807                                 }
2808                                 if (first.useColors) {
2809                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2810                                 }
2811                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2812                                 first.maybeThinking = TRUE;
2813                             } else {
2814                                 if (first.usePlayother) {
2815                                   if (first.sendTime) {
2816                                     SendTimeRemaining(&first, TRUE);
2817                                   }
2818                                   SendToProgram("playother\n", &first);
2819                                   firstMove = FALSE;
2820                                 } else {
2821                                   firstMove = TRUE;
2822                                 }
2823                             }
2824                         } else if (gameMode == IcsPlayingBlack) {
2825                             if (!WhiteOnMove(forwardMostMove)) {
2826                                 if (first.sendTime) {
2827                                   if (first.useColors) {
2828                                     SendToProgram("white\n", &first);
2829                                   }
2830                                   SendTimeRemaining(&first, FALSE);
2831                                 }
2832                                 if (first.useColors) {
2833                                   SendToProgram("black\n", &first);
2834                                 }
2835                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2836                                 first.maybeThinking = TRUE;
2837                             } else {
2838                                 if (first.usePlayother) {
2839                                   if (first.sendTime) {
2840                                     SendTimeRemaining(&first, FALSE);
2841                                   }
2842                                   SendToProgram("playother\n", &first);
2843                                   firstMove = FALSE;
2844                                 } else {
2845                                   firstMove = TRUE;
2846                                 }
2847                             }
2848                         }                       
2849                     }
2850 #endif
2851                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2852                         /* Moves came from oldmoves or moves command
2853                            while we weren't doing anything else.
2854                            */
2855                         currentMove = forwardMostMove;
2856                         ClearHighlights();/*!!could figure this out*/
2857                         flipView = appData.flipView;
2858                         DrawPosition(FALSE, boards[currentMove]);
2859                         DisplayBothClocks();
2860                         sprintf(str, "%s vs. %s",
2861                                 gameInfo.white, gameInfo.black);
2862                         DisplayTitle(str);
2863                         gameMode = IcsIdle;
2864                     } else {
2865                         /* Moves were history of an active game */
2866                         if (gameInfo.resultDetails != NULL) {
2867                             free(gameInfo.resultDetails);
2868                             gameInfo.resultDetails = NULL;
2869                         }
2870                     }
2871                     HistorySet(parseList, backwardMostMove,
2872                                forwardMostMove, currentMove-1);
2873                     DisplayMove(currentMove - 1);
2874                     if (started == STARTED_MOVES) next_out = i;
2875                     started = STARTED_NONE;
2876                     ics_getting_history = H_FALSE;
2877                     break;
2878
2879                   case STARTED_OBSERVE:
2880                     started = STARTED_NONE;
2881                     SendToICS(ics_prefix);
2882                     SendToICS("refresh\n");
2883                     break;
2884
2885                   default:
2886                     break;
2887                 }
2888                 if(bookHit) { // [HGM] book: simulate book reply
2889                     static char bookMove[MSG_SIZ]; // a bit generous?
2890
2891                     programStats.nodes = programStats.depth = programStats.time = 
2892                     programStats.score = programStats.got_only_move = 0;
2893                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2894
2895                     strcpy(bookMove, "move ");
2896                     strcat(bookMove, bookHit);
2897                     HandleMachineMove(bookMove, &first);
2898                 }
2899                 continue;
2900             }
2901             
2902             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2903                  started == STARTED_HOLDINGS ||
2904                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2905                 /* Accumulate characters in move list or board */
2906                 parse[parse_pos++] = buf[i];
2907             }
2908             
2909             /* Start of game messages.  Mostly we detect start of game
2910                when the first board image arrives.  On some versions
2911                of the ICS, though, we need to do a "refresh" after starting
2912                to observe in order to get the current board right away. */
2913             if (looking_at(buf, &i, "Adding game * to observation list")) {
2914                 started = STARTED_OBSERVE;
2915                 continue;
2916             }
2917
2918             /* Handle auto-observe */
2919             if (appData.autoObserve &&
2920                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2921                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2922                 char *player;
2923                 /* Choose the player that was highlighted, if any. */
2924                 if (star_match[0][0] == '\033' ||
2925                     star_match[1][0] != '\033') {
2926                     player = star_match[0];
2927                 } else {
2928                     player = star_match[2];
2929                 }
2930                 sprintf(str, "%sobserve %s\n",
2931                         ics_prefix, StripHighlightAndTitle(player));
2932                 SendToICS(str);
2933
2934                 /* Save ratings from notify string */
2935                 strcpy(player1Name, star_match[0]);
2936                 player1Rating = string_to_rating(star_match[1]);
2937                 strcpy(player2Name, star_match[2]);
2938                 player2Rating = string_to_rating(star_match[3]);
2939
2940                 if (appData.debugMode)
2941                   fprintf(debugFP, 
2942                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2943                           player1Name, player1Rating,
2944                           player2Name, player2Rating);
2945
2946                 continue;
2947             }
2948
2949             /* Deal with automatic examine mode after a game,
2950                and with IcsObserving -> IcsExamining transition */
2951             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2952                 looking_at(buf, &i, "has made you an examiner of game *")) {
2953
2954                 int gamenum = atoi(star_match[0]);
2955                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2956                     gamenum == ics_gamenum) {
2957                     /* We were already playing or observing this game;
2958                        no need to refetch history */
2959                     gameMode = IcsExamining;
2960                     if (pausing) {
2961                         pauseExamForwardMostMove = forwardMostMove;
2962                     } else if (currentMove < forwardMostMove) {
2963                         ForwardInner(forwardMostMove);
2964                     }
2965                 } else {
2966                     /* I don't think this case really can happen */
2967                     SendToICS(ics_prefix);
2968                     SendToICS("refresh\n");
2969                 }
2970                 continue;
2971             }    
2972             
2973             /* Error messages */
2974 //          if (ics_user_moved) {
2975             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2976                 if (looking_at(buf, &i, "Illegal move") ||
2977                     looking_at(buf, &i, "Not a legal move") ||
2978                     looking_at(buf, &i, "Your king is in check") ||
2979                     looking_at(buf, &i, "It isn't your turn") ||
2980                     looking_at(buf, &i, "It is not your move")) {
2981                     /* Illegal move */
2982                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2983                         currentMove = --forwardMostMove;
2984                         DisplayMove(currentMove - 1); /* before DMError */
2985                         DrawPosition(FALSE, boards[currentMove]);
2986                         SwitchClocks();
2987                         DisplayBothClocks();
2988                     }
2989                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2990                     ics_user_moved = 0;
2991                     continue;
2992                 }
2993             }
2994
2995             if (looking_at(buf, &i, "still have time") ||
2996                 looking_at(buf, &i, "not out of time") ||
2997                 looking_at(buf, &i, "either player is out of time") ||
2998                 looking_at(buf, &i, "has timeseal; checking")) {
2999                 /* We must have called his flag a little too soon */
3000                 whiteFlag = blackFlag = FALSE;
3001                 continue;
3002             }
3003
3004             if (looking_at(buf, &i, "added * seconds to") ||
3005                 looking_at(buf, &i, "seconds were added to")) {
3006                 /* Update the clocks */
3007                 SendToICS(ics_prefix);
3008                 SendToICS("refresh\n");
3009                 continue;
3010             }
3011
3012             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3013                 ics_clock_paused = TRUE;
3014                 StopClocks();
3015                 continue;
3016             }
3017
3018             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3019                 ics_clock_paused = FALSE;
3020                 StartClocks();
3021                 continue;
3022             }
3023
3024             /* Grab player ratings from the Creating: message.
3025                Note we have to check for the special case when
3026                the ICS inserts things like [white] or [black]. */
3027             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3028                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3029                 /* star_matches:
3030                    0    player 1 name (not necessarily white)
3031                    1    player 1 rating
3032                    2    empty, white, or black (IGNORED)
3033                    3    player 2 name (not necessarily black)
3034                    4    player 2 rating
3035                    
3036                    The names/ratings are sorted out when the game
3037                    actually starts (below).
3038                 */
3039                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3040                 player1Rating = string_to_rating(star_match[1]);
3041                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3042                 player2Rating = string_to_rating(star_match[4]);
3043
3044                 if (appData.debugMode)
3045                   fprintf(debugFP, 
3046                           "Ratings from 'Creating:' %s %d, %s %d\n",
3047                           player1Name, player1Rating,
3048                           player2Name, player2Rating);
3049
3050                 continue;
3051             }
3052             
3053             /* Improved generic start/end-of-game messages */
3054             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3055                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3056                 /* If tkind == 0: */
3057                 /* star_match[0] is the game number */
3058                 /*           [1] is the white player's name */
3059                 /*           [2] is the black player's name */
3060                 /* For end-of-game: */
3061                 /*           [3] is the reason for the game end */
3062                 /*           [4] is a PGN end game-token, preceded by " " */
3063                 /* For start-of-game: */
3064                 /*           [3] begins with "Creating" or "Continuing" */
3065                 /*           [4] is " *" or empty (don't care). */
3066                 int gamenum = atoi(star_match[0]);
3067                 char *whitename, *blackname, *why, *endtoken;
3068                 ChessMove endtype = (ChessMove) 0;
3069
3070                 if (tkind == 0) {
3071                   whitename = star_match[1];
3072                   blackname = star_match[2];
3073                   why = star_match[3];
3074                   endtoken = star_match[4];
3075                 } else {
3076                   whitename = star_match[1];
3077                   blackname = star_match[3];
3078                   why = star_match[5];
3079                   endtoken = star_match[6];
3080                 }
3081
3082                 /* Game start messages */
3083                 if (strncmp(why, "Creating ", 9) == 0 ||
3084                     strncmp(why, "Continuing ", 11) == 0) {
3085                     gs_gamenum = gamenum;
3086                     strcpy(gs_kind, strchr(why, ' ') + 1);
3087 #if ZIPPY
3088                     if (appData.zippyPlay) {
3089                         ZippyGameStart(whitename, blackname);
3090                     }
3091 #endif /*ZIPPY*/
3092                     continue;
3093                 }
3094
3095                 /* Game end messages */
3096                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3097                     ics_gamenum != gamenum) {
3098                     continue;
3099                 }
3100                 while (endtoken[0] == ' ') endtoken++;
3101                 switch (endtoken[0]) {
3102                   case '*':
3103                   default:
3104                     endtype = GameUnfinished;
3105                     break;
3106                   case '0':
3107                     endtype = BlackWins;
3108                     break;
3109                   case '1':
3110                     if (endtoken[1] == '/')
3111                       endtype = GameIsDrawn;
3112                     else
3113                       endtype = WhiteWins;
3114                     break;
3115                 }
3116                 GameEnds(endtype, why, GE_ICS);
3117 #if ZIPPY
3118                 if (appData.zippyPlay && first.initDone) {
3119                     ZippyGameEnd(endtype, why);
3120                     if (first.pr == NULL) {
3121                       /* Start the next process early so that we'll
3122                          be ready for the next challenge */
3123                       StartChessProgram(&first);
3124                     }
3125                     /* Send "new" early, in case this command takes
3126                        a long time to finish, so that we'll be ready
3127                        for the next challenge. */
3128                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3129                     Reset(TRUE, TRUE);
3130                 }
3131 #endif /*ZIPPY*/
3132                 continue;
3133             }
3134
3135             if (looking_at(buf, &i, "Removing game * from observation") ||
3136                 looking_at(buf, &i, "no longer observing game *") ||
3137                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3138                 if (gameMode == IcsObserving &&
3139                     atoi(star_match[0]) == ics_gamenum)
3140                   {
3141                       /* icsEngineAnalyze */
3142                       if (appData.icsEngineAnalyze) {
3143                             ExitAnalyzeMode();
3144                             ModeHighlight();
3145                       }
3146                       StopClocks();
3147                       gameMode = IcsIdle;
3148                       ics_gamenum = -1;
3149                       ics_user_moved = FALSE;
3150                   }
3151                 continue;
3152             }
3153
3154             if (looking_at(buf, &i, "no longer examining game *")) {
3155                 if (gameMode == IcsExamining &&
3156                     atoi(star_match[0]) == ics_gamenum)
3157                   {
3158                       gameMode = IcsIdle;
3159                       ics_gamenum = -1;
3160                       ics_user_moved = FALSE;
3161                   }
3162                 continue;
3163             }
3164
3165             /* Advance leftover_start past any newlines we find,
3166                so only partial lines can get reparsed */
3167             if (looking_at(buf, &i, "\n")) {
3168                 prevColor = curColor;
3169                 if (curColor != ColorNormal) {
3170                     if (oldi > next_out) {
3171                         SendToPlayer(&buf[next_out], oldi - next_out);
3172                         next_out = oldi;
3173                     }
3174                     Colorize(ColorNormal, FALSE);
3175                     curColor = ColorNormal;
3176                 }
3177                 if (started == STARTED_BOARD) {
3178                     started = STARTED_NONE;
3179                     parse[parse_pos] = NULLCHAR;
3180                     ParseBoard12(parse);
3181                     ics_user_moved = 0;
3182
3183                     /* Send premove here */
3184                     if (appData.premove) {
3185                       char str[MSG_SIZ];
3186                       if (currentMove == 0 &&
3187                           gameMode == IcsPlayingWhite &&
3188                           appData.premoveWhite) {
3189                         sprintf(str, "%s%s\n", ics_prefix,
3190                                 appData.premoveWhiteText);
3191                         if (appData.debugMode)
3192                           fprintf(debugFP, "Sending premove:\n");
3193                         SendToICS(str);
3194                       } else if (currentMove == 1 &&
3195                                  gameMode == IcsPlayingBlack &&
3196                                  appData.premoveBlack) {
3197                         sprintf(str, "%s%s\n", ics_prefix,
3198                                 appData.premoveBlackText);
3199                         if (appData.debugMode)
3200                           fprintf(debugFP, "Sending premove:\n");
3201                         SendToICS(str);
3202                       } else if (gotPremove) {
3203                         gotPremove = 0;
3204                         ClearPremoveHighlights();
3205                         if (appData.debugMode)
3206                           fprintf(debugFP, "Sending premove:\n");
3207                           UserMoveEvent(premoveFromX, premoveFromY, 
3208                                         premoveToX, premoveToY, 
3209                                         premovePromoChar);
3210                       }
3211                     }
3212
3213                     /* Usually suppress following prompt */
3214                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3215                         if (looking_at(buf, &i, "*% ")) {
3216                             savingComment = FALSE;
3217                         }
3218                     }
3219                     next_out = i;
3220                 } else if (started == STARTED_HOLDINGS) {
3221                     int gamenum;
3222                     char new_piece[MSG_SIZ];
3223                     started = STARTED_NONE;
3224                     parse[parse_pos] = NULLCHAR;
3225                     if (appData.debugMode)
3226                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3227                                                         parse, currentMove);
3228                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3229                         gamenum == ics_gamenum) {
3230                         if (gameInfo.variant == VariantNormal) {
3231                           /* [HGM] We seem to switch variant during a game!
3232                            * Presumably no holdings were displayed, so we have
3233                            * to move the position two files to the right to
3234                            * create room for them!
3235                            */
3236                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3237                           /* Get a move list just to see the header, which
3238                              will tell us whether this is really bug or zh */
3239                           if (ics_getting_history == H_FALSE) {
3240                             ics_getting_history = H_REQUESTED;
3241                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3242                             SendToICS(str);
3243                           }
3244                         }
3245                         new_piece[0] = NULLCHAR;
3246                         sscanf(parse, "game %d white [%s black [%s <- %s",
3247                                &gamenum, white_holding, black_holding,
3248                                new_piece);
3249                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3250                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3251                         /* [HGM] copy holdings to board holdings area */
3252                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3253                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3254 #if ZIPPY
3255                         if (appData.zippyPlay && first.initDone) {
3256                             ZippyHoldings(white_holding, black_holding,
3257                                           new_piece);
3258                         }
3259 #endif /*ZIPPY*/
3260                         if (tinyLayout || smallLayout) {
3261                             char wh[16], bh[16];
3262                             PackHolding(wh, white_holding);
3263                             PackHolding(bh, black_holding);
3264                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3265                                     gameInfo.white, gameInfo.black);
3266                         } else {
3267                             sprintf(str, "%s [%s] vs. %s [%s]",
3268                                     gameInfo.white, white_holding,
3269                                     gameInfo.black, black_holding);
3270                         }
3271
3272                         DrawPosition(FALSE, boards[currentMove]);
3273                         DisplayTitle(str);
3274                     }
3275                     /* Suppress following prompt */
3276                     if (looking_at(buf, &i, "*% ")) {
3277                         savingComment = FALSE;
3278                     }
3279                     next_out = i;
3280                 }
3281                 continue;
3282             }
3283
3284             i++;                /* skip unparsed character and loop back */
3285         }
3286         
3287         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3288             started != STARTED_HOLDINGS && i > next_out) {
3289             SendToPlayer(&buf[next_out], i - next_out);
3290             next_out = i;
3291         }
3292         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3293         
3294         leftover_len = buf_len - leftover_start;
3295         /* if buffer ends with something we couldn't parse,
3296            reparse it after appending the next read */
3297         
3298     } else if (count == 0) {
3299         RemoveInputSource(isr);
3300         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3301     } else {
3302         DisplayFatalError(_("Error reading from ICS"), error, 1);
3303     }
3304 }
3305
3306
3307 /* Board style 12 looks like this:
3308    
3309    <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
3310    
3311  * The "<12> " is stripped before it gets to this routine.  The two
3312  * trailing 0's (flip state and clock ticking) are later addition, and
3313  * some chess servers may not have them, or may have only the first.
3314  * Additional trailing fields may be added in the future.  
3315  */
3316
3317 #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"
3318
3319 #define RELATION_OBSERVING_PLAYED    0
3320 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3321 #define RELATION_PLAYING_MYMOVE      1
3322 #define RELATION_PLAYING_NOTMYMOVE  -1
3323 #define RELATION_EXAMINING           2
3324 #define RELATION_ISOLATED_BOARD     -3
3325 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3326
3327 void
3328 ParseBoard12(string)
3329      char *string;
3330
3331     GameMode newGameMode;
3332     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3333     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3334     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3335     char to_play, board_chars[200];
3336     char move_str[500], str[500], elapsed_time[500];
3337     char black[32], white[32];
3338     Board board;
3339     int prevMove = currentMove;
3340     int ticking = 2;
3341     ChessMove moveType;
3342     int fromX, fromY, toX, toY;
3343     char promoChar;
3344     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3345     char *bookHit = NULL; // [HGM] book
3346
3347     fromX = fromY = toX = toY = -1;
3348     
3349     newGame = FALSE;
3350
3351     if (appData.debugMode)
3352       fprintf(debugFP, _("Parsing board: %s\n"), string);
3353
3354     move_str[0] = NULLCHAR;
3355     elapsed_time[0] = NULLCHAR;
3356     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3357         int  i = 0, j;
3358         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3359             if(string[i] == ' ') { ranks++; files = 0; }
3360             else files++;
3361             i++;
3362         }
3363         for(j = 0; j <i; j++) board_chars[j] = string[j];
3364         board_chars[i] = '\0';
3365         string += i + 1;
3366     }
3367     n = sscanf(string, PATTERN, &to_play, &double_push,
3368                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3369                &gamenum, white, black, &relation, &basetime, &increment,
3370                &white_stren, &black_stren, &white_time, &black_time,
3371                &moveNum, str, elapsed_time, move_str, &ics_flip,
3372                &ticking);
3373
3374     if (n < 21) {
3375         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3376         DisplayError(str, 0);
3377         return;
3378     }
3379
3380     /* Convert the move number to internal form */
3381     moveNum = (moveNum - 1) * 2;
3382     if (to_play == 'B') moveNum++;
3383     if (moveNum >= MAX_MOVES) {
3384       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3385                         0, 1);
3386       return;
3387     }
3388     
3389     switch (relation) {
3390       case RELATION_OBSERVING_PLAYED:
3391       case RELATION_OBSERVING_STATIC:
3392         if (gamenum == -1) {
3393             /* Old ICC buglet */
3394             relation = RELATION_OBSERVING_STATIC;
3395         }
3396         newGameMode = IcsObserving;
3397         break;
3398       case RELATION_PLAYING_MYMOVE:
3399       case RELATION_PLAYING_NOTMYMOVE:
3400         newGameMode =
3401           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3402             IcsPlayingWhite : IcsPlayingBlack;
3403         break;
3404       case RELATION_EXAMINING:
3405         newGameMode = IcsExamining;
3406         break;
3407       case RELATION_ISOLATED_BOARD:
3408       default:
3409         /* Just display this board.  If user was doing something else,
3410            we will forget about it until the next board comes. */ 
3411         newGameMode = IcsIdle;
3412         break;
3413       case RELATION_STARTING_POSITION:
3414         newGameMode = gameMode;
3415         break;
3416     }
3417     
3418     /* Modify behavior for initial board display on move listing
3419        of wild games.
3420        */
3421     switch (ics_getting_history) {
3422       case H_FALSE:
3423       case H_REQUESTED:
3424         break;
3425       case H_GOT_REQ_HEADER:
3426       case H_GOT_UNREQ_HEADER:
3427         /* This is the initial position of the current game */
3428         gamenum = ics_gamenum;
3429         moveNum = 0;            /* old ICS bug workaround */
3430         if (to_play == 'B') {
3431           startedFromSetupPosition = TRUE;
3432           blackPlaysFirst = TRUE;
3433           moveNum = 1;
3434           if (forwardMostMove == 0) forwardMostMove = 1;
3435           if (backwardMostMove == 0) backwardMostMove = 1;
3436           if (currentMove == 0) currentMove = 1;
3437         }
3438         newGameMode = gameMode;
3439         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3440         break;
3441       case H_GOT_UNWANTED_HEADER:
3442         /* This is an initial board that we don't want */
3443         return;
3444       case H_GETTING_MOVES:
3445         /* Should not happen */
3446         DisplayError(_("Error gathering move list: extra board"), 0);
3447         ics_getting_history = H_FALSE;
3448         return;
3449     }
3450     
3451     /* Take action if this is the first board of a new game, or of a
3452        different game than is currently being displayed.  */
3453     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3454         relation == RELATION_ISOLATED_BOARD) {
3455         
3456         /* Forget the old game and get the history (if any) of the new one */
3457         if (gameMode != BeginningOfGame) {
3458           Reset(FALSE, TRUE);
3459         }
3460         newGame = TRUE;
3461         if (appData.autoRaiseBoard) BoardToTop();
3462         prevMove = -3;
3463         if (gamenum == -1) {
3464             newGameMode = IcsIdle;
3465         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3466                    appData.getMoveList) {
3467             /* Need to get game history */
3468             ics_getting_history = H_REQUESTED;
3469             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3470             SendToICS(str);
3471         }
3472         
3473         /* Initially flip the board to have black on the bottom if playing
3474            black or if the ICS flip flag is set, but let the user change
3475            it with the Flip View button. */
3476         flipView = appData.autoFlipView ? 
3477           (newGameMode == IcsPlayingBlack) || ics_flip :
3478           appData.flipView;
3479         
3480         /* Done with values from previous mode; copy in new ones */
3481         gameMode = newGameMode;
3482         ModeHighlight();
3483         ics_gamenum = gamenum;
3484         if (gamenum == gs_gamenum) {
3485             int klen = strlen(gs_kind);
3486             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3487             sprintf(str, "ICS %s", gs_kind);
3488             gameInfo.event = StrSave(str);
3489         } else {
3490             gameInfo.event = StrSave("ICS game");
3491         }
3492         gameInfo.site = StrSave(appData.icsHost);
3493         gameInfo.date = PGNDate();
3494         gameInfo.round = StrSave("-");
3495         gameInfo.white = StrSave(white);
3496         gameInfo.black = StrSave(black);
3497         timeControl = basetime * 60 * 1000;
3498         timeControl_2 = 0;
3499         timeIncrement = increment * 1000;
3500         movesPerSession = 0;
3501         gameInfo.timeControl = TimeControlTagValue();
3502         VariantSwitch(board, StringToVariant(gameInfo.event) );
3503   if (appData.debugMode) {
3504     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3505     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3506     setbuf(debugFP, NULL);
3507   }
3508
3509         gameInfo.outOfBook = NULL;
3510         
3511         /* Do we have the ratings? */
3512         if (strcmp(player1Name, white) == 0 &&
3513             strcmp(player2Name, black) == 0) {
3514             if (appData.debugMode)
3515               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3516                       player1Rating, player2Rating);
3517             gameInfo.whiteRating = player1Rating;
3518             gameInfo.blackRating = player2Rating;
3519         } else if (strcmp(player2Name, white) == 0 &&
3520                    strcmp(player1Name, black) == 0) {
3521             if (appData.debugMode)
3522               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3523                       player2Rating, player1Rating);
3524             gameInfo.whiteRating = player2Rating;
3525             gameInfo.blackRating = player1Rating;
3526         }
3527         player1Name[0] = player2Name[0] = NULLCHAR;
3528
3529         /* Silence shouts if requested */
3530         if (appData.quietPlay &&
3531             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3532             SendToICS(ics_prefix);
3533             SendToICS("set shout 0\n");
3534         }
3535     }
3536     
3537     /* Deal with midgame name changes */
3538     if (!newGame) {
3539         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3540             if (gameInfo.white) free(gameInfo.white);
3541             gameInfo.white = StrSave(white);
3542         }
3543         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3544             if (gameInfo.black) free(gameInfo.black);
3545             gameInfo.black = StrSave(black);
3546         }
3547     }
3548     
3549     /* Throw away game result if anything actually changes in examine mode */
3550     if (gameMode == IcsExamining && !newGame) {
3551         gameInfo.result = GameUnfinished;
3552         if (gameInfo.resultDetails != NULL) {
3553             free(gameInfo.resultDetails);
3554             gameInfo.resultDetails = NULL;
3555         }
3556     }
3557     
3558     /* In pausing && IcsExamining mode, we ignore boards coming
3559        in if they are in a different variation than we are. */
3560     if (pauseExamInvalid) return;
3561     if (pausing && gameMode == IcsExamining) {
3562         if (moveNum <= pauseExamForwardMostMove) {
3563             pauseExamInvalid = TRUE;
3564             forwardMostMove = pauseExamForwardMostMove;
3565             return;
3566         }
3567     }
3568     
3569   if (appData.debugMode) {
3570     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3571   }
3572     /* Parse the board */
3573     for (k = 0; k < ranks; k++) {
3574       for (j = 0; j < files; j++)
3575         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3576       if(gameInfo.holdingsWidth > 1) {
3577            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3578            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3579       }
3580     }
3581     CopyBoard(boards[moveNum], board);
3582     if (moveNum == 0) {
3583         startedFromSetupPosition =
3584           !CompareBoards(board, initialPosition);
3585         if(startedFromSetupPosition)
3586             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3587     }
3588
3589     /* [HGM] Set castling rights. Take the outermost Rooks,
3590        to make it also work for FRC opening positions. Note that board12
3591        is really defective for later FRC positions, as it has no way to
3592        indicate which Rook can castle if they are on the same side of King.
3593        For the initial position we grant rights to the outermost Rooks,
3594        and remember thos rights, and we then copy them on positions
3595        later in an FRC game. This means WB might not recognize castlings with
3596        Rooks that have moved back to their original position as illegal,
3597        but in ICS mode that is not its job anyway.
3598     */
3599     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3600     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3601
3602         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3603             if(board[0][i] == WhiteRook) j = i;
3604         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3605         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3606             if(board[0][i] == WhiteRook) j = i;
3607         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3608         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3609             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3610         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3611         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3612             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3613         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3614
3615         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3616         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3617             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3618         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3619             if(board[BOARD_HEIGHT-1][k] == bKing)
3620                 initialRights[5] = castlingRights[moveNum][5] = k;
3621     } else { int r;
3622         r = castlingRights[moveNum][0] = initialRights[0];
3623         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3624         r = castlingRights[moveNum][1] = initialRights[1];
3625         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3626         r = castlingRights[moveNum][3] = initialRights[3];
3627         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3628         r = castlingRights[moveNum][4] = initialRights[4];
3629         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3630         /* wildcastle kludge: always assume King has rights */
3631         r = castlingRights[moveNum][2] = initialRights[2];
3632         r = castlingRights[moveNum][5] = initialRights[5];
3633     }
3634     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3635     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3636
3637     
3638     if (ics_getting_history == H_GOT_REQ_HEADER ||
3639         ics_getting_history == H_GOT_UNREQ_HEADER) {
3640         /* This was an initial position from a move list, not
3641            the current position */
3642         return;
3643     }
3644     
3645     /* Update currentMove and known move number limits */
3646     newMove = newGame || moveNum > forwardMostMove;
3647
3648     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3649     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3650         takeback = forwardMostMove - moveNum;
3651         for (i = 0; i < takeback; i++) {
3652              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3653              SendToProgram("undo\n", &first);
3654         }
3655     }
3656
3657     if (newGame) {
3658         forwardMostMove = backwardMostMove = currentMove = moveNum;
3659         if (gameMode == IcsExamining && moveNum == 0) {
3660           /* Workaround for ICS limitation: we are not told the wild
3661              type when starting to examine a game.  But if we ask for
3662              the move list, the move list header will tell us */
3663             ics_getting_history = H_REQUESTED;
3664             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3665             SendToICS(str);
3666         }
3667     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3668                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3669         forwardMostMove = moveNum;
3670         if (!pausing || currentMove > forwardMostMove)
3671           currentMove = forwardMostMove;
3672     } else {
3673         /* New part of history that is not contiguous with old part */ 
3674         if (pausing && gameMode == IcsExamining) {
3675             pauseExamInvalid = TRUE;
3676             forwardMostMove = pauseExamForwardMostMove;
3677             return;
3678         }
3679         forwardMostMove = backwardMostMove = currentMove = moveNum;
3680         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3681             ics_getting_history = H_REQUESTED;
3682             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3683             SendToICS(str);
3684         }
3685     }
3686     
3687     /* Update the clocks */
3688     if (strchr(elapsed_time, '.')) {
3689       /* Time is in ms */
3690       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3691       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3692     } else {
3693       /* Time is in seconds */
3694       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3695       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3696     }
3697       
3698
3699 #if ZIPPY
3700     if (appData.zippyPlay && newGame &&
3701         gameMode != IcsObserving && gameMode != IcsIdle &&
3702         gameMode != IcsExamining)
3703       ZippyFirstBoard(moveNum, basetime, increment);
3704 #endif
3705     
3706     /* Put the move on the move list, first converting
3707        to canonical algebraic form. */
3708     if (moveNum > 0) {
3709   if (appData.debugMode) {
3710     if (appData.debugMode) { int f = forwardMostMove;
3711         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3712                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3713     }
3714     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3715     fprintf(debugFP, "moveNum = %d\n", moveNum);
3716     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3717     setbuf(debugFP, NULL);
3718   }
3719         if (moveNum <= backwardMostMove) {
3720             /* We don't know what the board looked like before
3721                this move.  Punt. */
3722             strcpy(parseList[moveNum - 1], move_str);
3723             strcat(parseList[moveNum - 1], " ");
3724             strcat(parseList[moveNum - 1], elapsed_time);
3725             moveList[moveNum - 1][0] = NULLCHAR;
3726         } else if (strcmp(move_str, "none") == 0) {
3727             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3728             /* Again, we don't know what the board looked like;
3729                this is really the start of the game. */
3730             parseList[moveNum - 1][0] = NULLCHAR;
3731             moveList[moveNum - 1][0] = NULLCHAR;
3732             backwardMostMove = moveNum;
3733             startedFromSetupPosition = TRUE;
3734             fromX = fromY = toX = toY = -1;
3735         } else {
3736           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3737           //                 So we parse the long-algebraic move string in stead of the SAN move
3738           int valid; char buf[MSG_SIZ], *prom;
3739
3740           // str looks something like "Q/a1-a2"; kill the slash
3741           if(str[1] == '/') 
3742                 sprintf(buf, "%c%s", str[0], str+2);
3743           else  strcpy(buf, str); // might be castling
3744           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3745                 strcat(buf, prom); // long move lacks promo specification!
3746           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3747                 if(appData.debugMode) 
3748                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3749                 strcpy(move_str, buf);
3750           }
3751           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3752                                 &fromX, &fromY, &toX, &toY, &promoChar)
3753                || ParseOneMove(buf, moveNum - 1, &moveType,
3754                                 &fromX, &fromY, &toX, &toY, &promoChar);
3755           // end of long SAN patch
3756           if (valid) {
3757             (void) CoordsToAlgebraic(boards[moveNum - 1],
3758                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3759                                      fromY, fromX, toY, toX, promoChar,
3760                                      parseList[moveNum-1]);
3761             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3762                              castlingRights[moveNum]) ) {
3763               case MT_NONE:
3764               case MT_STALEMATE:
3765               default:
3766                 break;
3767               case MT_CHECK:
3768                 if(gameInfo.variant != VariantShogi)
3769                     strcat(parseList[moveNum - 1], "+");
3770                 break;
3771               case MT_CHECKMATE:
3772               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3773                 strcat(parseList[moveNum - 1], "#");
3774                 break;
3775             }
3776             strcat(parseList[moveNum - 1], " ");
3777             strcat(parseList[moveNum - 1], elapsed_time);
3778             /* currentMoveString is set as a side-effect of ParseOneMove */
3779             strcpy(moveList[moveNum - 1], currentMoveString);
3780             strcat(moveList[moveNum - 1], "\n");
3781           } else {
3782             /* Move from ICS was illegal!?  Punt. */
3783   if (appData.debugMode) {
3784     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3785     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3786   }
3787             strcpy(parseList[moveNum - 1], move_str);
3788             strcat(parseList[moveNum - 1], " ");
3789             strcat(parseList[moveNum - 1], elapsed_time);
3790             moveList[moveNum - 1][0] = NULLCHAR;
3791             fromX = fromY = toX = toY = -1;
3792           }
3793         }
3794   if (appData.debugMode) {
3795     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3796     setbuf(debugFP, NULL);
3797   }
3798
3799 #if ZIPPY
3800         /* Send move to chess program (BEFORE animating it). */
3801         if (appData.zippyPlay && !newGame && newMove && 
3802            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3803
3804             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3805                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3806                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3807                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3808                             move_str);
3809                     DisplayError(str, 0);
3810                 } else {
3811                     if (first.sendTime) {
3812                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3813                     }
3814                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3815                     if (firstMove && !bookHit) {
3816                         firstMove = FALSE;
3817                         if (first.useColors) {
3818                           SendToProgram(gameMode == IcsPlayingWhite ?
3819                                         "white\ngo\n" :
3820                                         "black\ngo\n", &first);
3821                         } else {
3822                           SendToProgram("go\n", &first);
3823                         }
3824                         first.maybeThinking = TRUE;
3825                     }
3826                 }
3827             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3828               if (moveList[moveNum - 1][0] == NULLCHAR) {
3829                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3830                 DisplayError(str, 0);
3831               } else {
3832                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3833                 SendMoveToProgram(moveNum - 1, &first);
3834               }
3835             }
3836         }
3837 #endif
3838     }
3839
3840     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3841         /* If move comes from a remote source, animate it.  If it
3842            isn't remote, it will have already been animated. */
3843         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3844             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3845         }
3846         if (!pausing && appData.highlightLastMove) {
3847             SetHighlights(fromX, fromY, toX, toY);
3848         }
3849     }
3850     
3851     /* Start the clocks */
3852     whiteFlag = blackFlag = FALSE;
3853     appData.clockMode = !(basetime == 0 && increment == 0);
3854     if (ticking == 0) {
3855       ics_clock_paused = TRUE;
3856       StopClocks();
3857     } else if (ticking == 1) {
3858       ics_clock_paused = FALSE;
3859     }
3860     if (gameMode == IcsIdle ||
3861         relation == RELATION_OBSERVING_STATIC ||
3862         relation == RELATION_EXAMINING ||
3863         ics_clock_paused)
3864       DisplayBothClocks();
3865     else
3866       StartClocks();
3867     
3868     /* Display opponents and material strengths */
3869     if (gameInfo.variant != VariantBughouse &&
3870         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3871         if (tinyLayout || smallLayout) {
3872             if(gameInfo.variant == VariantNormal)
3873                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3874                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3875                     basetime, increment);
3876             else
3877                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3878                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3879                     basetime, increment, (int) gameInfo.variant);
3880         } else {
3881             if(gameInfo.variant == VariantNormal)
3882                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3883                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3884                     basetime, increment);
3885             else
3886                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3887                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3888                     basetime, increment, VariantName(gameInfo.variant));
3889         }
3890         DisplayTitle(str);
3891   if (appData.debugMode) {
3892     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3893   }
3894     }
3895
3896    
3897     /* Display the board */
3898     if (!pausing && !appData.noGUI) {
3899       
3900       if (appData.premove)
3901           if (!gotPremove || 
3902              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3903              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3904               ClearPremoveHighlights();
3905
3906       DrawPosition(FALSE, boards[currentMove]);
3907       DisplayMove(moveNum - 1);
3908       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3909             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3910               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3911         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3912       }
3913     }
3914
3915     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3916 #if ZIPPY
3917     if(bookHit) { // [HGM] book: simulate book reply
3918         static char bookMove[MSG_SIZ]; // a bit generous?
3919
3920         programStats.nodes = programStats.depth = programStats.time = 
3921         programStats.score = programStats.got_only_move = 0;
3922         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3923
3924         strcpy(bookMove, "move ");
3925         strcat(bookMove, bookHit);
3926         HandleMachineMove(bookMove, &first);
3927     }
3928 #endif
3929 }
3930
3931 void
3932 GetMoveListEvent()
3933 {
3934     char buf[MSG_SIZ];
3935     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3936         ics_getting_history = H_REQUESTED;
3937         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3938         SendToICS(buf);
3939     }
3940 }
3941
3942 void
3943 AnalysisPeriodicEvent(force)
3944      int force;
3945 {
3946     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3947          && !force) || !appData.periodicUpdates)
3948       return;
3949
3950     /* Send . command to Crafty to collect stats */
3951     SendToProgram(".\n", &first);
3952
3953     /* Don't send another until we get a response (this makes
3954        us stop sending to old Crafty's which don't understand
3955        the "." command (sending illegal cmds resets node count & time,
3956        which looks bad)) */
3957     programStats.ok_to_send = 0;
3958 }
3959
3960 void ics_update_width(new_width)
3961         int new_width;
3962 {
3963         ics_printf("set width %d\n", new_width);
3964 }
3965
3966 void
3967 SendMoveToProgram(moveNum, cps)
3968      int moveNum;
3969      ChessProgramState *cps;
3970 {
3971     char buf[MSG_SIZ];
3972
3973     if (cps->useUsermove) {
3974       SendToProgram("usermove ", cps);
3975     }
3976     if (cps->useSAN) {
3977       char *space;
3978       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3979         int len = space - parseList[moveNum];
3980         memcpy(buf, parseList[moveNum], len);
3981         buf[len++] = '\n';
3982         buf[len] = NULLCHAR;
3983       } else {
3984         sprintf(buf, "%s\n", parseList[moveNum]);
3985       }
3986       SendToProgram(buf, cps);
3987     } else {
3988       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3989         AlphaRank(moveList[moveNum], 4);
3990         SendToProgram(moveList[moveNum], cps);
3991         AlphaRank(moveList[moveNum], 4); // and back
3992       } else
3993       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3994        * the engine. It would be nice to have a better way to identify castle 
3995        * moves here. */
3996       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3997                                                                          && cps->useOOCastle) {
3998         int fromX = moveList[moveNum][0] - AAA; 
3999         int fromY = moveList[moveNum][1] - ONE;
4000         int toX = moveList[moveNum][2] - AAA; 
4001         int toY = moveList[moveNum][3] - ONE;
4002         if((boards[moveNum][fromY][fromX] == WhiteKing 
4003             && boards[moveNum][toY][toX] == WhiteRook)
4004            || (boards[moveNum][fromY][fromX] == BlackKing 
4005                && boards[moveNum][toY][toX] == BlackRook)) {
4006           if(toX > fromX) SendToProgram("O-O\n", cps);
4007           else SendToProgram("O-O-O\n", cps);
4008         }
4009         else SendToProgram(moveList[moveNum], cps);
4010       }
4011       else SendToProgram(moveList[moveNum], cps);
4012       /* End of additions by Tord */
4013     }
4014
4015     /* [HGM] setting up the opening has brought engine in force mode! */
4016     /*       Send 'go' if we are in a mode where machine should play. */
4017     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4018         (gameMode == TwoMachinesPlay   ||
4019 #ifdef ZIPPY
4020          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4021 #endif
4022          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4023         SendToProgram("go\n", cps);
4024   if (appData.debugMode) {
4025     fprintf(debugFP, "(extra)\n");
4026   }
4027     }
4028     setboardSpoiledMachineBlack = 0;
4029 }
4030
4031 void
4032 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4033      ChessMove moveType;
4034      int fromX, fromY, toX, toY;
4035 {
4036     char user_move[MSG_SIZ];
4037
4038     switch (moveType) {
4039       default:
4040         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4041                 (int)moveType, fromX, fromY, toX, toY);
4042         DisplayError(user_move + strlen("say "), 0);
4043         break;
4044       case WhiteKingSideCastle:
4045       case BlackKingSideCastle:
4046       case WhiteQueenSideCastleWild:
4047       case BlackQueenSideCastleWild:
4048       /* PUSH Fabien */
4049       case WhiteHSideCastleFR:
4050       case BlackHSideCastleFR:
4051       /* POP Fabien */
4052         sprintf(user_move, "o-o\n");
4053         break;
4054       case WhiteQueenSideCastle:
4055       case BlackQueenSideCastle:
4056       case WhiteKingSideCastleWild:
4057       case BlackKingSideCastleWild:
4058       /* PUSH Fabien */
4059       case WhiteASideCastleFR:
4060       case BlackASideCastleFR:
4061       /* POP Fabien */
4062         sprintf(user_move, "o-o-o\n");
4063         break;
4064       case WhitePromotionQueen:
4065       case BlackPromotionQueen:
4066       case WhitePromotionRook:
4067       case BlackPromotionRook:
4068       case WhitePromotionBishop:
4069       case BlackPromotionBishop:
4070       case WhitePromotionKnight:
4071       case BlackPromotionKnight:
4072       case WhitePromotionKing:
4073       case BlackPromotionKing:
4074       case WhitePromotionChancellor:
4075       case BlackPromotionChancellor:
4076       case WhitePromotionArchbishop:
4077       case BlackPromotionArchbishop:
4078         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4079             sprintf(user_move, "%c%c%c%c=%c\n",
4080                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4081                 PieceToChar(WhiteFerz));
4082         else if(gameInfo.variant == VariantGreat)
4083             sprintf(user_move, "%c%c%c%c=%c\n",
4084                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4085                 PieceToChar(WhiteMan));
4086         else
4087             sprintf(user_move, "%c%c%c%c=%c\n",
4088                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4089                 PieceToChar(PromoPiece(moveType)));
4090         break;
4091       case WhiteDrop:
4092       case BlackDrop:
4093         sprintf(user_move, "%c@%c%c\n",
4094                 ToUpper(PieceToChar((ChessSquare) fromX)),
4095                 AAA + toX, ONE + toY);
4096         break;
4097       case NormalMove:
4098       case WhiteCapturesEnPassant:
4099       case BlackCapturesEnPassant:
4100       case IllegalMove:  /* could be a variant we don't quite understand */
4101         sprintf(user_move, "%c%c%c%c\n",
4102                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4103         break;
4104     }
4105     SendToICS(user_move);
4106     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4107         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4108 }
4109
4110 void
4111 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4112      int rf, ff, rt, ft;
4113      char promoChar;
4114      char move[7];
4115 {
4116     if (rf == DROP_RANK) {
4117         sprintf(move, "%c@%c%c\n",
4118                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4119     } else {
4120         if (promoChar == 'x' || promoChar == NULLCHAR) {
4121             sprintf(move, "%c%c%c%c\n",
4122                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4123         } else {
4124             sprintf(move, "%c%c%c%c%c\n",
4125                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4126         }
4127     }
4128 }
4129
4130 void
4131 ProcessICSInitScript(f)
4132      FILE *f;
4133 {
4134     char buf[MSG_SIZ];
4135
4136     while (fgets(buf, MSG_SIZ, f)) {
4137         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4138     }
4139
4140     fclose(f);
4141 }
4142
4143
4144 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4145 void
4146 AlphaRank(char *move, int n)
4147 {
4148 //    char *p = move, c; int x, y;
4149
4150     if (appData.debugMode) {
4151         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4152     }
4153
4154     if(move[1]=='*' && 
4155        move[2]>='0' && move[2]<='9' &&
4156        move[3]>='a' && move[3]<='x'    ) {
4157         move[1] = '@';
4158         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4159         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4160     } else
4161     if(move[0]>='0' && move[0]<='9' &&
4162        move[1]>='a' && move[1]<='x' &&
4163        move[2]>='0' && move[2]<='9' &&
4164        move[3]>='a' && move[3]<='x'    ) {
4165         /* input move, Shogi -> normal */
4166         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4167         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4168         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4169         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4170     } else
4171     if(move[1]=='@' &&
4172        move[3]>='0' && move[3]<='9' &&
4173        move[2]>='a' && move[2]<='x'    ) {
4174         move[1] = '*';
4175         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4176         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4177     } else
4178     if(
4179        move[0]>='a' && move[0]<='x' &&
4180        move[3]>='0' && move[3]<='9' &&
4181        move[2]>='a' && move[2]<='x'    ) {
4182          /* output move, normal -> Shogi */
4183         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4184         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4185         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4186         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4187         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4188     }
4189     if (appData.debugMode) {
4190         fprintf(debugFP, "   out = '%s'\n", move);
4191     }
4192 }
4193
4194 /* Parser for moves from gnuchess, ICS, or user typein box */
4195 Boolean
4196 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4197      char *move;
4198      int moveNum;
4199      ChessMove *moveType;
4200      int *fromX, *fromY, *toX, *toY;
4201      char *promoChar;
4202 {       
4203     if (appData.debugMode) {
4204         fprintf(debugFP, "move to parse: %s\n", move);
4205     }
4206     *moveType = yylexstr(moveNum, move);
4207
4208     switch (*moveType) {
4209       case WhitePromotionChancellor:
4210       case BlackPromotionChancellor:
4211       case WhitePromotionArchbishop:
4212       case BlackPromotionArchbishop:
4213       case WhitePromotionQueen:
4214       case BlackPromotionQueen:
4215       case WhitePromotionRook:
4216       case BlackPromotionRook:
4217       case WhitePromotionBishop:
4218       case BlackPromotionBishop:
4219       case WhitePromotionKnight:
4220       case BlackPromotionKnight:
4221       case WhitePromotionKing:
4222       case BlackPromotionKing:
4223       case NormalMove:
4224       case WhiteCapturesEnPassant:
4225       case BlackCapturesEnPassant:
4226       case WhiteKingSideCastle:
4227       case WhiteQueenSideCastle:
4228       case BlackKingSideCastle:
4229       case BlackQueenSideCastle:
4230       case WhiteKingSideCastleWild:
4231       case WhiteQueenSideCastleWild:
4232       case BlackKingSideCastleWild:
4233       case BlackQueenSideCastleWild:
4234       /* Code added by Tord: */
4235       case WhiteHSideCastleFR:
4236       case WhiteASideCastleFR:
4237       case BlackHSideCastleFR:
4238       case BlackASideCastleFR:
4239       /* End of code added by Tord */
4240       case IllegalMove:         /* bug or odd chess variant */
4241         *fromX = currentMoveString[0] - AAA;
4242         *fromY = currentMoveString[1] - ONE;
4243         *toX = currentMoveString[2] - AAA;
4244         *toY = currentMoveString[3] - ONE;
4245         *promoChar = currentMoveString[4];
4246         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4247             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4248     if (appData.debugMode) {
4249         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4250     }
4251             *fromX = *fromY = *toX = *toY = 0;
4252             return FALSE;
4253         }
4254         if (appData.testLegality) {
4255           return (*moveType != IllegalMove);
4256         } else {
4257           return !(fromX == fromY && toX == toY);
4258         }
4259
4260       case WhiteDrop:
4261       case BlackDrop:
4262         *fromX = *moveType == WhiteDrop ?
4263           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4264           (int) CharToPiece(ToLower(currentMoveString[0]));
4265         *fromY = DROP_RANK;
4266         *toX = currentMoveString[2] - AAA;
4267         *toY = currentMoveString[3] - ONE;
4268         *promoChar = NULLCHAR;
4269         return TRUE;
4270
4271       case AmbiguousMove:
4272       case ImpossibleMove:
4273       case (ChessMove) 0:       /* end of file */
4274       case ElapsedTime:
4275       case Comment:
4276       case PGNTag:
4277       case NAG:
4278       case WhiteWins:
4279       case BlackWins:
4280       case GameIsDrawn:
4281       default:
4282     if (appData.debugMode) {
4283         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4284     }
4285         /* bug? */
4286         *fromX = *fromY = *toX = *toY = 0;
4287         *promoChar = NULLCHAR;
4288         return FALSE;
4289     }
4290 }
4291
4292 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4293 // All positions will have equal probability, but the current method will not provide a unique
4294 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4295 #define DARK 1
4296 #define LITE 2
4297 #define ANY 3
4298
4299 int squaresLeft[4];
4300 int piecesLeft[(int)BlackPawn];
4301 int seed, nrOfShuffles;
4302
4303 void GetPositionNumber()
4304 {       // sets global variable seed
4305         int i;
4306
4307         seed = appData.defaultFrcPosition;
4308         if(seed < 0) { // randomize based on time for negative FRC position numbers
4309                 for(i=0; i<50; i++) seed += random();
4310                 seed = random() ^ random() >> 8 ^ random() << 8;
4311                 if(seed<0) seed = -seed;
4312         }
4313 }
4314
4315 int put(Board board, int pieceType, int rank, int n, int shade)
4316 // put the piece on the (n-1)-th empty squares of the given shade
4317 {
4318         int i;
4319
4320         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4321                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4322                         board[rank][i] = (ChessSquare) pieceType;
4323                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4324                         squaresLeft[ANY]--;
4325                         piecesLeft[pieceType]--; 
4326                         return i;
4327                 }
4328         }
4329         return -1;
4330 }
4331
4332
4333 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4334 // calculate where the next piece goes, (any empty square), and put it there
4335 {
4336         int i;
4337
4338         i = seed % squaresLeft[shade];
4339         nrOfShuffles *= squaresLeft[shade];
4340         seed /= squaresLeft[shade];
4341         put(board, pieceType, rank, i, shade);
4342 }
4343
4344 void AddTwoPieces(Board board, int pieceType, int rank)
4345 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4346 {
4347         int i, n=squaresLeft[ANY], j=n-1, k;
4348
4349         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4350         i = seed % k;  // pick one
4351         nrOfShuffles *= k;
4352         seed /= k;
4353         while(i >= j) i -= j--;
4354         j = n - 1 - j; i += j;
4355         put(board, pieceType, rank, j, ANY);
4356         put(board, pieceType, rank, i, ANY);
4357 }
4358
4359 void SetUpShuffle(Board board, int number)
4360 {
4361         int i, p, first=1;
4362
4363         GetPositionNumber(); nrOfShuffles = 1;
4364
4365         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4366         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4367         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4368
4369         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4370
4371         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4372             p = (int) board[0][i];
4373             if(p < (int) BlackPawn) piecesLeft[p] ++;
4374             board[0][i] = EmptySquare;
4375         }
4376
4377         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4378             // shuffles restricted to allow normal castling put KRR first
4379             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4380                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4381             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4382                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4383             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4384                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4385             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4386                 put(board, WhiteRook, 0, 0, ANY);
4387             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4388         }
4389
4390         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4391             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4392             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4393                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4394                 while(piecesLeft[p] >= 2) {
4395                     AddOnePiece(board, p, 0, LITE);
4396                     AddOnePiece(board, p, 0, DARK);
4397                 }
4398                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4399             }
4400
4401         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4402             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4403             // but we leave King and Rooks for last, to possibly obey FRC restriction
4404             if(p == (int)WhiteRook) continue;
4405             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4406             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4407         }
4408
4409         // now everything is placed, except perhaps King (Unicorn) and Rooks
4410
4411         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4412             // Last King gets castling rights
4413             while(piecesLeft[(int)WhiteUnicorn]) {
4414                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4415                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4416             }
4417
4418             while(piecesLeft[(int)WhiteKing]) {
4419                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4420                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4421             }
4422
4423
4424         } else {
4425             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4426             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4427         }
4428
4429         // Only Rooks can be left; simply place them all
4430         while(piecesLeft[(int)WhiteRook]) {
4431                 i = put(board, WhiteRook, 0, 0, ANY);
4432                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4433                         if(first) {
4434                                 first=0;
4435                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4436                         }
4437                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4438                 }
4439         }
4440         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4441             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4442         }
4443
4444         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4445 }
4446
4447 int SetCharTable( char *table, const char * map )
4448 /* [HGM] moved here from winboard.c because of its general usefulness */
4449 /*       Basically a safe strcpy that uses the last character as King */
4450 {
4451     int result = FALSE; int NrPieces;
4452
4453     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4454                     && NrPieces >= 12 && !(NrPieces&1)) {
4455         int i; /* [HGM] Accept even length from 12 to 34 */
4456
4457         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4458         for( i=0; i<NrPieces/2-1; i++ ) {
4459             table[i] = map[i];
4460             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4461         }
4462         table[(int) WhiteKing]  = map[NrPieces/2-1];
4463         table[(int) BlackKing]  = map[NrPieces-1];
4464
4465         result = TRUE;
4466     }
4467
4468     return result;
4469 }
4470
4471 void Prelude(Board board)
4472 {       // [HGM] superchess: random selection of exo-pieces
4473         int i, j, k; ChessSquare p; 
4474         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4475
4476         GetPositionNumber(); // use FRC position number
4477
4478         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4479             SetCharTable(pieceToChar, appData.pieceToCharTable);
4480             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4481                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4482         }
4483
4484         j = seed%4;                 seed /= 4; 
4485         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4486         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4487         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4488         j = seed%3 + (seed%3 >= j); seed /= 3; 
4489         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4490         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4491         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4492         j = seed%3;                 seed /= 3; 
4493         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4494         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4495         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4496         j = seed%2 + (seed%2 >= j); seed /= 2; 
4497         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4498         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4499         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4500         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4501         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4502         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4503         put(board, exoPieces[0],    0, 0, ANY);
4504         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4505 }
4506
4507 void
4508 InitPosition(redraw)
4509      int redraw;
4510 {
4511     ChessSquare (* pieces)[BOARD_SIZE];
4512     int i, j, pawnRow, overrule,
4513     oldx = gameInfo.boardWidth,
4514     oldy = gameInfo.boardHeight,
4515     oldh = gameInfo.holdingsWidth,
4516     oldv = gameInfo.variant;
4517
4518     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4519
4520     /* [AS] Initialize pv info list [HGM] and game status */
4521     {
4522         for( i=0; i<MAX_MOVES; i++ ) {
4523             pvInfoList[i].depth = 0;
4524             epStatus[i]=EP_NONE;
4525             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4526         }
4527
4528         initialRulePlies = 0; /* 50-move counter start */
4529
4530         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4531         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4532     }
4533
4534     
4535     /* [HGM] logic here is completely changed. In stead of full positions */
4536     /* the initialized data only consist of the two backranks. The switch */
4537     /* selects which one we will use, which is than copied to the Board   */
4538     /* initialPosition, which for the rest is initialized by Pawns and    */
4539     /* empty squares. This initial position is then copied to boards[0],  */
4540     /* possibly after shuffling, so that it remains available.            */
4541
4542     gameInfo.holdingsWidth = 0; /* default board sizes */
4543     gameInfo.boardWidth    = 8;
4544     gameInfo.boardHeight   = 8;
4545     gameInfo.holdingsSize  = 0;
4546     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4547     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4548     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4549
4550     switch (gameInfo.variant) {
4551     case VariantFischeRandom:
4552       shuffleOpenings = TRUE;
4553     default:
4554       pieces = FIDEArray;
4555       break;
4556     case VariantShatranj:
4557       pieces = ShatranjArray;
4558       nrCastlingRights = 0;
4559       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4560       break;
4561     case VariantTwoKings:
4562       pieces = twoKingsArray;
4563       break;
4564     case VariantCapaRandom:
4565       shuffleOpenings = TRUE;
4566     case VariantCapablanca:
4567       pieces = CapablancaArray;
4568       gameInfo.boardWidth = 10;
4569       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4570       break;
4571     case VariantGothic:
4572       pieces = GothicArray;
4573       gameInfo.boardWidth = 10;
4574       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4575       break;
4576     case VariantJanus:
4577       pieces = JanusArray;
4578       gameInfo.boardWidth = 10;
4579       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4580       nrCastlingRights = 6;
4581         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4582         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4583         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4584         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4585         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4586         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4587       break;
4588     case VariantFalcon:
4589       pieces = FalconArray;
4590       gameInfo.boardWidth = 10;
4591       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4592       break;
4593     case VariantXiangqi:
4594       pieces = XiangqiArray;
4595       gameInfo.boardWidth  = 9;
4596       gameInfo.boardHeight = 10;
4597       nrCastlingRights = 0;
4598       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4599       break;
4600     case VariantShogi:
4601       pieces = ShogiArray;
4602       gameInfo.boardWidth  = 9;
4603       gameInfo.boardHeight = 9;
4604       gameInfo.holdingsSize = 7;
4605       nrCastlingRights = 0;
4606       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4607       break;
4608     case VariantCourier:
4609       pieces = CourierArray;
4610       gameInfo.boardWidth  = 12;
4611       nrCastlingRights = 0;
4612       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4613       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4614       break;
4615     case VariantKnightmate:
4616       pieces = KnightmateArray;
4617       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4618       break;
4619     case VariantFairy:
4620       pieces = fairyArray;
4621       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4622       break;
4623     case VariantGreat:
4624       pieces = GreatArray;
4625       gameInfo.boardWidth = 10;
4626       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4627       gameInfo.holdingsSize = 8;
4628       break;
4629     case VariantSuper:
4630       pieces = FIDEArray;
4631       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4632       gameInfo.holdingsSize = 8;
4633       startedFromSetupPosition = TRUE;
4634       break;
4635     case VariantCrazyhouse:
4636     case VariantBughouse:
4637       pieces = FIDEArray;
4638       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4639       gameInfo.holdingsSize = 5;
4640       break;
4641     case VariantWildCastle:
4642       pieces = FIDEArray;
4643       /* !!?shuffle with kings guaranteed to be on d or e file */
4644       shuffleOpenings = 1;
4645       break;
4646     case VariantNoCastle:
4647       pieces = FIDEArray;
4648       nrCastlingRights = 0;
4649       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4650       /* !!?unconstrained back-rank shuffle */
4651       shuffleOpenings = 1;
4652       break;
4653     }
4654
4655     overrule = 0;
4656     if(appData.NrFiles >= 0) {
4657         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4658         gameInfo.boardWidth = appData.NrFiles;
4659     }
4660     if(appData.NrRanks >= 0) {
4661         gameInfo.boardHeight = appData.NrRanks;
4662     }
4663     if(appData.holdingsSize >= 0) {
4664         i = appData.holdingsSize;
4665         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4666         gameInfo.holdingsSize = i;
4667     }
4668     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4669     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4670         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4671
4672     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4673     if(pawnRow < 1) pawnRow = 1;
4674
4675     /* User pieceToChar list overrules defaults */
4676     if(appData.pieceToCharTable != NULL)
4677         SetCharTable(pieceToChar, appData.pieceToCharTable);
4678
4679     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4680
4681         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4682             s = (ChessSquare) 0; /* account holding counts in guard band */
4683         for( i=0; i<BOARD_HEIGHT; i++ )
4684             initialPosition[i][j] = s;
4685
4686         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4687         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4688         initialPosition[pawnRow][j] = WhitePawn;
4689         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4690         if(gameInfo.variant == VariantXiangqi) {
4691             if(j&1) {
4692                 initialPosition[pawnRow][j] = 
4693                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4694                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4695                    initialPosition[2][j] = WhiteCannon;
4696                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4697                 }
4698             }
4699         }
4700         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4701     }
4702     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4703
4704             j=BOARD_LEFT+1;
4705             initialPosition[1][j] = WhiteBishop;
4706             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4707             j=BOARD_RGHT-2;
4708             initialPosition[1][j] = WhiteRook;
4709             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4710     }
4711
4712     if( nrCastlingRights == -1) {
4713         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4714         /*       This sets default castling rights from none to normal corners   */
4715         /* Variants with other castling rights must set them themselves above    */
4716         nrCastlingRights = 6;
4717        
4718         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4719         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4720         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4721         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4722         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4723         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4724      }
4725
4726      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4727      if(gameInfo.variant == VariantGreat) { // promotion commoners
4728         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4729         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4730         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4731         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4732      }
4733   if (appData.debugMode) {
4734     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4735   }
4736     if(shuffleOpenings) {
4737         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4738         startedFromSetupPosition = TRUE;
4739     }
4740     if(startedFromPositionFile) {
4741       /* [HGM] loadPos: use PositionFile for every new game */
4742       CopyBoard(initialPosition, filePosition);
4743       for(i=0; i<nrCastlingRights; i++)
4744           castlingRights[0][i] = initialRights[i] = fileRights[i];
4745       startedFromSetupPosition = TRUE;
4746     }
4747
4748     CopyBoard(boards[0], initialPosition);
4749
4750     if(oldx != gameInfo.boardWidth ||
4751        oldy != gameInfo.boardHeight ||
4752        oldh != gameInfo.holdingsWidth
4753 #ifdef GOTHIC
4754        || oldv == VariantGothic ||        // For licensing popups
4755        gameInfo.variant == VariantGothic
4756 #endif
4757 #ifdef FALCON
4758        || oldv == VariantFalcon ||
4759        gameInfo.variant == VariantFalcon
4760 #endif
4761                                          )
4762             InitDrawingSizes(-2 ,0);
4763
4764     if (redraw)
4765       DrawPosition(TRUE, boards[currentMove]);
4766 }
4767
4768 void
4769 SendBoard(cps, moveNum)
4770      ChessProgramState *cps;
4771      int moveNum;
4772 {
4773     char message[MSG_SIZ];
4774     
4775     if (cps->useSetboard) {
4776       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4777       sprintf(message, "setboard %s\n", fen);
4778       SendToProgram(message, cps);
4779       free(fen);
4780
4781     } else {
4782       ChessSquare *bp;
4783       int i, j;
4784       /* Kludge to set black to move, avoiding the troublesome and now
4785        * deprecated "black" command.
4786        */
4787       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4788
4789       SendToProgram("edit\n", cps);
4790       SendToProgram("#\n", cps);
4791       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4792         bp = &boards[moveNum][i][BOARD_LEFT];
4793         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4794           if ((int) *bp < (int) BlackPawn) {
4795             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4796                     AAA + j, ONE + i);
4797             if(message[0] == '+' || message[0] == '~') {
4798                 sprintf(message, "%c%c%c+\n",
4799                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4800                         AAA + j, ONE + i);
4801             }
4802             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4803                 message[1] = BOARD_RGHT   - 1 - j + '1';
4804                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4805             }
4806             SendToProgram(message, cps);
4807           }
4808         }
4809       }
4810     
4811       SendToProgram("c\n", cps);
4812       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4813         bp = &boards[moveNum][i][BOARD_LEFT];
4814         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4815           if (((int) *bp != (int) EmptySquare)
4816               && ((int) *bp >= (int) BlackPawn)) {
4817             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4818                     AAA + j, ONE + i);
4819             if(message[0] == '+' || message[0] == '~') {
4820                 sprintf(message, "%c%c%c+\n",
4821                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4822                         AAA + j, ONE + i);
4823             }
4824             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4825                 message[1] = BOARD_RGHT   - 1 - j + '1';
4826                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4827             }
4828             SendToProgram(message, cps);
4829           }
4830         }
4831       }
4832     
4833       SendToProgram(".\n", cps);
4834     }
4835     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4836 }
4837
4838 int
4839 IsPromotion(fromX, fromY, toX, toY)
4840      int fromX, fromY, toX, toY;
4841 {
4842     /* [HGM] add Shogi promotions */
4843     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4844     ChessSquare piece;
4845
4846     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4847       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4848    /* [HGM] Note to self: line above also weeds out drops */
4849     piece = boards[currentMove][fromY][fromX];
4850     if(gameInfo.variant == VariantShogi) {
4851         promotionZoneSize = 3;
4852         highestPromotingPiece = (int)WhiteKing;
4853         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4854            and if in normal chess we then allow promotion to King, why not
4855            allow promotion of other piece in Shogi?                         */
4856     }
4857     if((int)piece >= BlackPawn) {
4858         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4859              return FALSE;
4860         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4861     } else {
4862         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4863            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4864     }
4865     return ( (int)piece <= highestPromotingPiece );
4866 }
4867
4868 int
4869 InPalace(row, column)
4870      int row, column;
4871 {   /* [HGM] for Xiangqi */
4872     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4873          column < (BOARD_WIDTH + 4)/2 &&
4874          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4875     return FALSE;
4876 }
4877
4878 int
4879 PieceForSquare (x, y)
4880      int x;
4881      int y;
4882 {
4883   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4884      return -1;
4885   else
4886      return boards[currentMove][y][x];
4887 }
4888
4889 int
4890 OKToStartUserMove(x, y)
4891      int x, y;
4892 {
4893     ChessSquare from_piece;
4894     int white_piece;
4895
4896     if (matchMode) return FALSE;
4897     if (gameMode == EditPosition) return TRUE;
4898
4899     if (x >= 0 && y >= 0)
4900       from_piece = boards[currentMove][y][x];
4901     else
4902       from_piece = EmptySquare;
4903
4904     if (from_piece == EmptySquare) return FALSE;
4905
4906     white_piece = (int)from_piece >= (int)WhitePawn &&
4907       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4908
4909     switch (gameMode) {
4910       case PlayFromGameFile:
4911       case AnalyzeFile:
4912       case TwoMachinesPlay:
4913       case EndOfGame:
4914         return FALSE;
4915
4916       case IcsObserving:
4917       case IcsIdle:
4918         return FALSE;
4919
4920       case MachinePlaysWhite:
4921       case IcsPlayingBlack:
4922         if (appData.zippyPlay) return FALSE;
4923         if (white_piece) {
4924             DisplayMoveError(_("You are playing Black"));
4925             return FALSE;
4926         }
4927         break;
4928
4929       case MachinePlaysBlack:
4930       case IcsPlayingWhite:
4931         if (appData.zippyPlay) return FALSE;
4932         if (!white_piece) {
4933             DisplayMoveError(_("You are playing White"));
4934             return FALSE;
4935         }
4936         break;
4937
4938       case EditGame:
4939         if (!white_piece && WhiteOnMove(currentMove)) {
4940             DisplayMoveError(_("It is White's turn"));
4941             return FALSE;
4942         }           
4943         if (white_piece && !WhiteOnMove(currentMove)) {
4944             DisplayMoveError(_("It is Black's turn"));
4945             return FALSE;
4946         }           
4947         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4948             /* Editing correspondence game history */
4949             /* Could disallow this or prompt for confirmation */
4950             cmailOldMove = -1;
4951         }
4952         if (currentMove < forwardMostMove) {
4953             /* Discarding moves */
4954             /* Could prompt for confirmation here,
4955                but I don't think that's such a good idea */
4956             forwardMostMove = currentMove;
4957         }
4958         break;
4959
4960       case BeginningOfGame:
4961         if (appData.icsActive) return FALSE;
4962         if (!appData.noChessProgram) {
4963             if (!white_piece) {
4964                 DisplayMoveError(_("You are playing White"));
4965                 return FALSE;
4966             }
4967         }
4968         break;
4969         
4970       case Training:
4971         if (!white_piece && WhiteOnMove(currentMove)) {
4972             DisplayMoveError(_("It is White's turn"));
4973             return FALSE;
4974         }           
4975         if (white_piece && !WhiteOnMove(currentMove)) {
4976             DisplayMoveError(_("It is Black's turn"));
4977             return FALSE;
4978         }           
4979         break;
4980
4981       default:
4982       case IcsExamining:
4983         break;
4984     }
4985     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4986         && gameMode != AnalyzeFile && gameMode != Training) {
4987         DisplayMoveError(_("Displayed position is not current"));
4988         return FALSE;
4989     }
4990     return TRUE;
4991 }
4992
4993 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4994 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4995 int lastLoadGameUseList = FALSE;
4996 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4997 ChessMove lastLoadGameStart = (ChessMove) 0;
4998
4999 ChessMove
5000 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5001      int fromX, fromY, toX, toY;
5002      int promoChar;
5003      Boolean captureOwn;
5004 {
5005     ChessMove moveType;
5006     ChessSquare pdown, pup;
5007
5008     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5009
5010     /* [HGM] suppress all moves into holdings area and guard band */
5011     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5012             return ImpossibleMove;
5013
5014     /* [HGM] <sameColor> moved to here from winboard.c */
5015     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5016     pdown = boards[currentMove][fromY][fromX];
5017     pup = boards[currentMove][toY][toX];
5018     if (    gameMode != EditPosition && !captureOwn &&
5019             (WhitePawn <= pdown && pdown < BlackPawn &&
5020              WhitePawn <= pup && pup < BlackPawn  ||
5021              BlackPawn <= pdown && pdown < EmptySquare &&
5022              BlackPawn <= pup && pup < EmptySquare 
5023             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5024                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5025                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5026                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5027                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5028         )           )
5029          return Comment;
5030
5031     /* Check if the user is playing in turn.  This is complicated because we
5032        let the user "pick up" a piece before it is his turn.  So the piece he
5033        tried to pick up may have been captured by the time he puts it down!
5034        Therefore we use the color the user is supposed to be playing in this
5035        test, not the color of the piece that is currently on the starting
5036        square---except in EditGame mode, where the user is playing both
5037        sides; fortunately there the capture race can't happen.  (It can
5038        now happen in IcsExamining mode, but that's just too bad.  The user
5039        will get a somewhat confusing message in that case.)
5040        */
5041
5042     switch (gameMode) {
5043       case PlayFromGameFile:
5044       case AnalyzeFile:
5045       case TwoMachinesPlay:
5046       case EndOfGame:
5047       case IcsObserving:
5048       case IcsIdle:
5049         /* We switched into a game mode where moves are not accepted,
5050            perhaps while the mouse button was down. */
5051         return ImpossibleMove;
5052
5053       case MachinePlaysWhite:
5054         /* User is moving for Black */
5055         if (WhiteOnMove(currentMove)) {
5056             DisplayMoveError(_("It is White's turn"));
5057             return ImpossibleMove;
5058         }
5059         break;
5060
5061       case MachinePlaysBlack:
5062         /* User is moving for White */
5063         if (!WhiteOnMove(currentMove)) {
5064             DisplayMoveError(_("It is Black's turn"));
5065             return ImpossibleMove;
5066         }
5067         break;
5068
5069       case EditGame:
5070       case IcsExamining:
5071       case BeginningOfGame:
5072       case AnalyzeMode:
5073       case Training:
5074         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5075             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5076             /* User is moving for Black */
5077             if (WhiteOnMove(currentMove)) {
5078                 DisplayMoveError(_("It is White's turn"));
5079                 return ImpossibleMove;
5080             }
5081         } else {
5082             /* User is moving for White */
5083             if (!WhiteOnMove(currentMove)) {
5084                 DisplayMoveError(_("It is Black's turn"));
5085                 return ImpossibleMove;
5086             }
5087         }
5088         break;
5089
5090       case IcsPlayingBlack:
5091         /* User is moving for Black */
5092         if (WhiteOnMove(currentMove)) {
5093             if (!appData.premove) {
5094                 DisplayMoveError(_("It is White's turn"));
5095             } else if (toX >= 0 && toY >= 0) {
5096                 premoveToX = toX;
5097                 premoveToY = toY;
5098                 premoveFromX = fromX;
5099                 premoveFromY = fromY;
5100                 premovePromoChar = promoChar;
5101                 gotPremove = 1;
5102                 if (appData.debugMode) 
5103                     fprintf(debugFP, "Got premove: fromX %d,"
5104                             "fromY %d, toX %d, toY %d\n",
5105                             fromX, fromY, toX, toY);
5106             }
5107             return ImpossibleMove;
5108         }
5109         break;
5110
5111       case IcsPlayingWhite:
5112         /* User is moving for White */
5113         if (!WhiteOnMove(currentMove)) {
5114             if (!appData.premove) {
5115                 DisplayMoveError(_("It is Black's turn"));
5116             } else if (toX >= 0 && toY >= 0) {
5117                 premoveToX = toX;
5118                 premoveToY = toY;
5119                 premoveFromX = fromX;
5120                 premoveFromY = fromY;
5121                 premovePromoChar = promoChar;
5122                 gotPremove = 1;
5123                 if (appData.debugMode) 
5124                     fprintf(debugFP, "Got premove: fromX %d,"
5125                             "fromY %d, toX %d, toY %d\n",
5126                             fromX, fromY, toX, toY);
5127             }
5128             return ImpossibleMove;
5129         }
5130         break;
5131
5132       default:
5133         break;
5134
5135       case EditPosition:
5136         /* EditPosition, empty square, or different color piece;
5137            click-click move is possible */
5138         if (toX == -2 || toY == -2) {
5139             boards[0][fromY][fromX] = EmptySquare;
5140             return AmbiguousMove;
5141         } else if (toX >= 0 && toY >= 0) {
5142             boards[0][toY][toX] = boards[0][fromY][fromX];
5143             boards[0][fromY][fromX] = EmptySquare;
5144             return AmbiguousMove;
5145         }
5146         return ImpossibleMove;
5147     }
5148
5149     /* [HGM] If move started in holdings, it means a drop */
5150     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5151          if( pup != EmptySquare ) return ImpossibleMove;
5152          if(appData.testLegality) {
5153              /* it would be more logical if LegalityTest() also figured out
5154               * which drops are legal. For now we forbid pawns on back rank.
5155               * Shogi is on its own here...
5156               */
5157              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5158                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5159                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5160          }
5161          return WhiteDrop; /* Not needed to specify white or black yet */
5162     }
5163
5164     userOfferedDraw = FALSE;
5165         
5166     /* [HGM] always test for legality, to get promotion info */
5167     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5168                           epStatus[currentMove], castlingRights[currentMove],
5169                                          fromY, fromX, toY, toX, promoChar);
5170     /* [HGM] but possibly ignore an IllegalMove result */
5171     if (appData.testLegality) {
5172         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5173             DisplayMoveError(_("Illegal move"));
5174             return ImpossibleMove;
5175         }
5176     }
5177 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5178     return moveType;
5179     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5180        function is made into one that returns an OK move type if FinishMove
5181        should be called. This to give the calling driver routine the
5182        opportunity to finish the userMove input with a promotion popup,
5183        without bothering the user with this for invalid or illegal moves */
5184
5185 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5186 }
5187
5188 /* Common tail of UserMoveEvent and DropMenuEvent */
5189 int
5190 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5191      ChessMove moveType;
5192      int fromX, fromY, toX, toY;
5193      /*char*/int promoChar;
5194 {
5195     char *bookHit = 0;
5196 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5197     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5198         // [HGM] superchess: suppress promotions to non-available piece
5199         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5200         if(WhiteOnMove(currentMove)) {
5201             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5202         } else {
5203             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5204         }
5205     }
5206
5207     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5208        move type in caller when we know the move is a legal promotion */
5209     if(moveType == NormalMove && promoChar)
5210         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5211 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5212     /* [HGM] convert drag-and-drop piece drops to standard form */
5213     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5214          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5215            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5216                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5217 //         fromX = boards[currentMove][fromY][fromX];
5218            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5219            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5220            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5221            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5222          fromY = DROP_RANK;
5223     }
5224
5225     /* [HGM] <popupFix> The following if has been moved here from
5226        UserMoveEvent(). Because it seemed to belon here (why not allow
5227        piece drops in training games?), and because it can only be
5228        performed after it is known to what we promote. */
5229     if (gameMode == Training) {
5230       /* compare the move played on the board to the next move in the
5231        * game. If they match, display the move and the opponent's response. 
5232        * If they don't match, display an error message.
5233        */
5234       int saveAnimate;
5235       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5236       CopyBoard(testBoard, boards[currentMove]);
5237       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5238
5239       if (CompareBoards(testBoard, boards[currentMove+1])) {
5240         ForwardInner(currentMove+1);
5241
5242         /* Autoplay the opponent's response.
5243          * if appData.animate was TRUE when Training mode was entered,
5244          * the response will be animated.
5245          */
5246         saveAnimate = appData.animate;
5247         appData.animate = animateTraining;
5248         ForwardInner(currentMove+1);
5249         appData.animate = saveAnimate;
5250
5251         /* check for the end of the game */
5252         if (currentMove >= forwardMostMove) {
5253           gameMode = PlayFromGameFile;
5254           ModeHighlight();
5255           SetTrainingModeOff();
5256           DisplayInformation(_("End of game"));
5257         }
5258       } else {
5259         DisplayError(_("Incorrect move"), 0);
5260       }
5261       return 1;
5262     }
5263
5264   /* Ok, now we know that the move is good, so we can kill
5265      the previous line in Analysis Mode */
5266   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5267     forwardMostMove = currentMove;
5268   }
5269
5270   /* If we need the chess program but it's dead, restart it */
5271   ResurrectChessProgram();
5272
5273   /* A user move restarts a paused game*/
5274   if (pausing)
5275     PauseEvent();
5276
5277   thinkOutput[0] = NULLCHAR;
5278
5279   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5280
5281   if (gameMode == BeginningOfGame) {
5282     if (appData.noChessProgram) {
5283       gameMode = EditGame;
5284       SetGameInfo();
5285     } else {
5286       char buf[MSG_SIZ];
5287       gameMode = MachinePlaysBlack;
5288       StartClocks();
5289       SetGameInfo();
5290       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5291       DisplayTitle(buf);
5292       if (first.sendName) {
5293         sprintf(buf, "name %s\n", gameInfo.white);
5294         SendToProgram(buf, &first);
5295       }
5296       StartClocks();
5297     }
5298     ModeHighlight();
5299   }
5300 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5301   /* Relay move to ICS or chess engine */
5302   if (appData.icsActive) {
5303     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5304         gameMode == IcsExamining) {
5305       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5306       ics_user_moved = 1;
5307     }
5308   } else {
5309     if (first.sendTime && (gameMode == BeginningOfGame ||
5310                            gameMode == MachinePlaysWhite ||
5311                            gameMode == MachinePlaysBlack)) {
5312       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5313     }
5314     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5315          // [HGM] book: if program might be playing, let it use book
5316         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5317         first.maybeThinking = TRUE;
5318     } else SendMoveToProgram(forwardMostMove-1, &first);
5319     if (currentMove == cmailOldMove + 1) {
5320       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5321     }
5322   }
5323
5324   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5325
5326   switch (gameMode) {
5327   case EditGame:
5328     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5329                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5330     case MT_NONE:
5331     case MT_CHECK:
5332       break;
5333     case MT_CHECKMATE:
5334     case MT_STAINMATE:
5335       if (WhiteOnMove(currentMove)) {
5336         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5337       } else {
5338         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5339       }
5340       break;
5341     case MT_STALEMATE:
5342       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5343       break;
5344     }
5345     break;
5346     
5347   case MachinePlaysBlack:
5348   case MachinePlaysWhite:
5349     /* disable certain menu options while machine is thinking */
5350     SetMachineThinkingEnables();
5351     break;
5352
5353   default:
5354     break;
5355   }
5356
5357   if(bookHit) { // [HGM] book: simulate book reply
5358         static char bookMove[MSG_SIZ]; // a bit generous?
5359
5360         programStats.nodes = programStats.depth = programStats.time = 
5361         programStats.score = programStats.got_only_move = 0;
5362         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5363
5364         strcpy(bookMove, "move ");
5365         strcat(bookMove, bookHit);
5366         HandleMachineMove(bookMove, &first);
5367   }
5368   return 1;
5369 }
5370
5371 void
5372 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5373      int fromX, fromY, toX, toY;
5374      int promoChar;
5375 {
5376     /* [HGM] This routine was added to allow calling of its two logical
5377        parts from other modules in the old way. Before, UserMoveEvent()
5378        automatically called FinishMove() if the move was OK, and returned
5379        otherwise. I separated the two, in order to make it possible to
5380        slip a promotion popup in between. But that it always needs two
5381        calls, to the first part, (now called UserMoveTest() ), and to
5382        FinishMove if the first part succeeded. Calls that do not need
5383        to do anything in between, can call this routine the old way. 
5384     */
5385     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5386 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5387     if(moveType == AmbiguousMove)
5388         DrawPosition(FALSE, boards[currentMove]);
5389     else if(moveType != ImpossibleMove && moveType != Comment)
5390         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5391 }
5392
5393 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5394 {
5395 //    char * hint = lastHint;
5396     FrontEndProgramStats stats;
5397
5398     stats.which = cps == &first ? 0 : 1;
5399     stats.depth = cpstats->depth;
5400     stats.nodes = cpstats->nodes;
5401     stats.score = cpstats->score;
5402     stats.time = cpstats->time;
5403     stats.pv = cpstats->movelist;
5404     stats.hint = lastHint;
5405     stats.an_move_index = 0;
5406     stats.an_move_count = 0;
5407
5408     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5409         stats.hint = cpstats->move_name;
5410         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5411         stats.an_move_count = cpstats->nr_moves;
5412     }
5413
5414     SetProgramStats( &stats );
5415 }
5416
5417 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5418 {   // [HGM] book: this routine intercepts moves to simulate book replies
5419     char *bookHit = NULL;
5420
5421     //first determine if the incoming move brings opponent into his book
5422     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5423         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5424     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5425     if(bookHit != NULL && !cps->bookSuspend) {
5426         // make sure opponent is not going to reply after receiving move to book position
5427         SendToProgram("force\n", cps);
5428         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5429     }
5430     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5431     // now arrange restart after book miss
5432     if(bookHit) {
5433         // after a book hit we never send 'go', and the code after the call to this routine
5434         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5435         char buf[MSG_SIZ];
5436         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5437         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5438         SendToProgram(buf, cps);
5439         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5440     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5441         SendToProgram("go\n", cps);
5442         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5443     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5444         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5445             SendToProgram("go\n", cps); 
5446         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5447     }
5448     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5449 }
5450
5451 char *savedMessage;
5452 ChessProgramState *savedState;
5453 void DeferredBookMove(void)
5454 {
5455         if(savedState->lastPing != savedState->lastPong)
5456                     ScheduleDelayedEvent(DeferredBookMove, 10);
5457         else
5458         HandleMachineMove(savedMessage, savedState);
5459 }
5460
5461 void
5462 HandleMachineMove(message, cps)
5463      char *message;
5464      ChessProgramState *cps;
5465 {
5466     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5467     char realname[MSG_SIZ];
5468     int fromX, fromY, toX, toY;
5469     ChessMove moveType;
5470     char promoChar;
5471     char *p;
5472     int machineWhite;
5473     char *bookHit;
5474
5475 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5476     /*
5477      * Kludge to ignore BEL characters
5478      */
5479     while (*message == '\007') message++;
5480
5481     /*
5482      * [HGM] engine debug message: ignore lines starting with '#' character
5483      */
5484     if(cps->debug && *message == '#') return;
5485
5486     /*
5487      * Look for book output
5488      */
5489     if (cps == &first && bookRequested) {
5490         if (message[0] == '\t' || message[0] == ' ') {
5491             /* Part of the book output is here; append it */
5492             strcat(bookOutput, message);
5493             strcat(bookOutput, "  \n");
5494             return;
5495         } else if (bookOutput[0] != NULLCHAR) {
5496             /* All of book output has arrived; display it */
5497             char *p = bookOutput;
5498             while (*p != NULLCHAR) {
5499                 if (*p == '\t') *p = ' ';
5500                 p++;
5501             }
5502             DisplayInformation(bookOutput);
5503             bookRequested = FALSE;
5504             /* Fall through to parse the current output */
5505         }
5506     }
5507
5508     /*
5509      * Look for machine move.
5510      */
5511     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5512         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5513     {
5514         /* This method is only useful on engines that support ping */
5515         if (cps->lastPing != cps->lastPong) {
5516           if (gameMode == BeginningOfGame) {
5517             /* Extra move from before last new; ignore */
5518             if (appData.debugMode) {
5519                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5520             }
5521           } else {
5522             if (appData.debugMode) {
5523                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5524                         cps->which, gameMode);
5525             }
5526
5527             SendToProgram("undo\n", cps);
5528           }
5529           return;
5530         }
5531
5532         switch (gameMode) {
5533           case BeginningOfGame:
5534             /* Extra move from before last reset; ignore */
5535             if (appData.debugMode) {
5536                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5537             }
5538             return;
5539
5540           case EndOfGame:
5541           case IcsIdle:
5542           default:
5543             /* Extra move after we tried to stop.  The mode test is
5544                not a reliable way of detecting this problem, but it's
5545                the best we can do on engines that don't support ping.
5546             */
5547             if (appData.debugMode) {
5548                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5549                         cps->which, gameMode);
5550             }
5551             SendToProgram("undo\n", cps);
5552             return;
5553
5554           case MachinePlaysWhite:
5555           case IcsPlayingWhite:
5556             machineWhite = TRUE;
5557             break;
5558
5559           case MachinePlaysBlack:
5560           case IcsPlayingBlack:
5561             machineWhite = FALSE;
5562             break;
5563
5564           case TwoMachinesPlay:
5565             machineWhite = (cps->twoMachinesColor[0] == 'w');
5566             break;
5567         }
5568         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5569             if (appData.debugMode) {
5570                 fprintf(debugFP,
5571                         "Ignoring move out of turn by %s, gameMode %d"
5572                         ", forwardMost %d\n",
5573                         cps->which, gameMode, forwardMostMove);
5574             }
5575             return;
5576         }
5577
5578     if (appData.debugMode) { int f = forwardMostMove;
5579         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5580                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5581     }
5582         if(cps->alphaRank) AlphaRank(machineMove, 4);
5583         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5584                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5585             /* Machine move could not be parsed; ignore it. */
5586             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5587                     machineMove, cps->which);
5588             DisplayError(buf1, 0);
5589             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5590                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5591             if (gameMode == TwoMachinesPlay) {
5592               GameEnds(machineWhite ? BlackWins : WhiteWins,
5593                        buf1, GE_XBOARD);
5594             }
5595             return;
5596         }
5597
5598         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5599         /* So we have to redo legality test with true e.p. status here,  */
5600         /* to make sure an illegal e.p. capture does not slip through,   */
5601         /* to cause a forfeit on a justified illegal-move complaint      */
5602         /* of the opponent.                                              */
5603         if( gameMode==TwoMachinesPlay && appData.testLegality
5604             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5605                                                               ) {
5606            ChessMove moveType;
5607            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5608                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5609                              fromY, fromX, toY, toX, promoChar);
5610             if (appData.debugMode) {
5611                 int i;
5612                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5613                     castlingRights[forwardMostMove][i], castlingRank[i]);
5614                 fprintf(debugFP, "castling rights\n");
5615             }
5616             if(moveType == IllegalMove) {
5617                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5618                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5619                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5620                            buf1, GE_XBOARD);
5621                 return;
5622            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5623            /* [HGM] Kludge to handle engines that send FRC-style castling
5624               when they shouldn't (like TSCP-Gothic) */
5625            switch(moveType) {
5626              case WhiteASideCastleFR:
5627              case BlackASideCastleFR:
5628                toX+=2;
5629                currentMoveString[2]++;
5630                break;
5631              case WhiteHSideCastleFR:
5632              case BlackHSideCastleFR:
5633                toX--;
5634                currentMoveString[2]--;
5635                break;
5636              default: ; // nothing to do, but suppresses warning of pedantic compilers
5637            }
5638         }
5639         hintRequested = FALSE;
5640         lastHint[0] = NULLCHAR;
5641         bookRequested = FALSE;
5642         /* Program may be pondering now */
5643         cps->maybeThinking = TRUE;
5644         if (cps->sendTime == 2) cps->sendTime = 1;
5645         if (cps->offeredDraw) cps->offeredDraw--;
5646
5647 #if ZIPPY
5648         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5649             first.initDone) {
5650           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5651           ics_user_moved = 1;
5652           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5653                 char buf[3*MSG_SIZ];
5654
5655                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5656                         programStats.score / 100.,
5657                         programStats.depth,
5658                         programStats.time / 100.,
5659                         (unsigned int)programStats.nodes,
5660                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5661                         programStats.movelist);
5662                 SendToICS(buf);
5663 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5664           }
5665         }
5666 #endif
5667         /* currentMoveString is set as a side-effect of ParseOneMove */
5668         strcpy(machineMove, currentMoveString);
5669         strcat(machineMove, "\n");
5670         strcpy(moveList[forwardMostMove], machineMove);
5671
5672         /* [AS] Save move info and clear stats for next move */
5673         pvInfoList[ forwardMostMove ].score = programStats.score;
5674         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5675         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5676         ClearProgramStats();
5677         thinkOutput[0] = NULLCHAR;
5678         hiddenThinkOutputState = 0;
5679
5680         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5681
5682         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5683         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5684             int count = 0;
5685
5686             while( count < adjudicateLossPlies ) {
5687                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5688
5689                 if( count & 1 ) {
5690                     score = -score; /* Flip score for winning side */
5691                 }
5692
5693                 if( score > adjudicateLossThreshold ) {
5694                     break;
5695                 }
5696
5697                 count++;
5698             }
5699
5700             if( count >= adjudicateLossPlies ) {
5701                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5702
5703                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5704                     "Xboard adjudication", 
5705                     GE_XBOARD );
5706
5707                 return;
5708             }
5709         }
5710
5711         if( gameMode == TwoMachinesPlay ) {
5712           // [HGM] some adjudications useful with buggy engines
5713             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5714           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5715
5716
5717             if( appData.testLegality )
5718             {   /* [HGM] Some more adjudications for obstinate engines */
5719                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5720                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5721                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5722                 static int moveCount = 6;
5723                 ChessMove result;
5724                 char *reason = NULL;
5725
5726                 /* Count what is on board. */
5727                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5728                 {   ChessSquare p = boards[forwardMostMove][i][j];
5729                     int m=i;
5730
5731                     switch((int) p)
5732                     {   /* count B,N,R and other of each side */
5733                         case WhiteKing:
5734                         case BlackKing:
5735                              NrK++; break; // [HGM] atomic: count Kings
5736                         case WhiteKnight:
5737                              NrWN++; break;
5738                         case WhiteBishop:
5739                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5740                              bishopsColor |= 1 << ((i^j)&1);
5741                              NrWB++; break;
5742                         case BlackKnight:
5743                              NrBN++; break;
5744                         case BlackBishop:
5745                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5746                              bishopsColor |= 1 << ((i^j)&1);
5747                              NrBB++; break;
5748                         case WhiteRook:
5749                              NrWR++; break;
5750                         case BlackRook:
5751                              NrBR++; break;
5752                         case WhiteQueen:
5753                              NrWQ++; break;
5754                         case BlackQueen:
5755                              NrBQ++; break;
5756                         case EmptySquare: 
5757                              break;
5758                         case BlackPawn:
5759                              m = 7-i;
5760                         case WhitePawn:
5761                              PawnAdvance += m; NrPawns++;
5762                     }
5763                     NrPieces += (p != EmptySquare);
5764                     NrW += ((int)p < (int)BlackPawn);
5765                     if(gameInfo.variant == VariantXiangqi && 
5766                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5767                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5768                         NrW -= ((int)p < (int)BlackPawn);
5769                     }
5770                 }
5771
5772                 /* Some material-based adjudications that have to be made before stalemate test */
5773                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5774                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5775                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5776                      if(appData.checkMates) {
5777                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5778                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5779                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5780                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5781                          return;
5782                      }
5783                 }
5784
5785                 /* Bare King in Shatranj (loses) or Losers (wins) */
5786                 if( NrW == 1 || NrPieces - NrW == 1) {
5787                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5788                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5789                      if(appData.checkMates) {
5790                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5791                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5792                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5793                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5794                          return;
5795                      }
5796                   } else
5797                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5798                   {    /* bare King */
5799                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5800                         if(appData.checkMates) {
5801                             /* but only adjudicate if adjudication enabled */
5802                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5803                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5804                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5805                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5806                             return;
5807                         }
5808                   }
5809                 } else bare = 1;
5810
5811
5812             // don't wait for engine to announce game end if we can judge ourselves
5813             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5814                                        castlingRights[forwardMostMove]) ) {
5815               case MT_CHECK:
5816                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5817                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5818                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5819                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5820                             checkCnt++;
5821                         if(checkCnt >= 2) {
5822                             reason = "Xboard adjudication: 3rd check";
5823                             epStatus[forwardMostMove] = EP_CHECKMATE;
5824                             break;
5825                         }
5826                     }
5827                 }
5828               case MT_NONE:
5829               default:
5830                 break;
5831               case MT_STALEMATE:
5832               case MT_STAINMATE:
5833                 reason = "Xboard adjudication: Stalemate";
5834                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5835                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5836                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5837                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5838                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5839                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5840                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5841                                                                         EP_CHECKMATE : EP_WINS);
5842                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5843                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5844                 }
5845                 break;
5846               case MT_CHECKMATE:
5847                 reason = "Xboard adjudication: Checkmate";
5848                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5849                 break;
5850             }
5851
5852                 switch(i = epStatus[forwardMostMove]) {
5853                     case EP_STALEMATE:
5854                         result = GameIsDrawn; break;
5855                     case EP_CHECKMATE:
5856                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5857                     case EP_WINS:
5858                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5859                     default:
5860                         result = (ChessMove) 0;
5861                 }
5862                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5863                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5864                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5865                     GameEnds( result, reason, GE_XBOARD );
5866                     return;
5867                 }
5868
5869                 /* Next absolutely insufficient mating material. */
5870                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5871                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5872                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5873                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5874                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5875
5876                      /* always flag draws, for judging claims */
5877                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5878
5879                      if(appData.materialDraws) {
5880                          /* but only adjudicate them if adjudication enabled */
5881                          SendToProgram("force\n", cps->other); // suppress reply
5882                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5883                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5884                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5885                          return;
5886                      }
5887                 }
5888
5889                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5890                 if(NrPieces == 4 && 
5891                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5892                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5893                    || NrWN==2 || NrBN==2     /* KNNK */
5894                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5895                   ) ) {
5896                      if(--moveCount < 0 && appData.trivialDraws)
5897                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5898                           SendToProgram("force\n", cps->other); // suppress reply
5899                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5900                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5901                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5902                           return;
5903                      }
5904                 } else moveCount = 6;
5905             }
5906           }
5907           
5908           if (appData.debugMode) { int i;
5909             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5910                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5911                     appData.drawRepeats);
5912             for( i=forwardMostMove; i>=backwardMostMove; i-- )
5913               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5914             
5915           }
5916
5917                 /* Check for rep-draws */
5918                 count = 0;
5919                 for(k = forwardMostMove-2;
5920                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5921                         epStatus[k] < EP_UNKNOWN &&
5922                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5923                     k-=2)
5924                 {   int rights=0;
5925                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5926                         /* compare castling rights */
5927                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5928                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5929                                 rights++; /* King lost rights, while rook still had them */
5930                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5931                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5932                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5933                                    rights++; /* but at least one rook lost them */
5934                         }
5935                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5936                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5937                                 rights++; 
5938                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5939                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5940                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5941                                    rights++;
5942                         }
5943                         if( rights == 0 && ++count > appData.drawRepeats-2
5944                             && appData.drawRepeats > 1) {
5945                              /* adjudicate after user-specified nr of repeats */
5946                              SendToProgram("force\n", cps->other); // suppress reply
5947                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5948                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5949                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5950                                 // [HGM] xiangqi: check for forbidden perpetuals
5951                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5952                                 for(m=forwardMostMove; m>k; m-=2) {
5953                                     if(MateTest(boards[m], PosFlags(m), 
5954                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5955                                         ourPerpetual = 0; // the current mover did not always check
5956                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5957                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5958                                         hisPerpetual = 0; // the opponent did not always check
5959                                 }
5960                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5961                                                                         ourPerpetual, hisPerpetual);
5962                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5963                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5964                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5965                                     return;
5966                                 }
5967                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5968                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5969                                 // Now check for perpetual chases
5970                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5971                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5972                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5973                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5974                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5975                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5976                                         return;
5977                                     }
5978                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5979                                         break; // Abort repetition-checking loop.
5980                                 }
5981                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5982                              }
5983                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5984                              return;
5985                         }
5986                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5987                              epStatus[forwardMostMove] = EP_REP_DRAW;
5988                     }
5989                 }
5990
5991                 /* Now we test for 50-move draws. Determine ply count */
5992                 count = forwardMostMove;
5993                 /* look for last irreversble move */
5994                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5995                     count--;
5996                 /* if we hit starting position, add initial plies */
5997                 if( count == backwardMostMove )
5998                     count -= initialRulePlies;
5999                 count = forwardMostMove - count; 
6000                 if( count >= 100)
6001                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6002                          /* this is used to judge if draw claims are legal */
6003                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6004                          SendToProgram("force\n", cps->other); // suppress reply
6005                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6006                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6007                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6008                          return;
6009                 }
6010
6011                 /* if draw offer is pending, treat it as a draw claim
6012                  * when draw condition present, to allow engines a way to
6013                  * claim draws before making their move to avoid a race
6014                  * condition occurring after their move
6015                  */
6016                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6017                          char *p = NULL;
6018                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6019                              p = "Draw claim: 50-move rule";
6020                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6021                              p = "Draw claim: 3-fold repetition";
6022                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6023                              p = "Draw claim: insufficient mating material";
6024                          if( p != NULL ) {
6025                              SendToProgram("force\n", cps->other); // suppress reply
6026                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6027                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6028                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6029                              return;
6030                          }
6031                 }
6032
6033
6034                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6035                     SendToProgram("force\n", cps->other); // suppress reply
6036                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6037                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6038
6039                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6040
6041                     return;
6042                 }
6043         }
6044
6045         bookHit = NULL;
6046         if (gameMode == TwoMachinesPlay) {
6047             /* [HGM] relaying draw offers moved to after reception of move */
6048             /* and interpreting offer as claim if it brings draw condition */
6049             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6050                 SendToProgram("draw\n", cps->other);
6051             }
6052             if (cps->other->sendTime) {
6053                 SendTimeRemaining(cps->other,
6054                                   cps->other->twoMachinesColor[0] == 'w');
6055             }
6056             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6057             if (firstMove && !bookHit) {
6058                 firstMove = FALSE;
6059                 if (cps->other->useColors) {
6060                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6061                 }
6062                 SendToProgram("go\n", cps->other);
6063             }
6064             cps->other->maybeThinking = TRUE;
6065         }
6066
6067         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6068         
6069         if (!pausing && appData.ringBellAfterMoves) {
6070             RingBell();
6071         }
6072
6073         /* 
6074          * Reenable menu items that were disabled while
6075          * machine was thinking
6076          */
6077         if (gameMode != TwoMachinesPlay)
6078             SetUserThinkingEnables();
6079
6080         // [HGM] book: after book hit opponent has received move and is now in force mode
6081         // force the book reply into it, and then fake that it outputted this move by jumping
6082         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6083         if(bookHit) {
6084                 static char bookMove[MSG_SIZ]; // a bit generous?
6085
6086                 strcpy(bookMove, "move ");
6087                 strcat(bookMove, bookHit);
6088                 message = bookMove;
6089                 cps = cps->other;
6090                 programStats.nodes = programStats.depth = programStats.time = 
6091                 programStats.score = programStats.got_only_move = 0;
6092                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6093
6094                 if(cps->lastPing != cps->lastPong) {
6095                     savedMessage = message; // args for deferred call
6096                     savedState = cps;
6097                     ScheduleDelayedEvent(DeferredBookMove, 10);
6098                     return;
6099                 }
6100                 goto FakeBookMove;
6101         }
6102
6103         return;
6104     }
6105
6106     /* Set special modes for chess engines.  Later something general
6107      *  could be added here; for now there is just one kludge feature,
6108      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6109      *  when "xboard" is given as an interactive command.
6110      */
6111     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6112         cps->useSigint = FALSE;
6113         cps->useSigterm = FALSE;
6114     }
6115     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6116       ParseFeatures(message+8, cps);
6117       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6118     }
6119
6120     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6121      * want this, I was asked to put it in, and obliged.
6122      */
6123     if (!strncmp(message, "setboard ", 9)) {
6124         Board initial_position; int i;
6125
6126         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6127
6128         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6129             DisplayError(_("Bad FEN received from engine"), 0);
6130             return ;
6131         } else {
6132            Reset(FALSE, FALSE);
6133            CopyBoard(boards[0], initial_position);
6134            initialRulePlies = FENrulePlies;
6135            epStatus[0] = FENepStatus;
6136            for( i=0; i<nrCastlingRights; i++ )
6137                 castlingRights[0][i] = FENcastlingRights[i];
6138            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6139            else gameMode = MachinePlaysBlack;                 
6140            DrawPosition(FALSE, boards[currentMove]);
6141         }
6142         return;
6143     }
6144
6145     /*
6146      * Look for communication commands
6147      */
6148     if (!strncmp(message, "telluser ", 9)) {
6149         DisplayNote(message + 9);
6150         return;
6151     }
6152     if (!strncmp(message, "tellusererror ", 14)) {
6153         DisplayError(message + 14, 0);
6154         return;
6155     }
6156     if (!strncmp(message, "tellopponent ", 13)) {
6157       if (appData.icsActive) {
6158         if (loggedOn) {
6159           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6160           SendToICS(buf1);
6161         }
6162       } else {
6163         DisplayNote(message + 13);
6164       }
6165       return;
6166     }
6167     if (!strncmp(message, "tellothers ", 11)) {
6168       if (appData.icsActive) {
6169         if (loggedOn) {
6170           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6171           SendToICS(buf1);
6172         }
6173       }
6174       return;
6175     }
6176     if (!strncmp(message, "tellall ", 8)) {
6177       if (appData.icsActive) {
6178         if (loggedOn) {
6179           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6180           SendToICS(buf1);
6181         }
6182       } else {
6183         DisplayNote(message + 8);
6184       }
6185       return;
6186     }
6187     if (strncmp(message, "warning", 7) == 0) {
6188         /* Undocumented feature, use tellusererror in new code */
6189         DisplayError(message, 0);
6190         return;
6191     }
6192     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6193         strcpy(realname, cps->tidy);
6194         strcat(realname, " query");
6195         AskQuestion(realname, buf2, buf1, cps->pr);
6196         return;
6197     }
6198     /* Commands from the engine directly to ICS.  We don't allow these to be 
6199      *  sent until we are logged on. Crafty kibitzes have been known to 
6200      *  interfere with the login process.
6201      */
6202     if (loggedOn) {
6203         if (!strncmp(message, "tellics ", 8)) {
6204             SendToICS(message + 8);
6205             SendToICS("\n");
6206             return;
6207         }
6208         if (!strncmp(message, "tellicsnoalias ", 15)) {
6209             SendToICS(ics_prefix);
6210             SendToICS(message + 15);
6211             SendToICS("\n");
6212             return;
6213         }
6214         /* The following are for backward compatibility only */
6215         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6216             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6217             SendToICS(ics_prefix);
6218             SendToICS(message);
6219             SendToICS("\n");
6220             return;
6221         }
6222     }
6223     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6224         return;
6225     }
6226     /*
6227      * If the move is illegal, cancel it and redraw the board.
6228      * Also deal with other error cases.  Matching is rather loose
6229      * here to accommodate engines written before the spec.
6230      */
6231     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6232         strncmp(message, "Error", 5) == 0) {
6233         if (StrStr(message, "name") || 
6234             StrStr(message, "rating") || StrStr(message, "?") ||
6235             StrStr(message, "result") || StrStr(message, "board") ||
6236             StrStr(message, "bk") || StrStr(message, "computer") ||
6237             StrStr(message, "variant") || StrStr(message, "hint") ||
6238             StrStr(message, "random") || StrStr(message, "depth") ||
6239             StrStr(message, "accepted")) {
6240             return;
6241         }
6242         if (StrStr(message, "protover")) {
6243           /* Program is responding to input, so it's apparently done
6244              initializing, and this error message indicates it is
6245              protocol version 1.  So we don't need to wait any longer
6246              for it to initialize and send feature commands. */
6247           FeatureDone(cps, 1);
6248           cps->protocolVersion = 1;
6249           return;
6250         }
6251         cps->maybeThinking = FALSE;
6252
6253         if (StrStr(message, "draw")) {
6254             /* Program doesn't have "draw" command */
6255             cps->sendDrawOffers = 0;
6256             return;
6257         }
6258         if (cps->sendTime != 1 &&
6259             (StrStr(message, "time") || StrStr(message, "otim"))) {
6260           /* Program apparently doesn't have "time" or "otim" command */
6261           cps->sendTime = 0;
6262           return;
6263         }
6264         if (StrStr(message, "analyze")) {
6265             cps->analysisSupport = FALSE;
6266             cps->analyzing = FALSE;
6267             Reset(FALSE, TRUE);
6268             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6269             DisplayError(buf2, 0);
6270             return;
6271         }
6272         if (StrStr(message, "(no matching move)st")) {
6273           /* Special kludge for GNU Chess 4 only */
6274           cps->stKludge = TRUE;
6275           SendTimeControl(cps, movesPerSession, timeControl,
6276                           timeIncrement, appData.searchDepth,
6277                           searchTime);
6278           return;
6279         }
6280         if (StrStr(message, "(no matching move)sd")) {
6281           /* Special kludge for GNU Chess 4 only */
6282           cps->sdKludge = TRUE;
6283           SendTimeControl(cps, movesPerSession, timeControl,
6284                           timeIncrement, appData.searchDepth,
6285                           searchTime);
6286           return;
6287         }
6288         if (!StrStr(message, "llegal")) {
6289             return;
6290         }
6291         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6292             gameMode == IcsIdle) return;
6293         if (forwardMostMove <= backwardMostMove) return;
6294         if (pausing) PauseEvent();
6295       if(appData.forceIllegal) {
6296             // [HGM] illegal: machine refused move; force position after move into it
6297           SendToProgram("force\n", cps);
6298           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6299                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6300                 // when black is to move, while there might be nothing on a2 or black
6301                 // might already have the move. So send the board as if white has the move.
6302                 // But first we must change the stm of the engine, as it refused the last move
6303                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6304                 if(WhiteOnMove(forwardMostMove)) {
6305                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6306                     SendBoard(cps, forwardMostMove); // kludgeless board
6307                 } else {
6308                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6309                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6310                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6311                 }
6312           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6313             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6314                  gameMode == TwoMachinesPlay)
6315               SendToProgram("go\n", cps);
6316             return;
6317       } else
6318         if (gameMode == PlayFromGameFile) {
6319             /* Stop reading this game file */
6320             gameMode = EditGame;
6321             ModeHighlight();
6322         }
6323         currentMove = --forwardMostMove;
6324         DisplayMove(currentMove-1); /* before DisplayMoveError */
6325         SwitchClocks();
6326         DisplayBothClocks();
6327         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6328                 parseList[currentMove], cps->which);
6329         DisplayMoveError(buf1);
6330         DrawPosition(FALSE, boards[currentMove]);
6331
6332         /* [HGM] illegal-move claim should forfeit game when Xboard */
6333         /* only passes fully legal moves                            */
6334         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6335             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6336                                 "False illegal-move claim", GE_XBOARD );
6337         }
6338         return;
6339     }
6340     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6341         /* Program has a broken "time" command that
6342            outputs a string not ending in newline.
6343            Don't use it. */
6344         cps->sendTime = 0;
6345     }
6346     
6347     /*
6348      * If chess program startup fails, exit with an error message.
6349      * Attempts to recover here are futile.
6350      */
6351     if ((StrStr(message, "unknown host") != NULL)
6352         || (StrStr(message, "No remote directory") != NULL)
6353         || (StrStr(message, "not found") != NULL)
6354         || (StrStr(message, "No such file") != NULL)
6355         || (StrStr(message, "can't alloc") != NULL)
6356         || (StrStr(message, "Permission denied") != NULL)) {
6357
6358         cps->maybeThinking = FALSE;
6359         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6360                 cps->which, cps->program, cps->host, message);
6361         RemoveInputSource(cps->isr);
6362         DisplayFatalError(buf1, 0, 1);
6363         return;
6364     }
6365     
6366     /* 
6367      * Look for hint output
6368      */
6369     if (sscanf(message, "Hint: %s", buf1) == 1) {
6370         if (cps == &first && hintRequested) {
6371             hintRequested = FALSE;
6372             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6373                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6374                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6375                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6376                                     fromY, fromX, toY, toX, promoChar, buf1);
6377                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6378                 DisplayInformation(buf2);
6379             } else {
6380                 /* Hint move could not be parsed!? */
6381               snprintf(buf2, sizeof(buf2),
6382                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6383                         buf1, cps->which);
6384                 DisplayError(buf2, 0);
6385             }
6386         } else {
6387             strcpy(lastHint, buf1);
6388         }
6389         return;
6390     }
6391
6392     /*
6393      * Ignore other messages if game is not in progress
6394      */
6395     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6396         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6397
6398     /*
6399      * look for win, lose, draw, or draw offer
6400      */
6401     if (strncmp(message, "1-0", 3) == 0) {
6402         char *p, *q, *r = "";
6403         p = strchr(message, '{');
6404         if (p) {
6405             q = strchr(p, '}');
6406             if (q) {
6407                 *q = NULLCHAR;
6408                 r = p + 1;
6409             }
6410         }
6411         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6412         return;
6413     } else if (strncmp(message, "0-1", 3) == 0) {
6414         char *p, *q, *r = "";
6415         p = strchr(message, '{');
6416         if (p) {
6417             q = strchr(p, '}');
6418             if (q) {
6419                 *q = NULLCHAR;
6420                 r = p + 1;
6421             }
6422         }
6423         /* Kludge for Arasan 4.1 bug */
6424         if (strcmp(r, "Black resigns") == 0) {
6425             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6426             return;
6427         }
6428         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6429         return;
6430     } else if (strncmp(message, "1/2", 3) == 0) {
6431         char *p, *q, *r = "";
6432         p = strchr(message, '{');
6433         if (p) {
6434             q = strchr(p, '}');
6435             if (q) {
6436                 *q = NULLCHAR;
6437                 r = p + 1;
6438             }
6439         }
6440             
6441         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6442         return;
6443
6444     } else if (strncmp(message, "White resign", 12) == 0) {
6445         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6446         return;
6447     } else if (strncmp(message, "Black resign", 12) == 0) {
6448         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6449         return;
6450     } else if (strncmp(message, "White matches", 13) == 0 ||
6451                strncmp(message, "Black matches", 13) == 0   ) {
6452         /* [HGM] ignore GNUShogi noises */
6453         return;
6454     } else if (strncmp(message, "White", 5) == 0 &&
6455                message[5] != '(' &&
6456                StrStr(message, "Black") == NULL) {
6457         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6458         return;
6459     } else if (strncmp(message, "Black", 5) == 0 &&
6460                message[5] != '(') {
6461         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6462         return;
6463     } else if (strcmp(message, "resign") == 0 ||
6464                strcmp(message, "computer resigns") == 0) {
6465         switch (gameMode) {
6466           case MachinePlaysBlack:
6467           case IcsPlayingBlack:
6468             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6469             break;
6470           case MachinePlaysWhite:
6471           case IcsPlayingWhite:
6472             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6473             break;
6474           case TwoMachinesPlay:
6475             if (cps->twoMachinesColor[0] == 'w')
6476               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6477             else
6478               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6479             break;
6480           default:
6481             /* can't happen */
6482             break;
6483         }
6484         return;
6485     } else if (strncmp(message, "opponent mates", 14) == 0) {
6486         switch (gameMode) {
6487           case MachinePlaysBlack:
6488           case IcsPlayingBlack:
6489             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6490             break;
6491           case MachinePlaysWhite:
6492           case IcsPlayingWhite:
6493             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6494             break;
6495           case TwoMachinesPlay:
6496             if (cps->twoMachinesColor[0] == 'w')
6497               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6498             else
6499               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6500             break;
6501           default:
6502             /* can't happen */
6503             break;
6504         }
6505         return;
6506     } else if (strncmp(message, "computer mates", 14) == 0) {
6507         switch (gameMode) {
6508           case MachinePlaysBlack:
6509           case IcsPlayingBlack:
6510             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6511             break;
6512           case MachinePlaysWhite:
6513           case IcsPlayingWhite:
6514             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6515             break;
6516           case TwoMachinesPlay:
6517             if (cps->twoMachinesColor[0] == 'w')
6518               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6519             else
6520               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6521             break;
6522           default:
6523             /* can't happen */
6524             break;
6525         }
6526         return;
6527     } else if (strncmp(message, "checkmate", 9) == 0) {
6528         if (WhiteOnMove(forwardMostMove)) {
6529             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6530         } else {
6531             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6532         }
6533         return;
6534     } else if (strstr(message, "Draw") != NULL ||
6535                strstr(message, "game is a draw") != NULL) {
6536         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6537         return;
6538     } else if (strstr(message, "offer") != NULL &&
6539                strstr(message, "draw") != NULL) {
6540 #if ZIPPY
6541         if (appData.zippyPlay && first.initDone) {
6542             /* Relay offer to ICS */
6543             SendToICS(ics_prefix);
6544             SendToICS("draw\n");
6545         }
6546 #endif
6547         cps->offeredDraw = 2; /* valid until this engine moves twice */
6548         if (gameMode == TwoMachinesPlay) {
6549             if (cps->other->offeredDraw) {
6550                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6551             /* [HGM] in two-machine mode we delay relaying draw offer      */
6552             /* until after we also have move, to see if it is really claim */
6553             }
6554         } else if (gameMode == MachinePlaysWhite ||
6555                    gameMode == MachinePlaysBlack) {
6556           if (userOfferedDraw) {
6557             DisplayInformation(_("Machine accepts your draw offer"));
6558             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6559           } else {
6560             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6561           }
6562         }
6563     }
6564
6565     
6566     /*
6567      * Look for thinking output
6568      */
6569     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6570           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6571                                 ) {
6572         int plylev, mvleft, mvtot, curscore, time;
6573         char mvname[MOVE_LEN];
6574         u64 nodes; // [DM]
6575         char plyext;
6576         int ignore = FALSE;
6577         int prefixHint = FALSE;
6578         mvname[0] = NULLCHAR;
6579
6580         switch (gameMode) {
6581           case MachinePlaysBlack:
6582           case IcsPlayingBlack:
6583             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6584             break;
6585           case MachinePlaysWhite:
6586           case IcsPlayingWhite:
6587             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6588             break;
6589           case AnalyzeMode:
6590           case AnalyzeFile:
6591             break;
6592           case IcsObserving: /* [DM] icsEngineAnalyze */
6593             if (!appData.icsEngineAnalyze) ignore = TRUE;
6594             break;
6595           case TwoMachinesPlay:
6596             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6597                 ignore = TRUE;
6598             }
6599             break;
6600           default:
6601             ignore = TRUE;
6602             break;
6603         }
6604
6605         if (!ignore) {
6606             buf1[0] = NULLCHAR;
6607             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6608                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6609
6610                 if (plyext != ' ' && plyext != '\t') {
6611                     time *= 100;
6612                 }
6613
6614                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6615                 if( cps->scoreIsAbsolute && 
6616                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6617                 {
6618                     curscore = -curscore;
6619                 }
6620
6621
6622                 programStats.depth = plylev;
6623                 programStats.nodes = nodes;
6624                 programStats.time = time;
6625                 programStats.score = curscore;
6626                 programStats.got_only_move = 0;
6627
6628                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6629                         int ticklen;
6630
6631                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6632                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6633                         if(WhiteOnMove(forwardMostMove)) 
6634                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6635                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6636                 }
6637
6638                 /* Buffer overflow protection */
6639                 if (buf1[0] != NULLCHAR) {
6640                     if (strlen(buf1) >= sizeof(programStats.movelist)
6641                         && appData.debugMode) {
6642                         fprintf(debugFP,
6643                                 "PV is too long; using the first %d bytes.\n",
6644                                 sizeof(programStats.movelist) - 1);
6645                     }
6646
6647                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6648                 } else {
6649                     sprintf(programStats.movelist, " no PV\n");
6650                 }
6651
6652                 if (programStats.seen_stat) {
6653                     programStats.ok_to_send = 1;
6654                 }
6655
6656                 if (strchr(programStats.movelist, '(') != NULL) {
6657                     programStats.line_is_book = 1;
6658                     programStats.nr_moves = 0;
6659                     programStats.moves_left = 0;
6660                 } else {
6661                     programStats.line_is_book = 0;
6662                 }
6663
6664                 SendProgramStatsToFrontend( cps, &programStats );
6665
6666                 /* 
6667                     [AS] Protect the thinkOutput buffer from overflow... this
6668                     is only useful if buf1 hasn't overflowed first!
6669                 */
6670                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6671                         plylev, 
6672                         (gameMode == TwoMachinesPlay ?
6673                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6674                         ((double) curscore) / 100.0,
6675                         prefixHint ? lastHint : "",
6676                         prefixHint ? " " : "" );
6677
6678                 if( buf1[0] != NULLCHAR ) {
6679                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6680
6681                     if( strlen(buf1) > max_len ) {
6682                         if( appData.debugMode) {
6683                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6684                         }
6685                         buf1[max_len+1] = '\0';
6686                     }
6687
6688                     strcat( thinkOutput, buf1 );
6689                 }
6690
6691                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6692                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6693                     DisplayMove(currentMove - 1);
6694                 }
6695                 return;
6696
6697             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6698                 /* crafty (9.25+) says "(only move) <move>"
6699                  * if there is only 1 legal move
6700                  */
6701                 sscanf(p, "(only move) %s", buf1);
6702                 sprintf(thinkOutput, "%s (only move)", buf1);
6703                 sprintf(programStats.movelist, "%s (only move)", buf1);
6704                 programStats.depth = 1;
6705                 programStats.nr_moves = 1;
6706                 programStats.moves_left = 1;
6707                 programStats.nodes = 1;
6708                 programStats.time = 1;
6709                 programStats.got_only_move = 1;
6710
6711                 /* Not really, but we also use this member to
6712                    mean "line isn't going to change" (Crafty
6713                    isn't searching, so stats won't change) */
6714                 programStats.line_is_book = 1;
6715
6716                 SendProgramStatsToFrontend( cps, &programStats );
6717                 
6718                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6719                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6720                     DisplayMove(currentMove - 1);
6721                 }
6722                 return;
6723             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6724                               &time, &nodes, &plylev, &mvleft,
6725                               &mvtot, mvname) >= 5) {
6726                 /* The stat01: line is from Crafty (9.29+) in response
6727                    to the "." command */
6728                 programStats.seen_stat = 1;
6729                 cps->maybeThinking = TRUE;
6730
6731                 if (programStats.got_only_move || !appData.periodicUpdates)
6732                   return;
6733
6734                 programStats.depth = plylev;
6735                 programStats.time = time;
6736                 programStats.nodes = nodes;
6737                 programStats.moves_left = mvleft;
6738                 programStats.nr_moves = mvtot;
6739                 strcpy(programStats.move_name, mvname);
6740                 programStats.ok_to_send = 1;
6741                 programStats.movelist[0] = '\0';
6742
6743                 SendProgramStatsToFrontend( cps, &programStats );
6744
6745                 return;
6746
6747             } else if (strncmp(message,"++",2) == 0) {
6748                 /* Crafty 9.29+ outputs this */
6749                 programStats.got_fail = 2;
6750                 return;
6751
6752             } else if (strncmp(message,"--",2) == 0) {
6753                 /* Crafty 9.29+ outputs this */
6754                 programStats.got_fail = 1;
6755                 return;
6756
6757             } else if (thinkOutput[0] != NULLCHAR &&
6758                        strncmp(message, "    ", 4) == 0) {
6759                 unsigned message_len;
6760
6761                 p = message;
6762                 while (*p && *p == ' ') p++;
6763
6764                 message_len = strlen( p );
6765
6766                 /* [AS] Avoid buffer overflow */
6767                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6768                     strcat(thinkOutput, " ");
6769                     strcat(thinkOutput, p);
6770                 }
6771
6772                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6773                     strcat(programStats.movelist, " ");
6774                     strcat(programStats.movelist, p);
6775                 }
6776
6777                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6778                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6779                     DisplayMove(currentMove - 1);
6780                 }
6781                 return;
6782             }
6783         }
6784         else {
6785             buf1[0] = NULLCHAR;
6786
6787             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6788                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6789             {
6790                 ChessProgramStats cpstats;
6791
6792                 if (plyext != ' ' && plyext != '\t') {
6793                     time *= 100;
6794                 }
6795
6796                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6797                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6798                     curscore = -curscore;
6799                 }
6800
6801                 cpstats.depth = plylev;
6802                 cpstats.nodes = nodes;
6803                 cpstats.time = time;
6804                 cpstats.score = curscore;
6805                 cpstats.got_only_move = 0;
6806                 cpstats.movelist[0] = '\0';
6807
6808                 if (buf1[0] != NULLCHAR) {
6809                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6810                 }
6811
6812                 cpstats.ok_to_send = 0;
6813                 cpstats.line_is_book = 0;
6814                 cpstats.nr_moves = 0;
6815                 cpstats.moves_left = 0;
6816
6817                 SendProgramStatsToFrontend( cps, &cpstats );
6818             }
6819         }
6820     }
6821 }
6822
6823
6824 /* Parse a game score from the character string "game", and
6825    record it as the history of the current game.  The game
6826    score is NOT assumed to start from the standard position. 
6827    The display is not updated in any way.
6828    */
6829 void
6830 ParseGameHistory(game)
6831      char *game;
6832 {
6833     ChessMove moveType;
6834     int fromX, fromY, toX, toY, boardIndex;
6835     char promoChar;
6836     char *p, *q;
6837     char buf[MSG_SIZ];
6838
6839     if (appData.debugMode)
6840       fprintf(debugFP, "Parsing game history: %s\n", game);
6841
6842     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6843     gameInfo.site = StrSave(appData.icsHost);
6844     gameInfo.date = PGNDate();
6845     gameInfo.round = StrSave("-");
6846
6847     /* Parse out names of players */
6848     while (*game == ' ') game++;
6849     p = buf;
6850     while (*game != ' ') *p++ = *game++;
6851     *p = NULLCHAR;
6852     gameInfo.white = StrSave(buf);
6853     while (*game == ' ') game++;
6854     p = buf;
6855     while (*game != ' ' && *game != '\n') *p++ = *game++;
6856     *p = NULLCHAR;
6857     gameInfo.black = StrSave(buf);
6858
6859     /* Parse moves */
6860     boardIndex = blackPlaysFirst ? 1 : 0;
6861     yynewstr(game);
6862     for (;;) {
6863         yyboardindex = boardIndex;
6864         moveType = (ChessMove) yylex();
6865         switch (moveType) {
6866           case IllegalMove:             /* maybe suicide chess, etc. */
6867   if (appData.debugMode) {
6868     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6869     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6870     setbuf(debugFP, NULL);
6871   }
6872           case WhitePromotionChancellor:
6873           case BlackPromotionChancellor:
6874           case WhitePromotionArchbishop:
6875           case BlackPromotionArchbishop:
6876           case WhitePromotionQueen:
6877           case BlackPromotionQueen:
6878           case WhitePromotionRook:
6879           case BlackPromotionRook:
6880           case WhitePromotionBishop:
6881           case BlackPromotionBishop:
6882           case WhitePromotionKnight:
6883           case BlackPromotionKnight:
6884           case WhitePromotionKing:
6885           case BlackPromotionKing:
6886           case NormalMove:
6887           case WhiteCapturesEnPassant:
6888           case BlackCapturesEnPassant:
6889           case WhiteKingSideCastle:
6890           case WhiteQueenSideCastle:
6891           case BlackKingSideCastle:
6892           case BlackQueenSideCastle:
6893           case WhiteKingSideCastleWild:
6894           case WhiteQueenSideCastleWild:
6895           case BlackKingSideCastleWild:
6896           case BlackQueenSideCastleWild:
6897           /* PUSH Fabien */
6898           case WhiteHSideCastleFR:
6899           case WhiteASideCastleFR:
6900           case BlackHSideCastleFR:
6901           case BlackASideCastleFR:
6902           /* POP Fabien */
6903             fromX = currentMoveString[0] - AAA;
6904             fromY = currentMoveString[1] - ONE;
6905             toX = currentMoveString[2] - AAA;
6906             toY = currentMoveString[3] - ONE;
6907             promoChar = currentMoveString[4];
6908             break;
6909           case WhiteDrop:
6910           case BlackDrop:
6911             fromX = moveType == WhiteDrop ?
6912               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6913             (int) CharToPiece(ToLower(currentMoveString[0]));
6914             fromY = DROP_RANK;
6915             toX = currentMoveString[2] - AAA;
6916             toY = currentMoveString[3] - ONE;
6917             promoChar = NULLCHAR;
6918             break;
6919           case AmbiguousMove:
6920             /* bug? */
6921             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6922   if (appData.debugMode) {
6923     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6924     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6925     setbuf(debugFP, NULL);
6926   }
6927             DisplayError(buf, 0);
6928             return;
6929           case ImpossibleMove:
6930             /* bug? */
6931             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6932   if (appData.debugMode) {
6933     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6934     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6935     setbuf(debugFP, NULL);
6936   }
6937             DisplayError(buf, 0);
6938             return;
6939           case (ChessMove) 0:   /* end of file */
6940             if (boardIndex < backwardMostMove) {
6941                 /* Oops, gap.  How did that happen? */
6942                 DisplayError(_("Gap in move list"), 0);
6943                 return;
6944             }
6945             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6946             if (boardIndex > forwardMostMove) {
6947                 forwardMostMove = boardIndex;
6948             }
6949             return;
6950           case ElapsedTime:
6951             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6952                 strcat(parseList[boardIndex-1], " ");
6953                 strcat(parseList[boardIndex-1], yy_text);
6954             }
6955             continue;
6956           case Comment:
6957           case PGNTag:
6958           case NAG:
6959           default:
6960             /* ignore */
6961             continue;
6962           case WhiteWins:
6963           case BlackWins:
6964           case GameIsDrawn:
6965           case GameUnfinished:
6966             if (gameMode == IcsExamining) {
6967                 if (boardIndex < backwardMostMove) {
6968                     /* Oops, gap.  How did that happen? */
6969                     return;
6970                 }
6971                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6972                 return;
6973             }
6974             gameInfo.result = moveType;
6975             p = strchr(yy_text, '{');
6976             if (p == NULL) p = strchr(yy_text, '(');
6977             if (p == NULL) {
6978                 p = yy_text;
6979                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6980             } else {
6981                 q = strchr(p, *p == '{' ? '}' : ')');
6982                 if (q != NULL) *q = NULLCHAR;
6983                 p++;
6984             }
6985             gameInfo.resultDetails = StrSave(p);
6986             continue;
6987         }
6988         if (boardIndex >= forwardMostMove &&
6989             !(gameMode == IcsObserving && ics_gamenum == -1)) {
6990             backwardMostMove = blackPlaysFirst ? 1 : 0;
6991             return;
6992         }
6993         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6994                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6995                                  parseList[boardIndex]);
6996         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6997         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6998         /* currentMoveString is set as a side-effect of yylex */
6999         strcpy(moveList[boardIndex], currentMoveString);
7000         strcat(moveList[boardIndex], "\n");
7001         boardIndex++;
7002         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7003                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7004         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7005                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7006           case MT_NONE:
7007           case MT_STALEMATE:
7008           default:
7009             break;
7010           case MT_CHECK:
7011             if(gameInfo.variant != VariantShogi)
7012                 strcat(parseList[boardIndex - 1], "+");
7013             break;
7014           case MT_CHECKMATE:
7015           case MT_STAINMATE:
7016             strcat(parseList[boardIndex - 1], "#");
7017             break;
7018         }
7019     }
7020 }
7021
7022
7023 /* Apply a move to the given board  */
7024 void
7025 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7026      int fromX, fromY, toX, toY;
7027      int promoChar;
7028      Board board;
7029      char *castling;
7030      char *ep;
7031 {
7032   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7033
7034     /* [HGM] compute & store e.p. status and castling rights for new position */
7035     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7036     { int i;
7037
7038       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7039       oldEP = *ep;
7040       *ep = EP_NONE;
7041
7042       if( board[toY][toX] != EmptySquare ) 
7043            *ep = EP_CAPTURE;  
7044
7045       if( board[fromY][fromX] == WhitePawn ) {
7046            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7047                *ep = EP_PAWN_MOVE;
7048            if( toY-fromY==2) {
7049                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7050                         gameInfo.variant != VariantBerolina || toX < fromX)
7051                       *ep = toX | berolina;
7052                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7053                         gameInfo.variant != VariantBerolina || toX > fromX) 
7054                       *ep = toX;
7055            }
7056       } else 
7057       if( board[fromY][fromX] == BlackPawn ) {
7058            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7059                *ep = EP_PAWN_MOVE; 
7060            if( toY-fromY== -2) {
7061                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7062                         gameInfo.variant != VariantBerolina || toX < fromX)
7063                       *ep = toX | berolina;
7064                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7065                         gameInfo.variant != VariantBerolina || toX > fromX) 
7066                       *ep = toX;
7067            }
7068        }
7069
7070        for(i=0; i<nrCastlingRights; i++) {
7071            if(castling[i] == fromX && castlingRank[i] == fromY ||
7072               castling[i] == toX   && castlingRank[i] == toY   
7073              ) castling[i] = -1; // revoke for moved or captured piece
7074        }
7075
7076     }
7077
7078   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7079   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7080        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7081          
7082   if (fromX == toX && fromY == toY) return;
7083
7084   if (fromY == DROP_RANK) {
7085         /* must be first */
7086         piece = board[toY][toX] = (ChessSquare) fromX;
7087   } else {
7088      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7089      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7090      if(gameInfo.variant == VariantKnightmate)
7091          king += (int) WhiteUnicorn - (int) WhiteKing;
7092
7093     /* Code added by Tord: */
7094     /* FRC castling assumed when king captures friendly rook. */
7095     if (board[fromY][fromX] == WhiteKing &&
7096              board[toY][toX] == WhiteRook) {
7097       board[fromY][fromX] = EmptySquare;
7098       board[toY][toX] = EmptySquare;
7099       if(toX > fromX) {
7100         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7101       } else {
7102         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7103       }
7104     } else if (board[fromY][fromX] == BlackKing &&
7105                board[toY][toX] == BlackRook) {
7106       board[fromY][fromX] = EmptySquare;
7107       board[toY][toX] = EmptySquare;
7108       if(toX > fromX) {
7109         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7110       } else {
7111         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7112       }
7113     /* End of code added by Tord */
7114
7115     } else if (board[fromY][fromX] == king
7116         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7117         && toY == fromY && toX > fromX+1) {
7118         board[fromY][fromX] = EmptySquare;
7119         board[toY][toX] = king;
7120         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7121         board[fromY][BOARD_RGHT-1] = EmptySquare;
7122     } else if (board[fromY][fromX] == king
7123         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7124                && toY == fromY && toX < fromX-1) {
7125         board[fromY][fromX] = EmptySquare;
7126         board[toY][toX] = king;
7127         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7128         board[fromY][BOARD_LEFT] = EmptySquare;
7129     } else if (board[fromY][fromX] == WhitePawn
7130                && toY == BOARD_HEIGHT-1
7131                && gameInfo.variant != VariantXiangqi
7132                ) {
7133         /* white pawn promotion */
7134         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7135         if (board[toY][toX] == EmptySquare) {
7136             board[toY][toX] = WhiteQueen;
7137         }
7138         if(gameInfo.variant==VariantBughouse ||
7139            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7140             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7141         board[fromY][fromX] = EmptySquare;
7142     } else if ((fromY == BOARD_HEIGHT-4)
7143                && (toX != fromX)
7144                && gameInfo.variant != VariantXiangqi
7145                && gameInfo.variant != VariantBerolina
7146                && (board[fromY][fromX] == WhitePawn)
7147                && (board[toY][toX] == EmptySquare)) {
7148         board[fromY][fromX] = EmptySquare;
7149         board[toY][toX] = WhitePawn;
7150         captured = board[toY - 1][toX];
7151         board[toY - 1][toX] = EmptySquare;
7152     } else if ((fromY == BOARD_HEIGHT-4)
7153                && (toX == fromX)
7154                && gameInfo.variant == VariantBerolina
7155                && (board[fromY][fromX] == WhitePawn)
7156                && (board[toY][toX] == EmptySquare)) {
7157         board[fromY][fromX] = EmptySquare;
7158         board[toY][toX] = WhitePawn;
7159         if(oldEP & EP_BEROLIN_A) {
7160                 captured = board[fromY][fromX-1];
7161                 board[fromY][fromX-1] = EmptySquare;
7162         }else{  captured = board[fromY][fromX+1];
7163                 board[fromY][fromX+1] = EmptySquare;
7164         }
7165     } else if (board[fromY][fromX] == king
7166         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7167                && toY == fromY && toX > fromX+1) {
7168         board[fromY][fromX] = EmptySquare;
7169         board[toY][toX] = king;
7170         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7171         board[fromY][BOARD_RGHT-1] = EmptySquare;
7172     } else if (board[fromY][fromX] == king
7173         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7174                && toY == fromY && toX < fromX-1) {
7175         board[fromY][fromX] = EmptySquare;
7176         board[toY][toX] = king;
7177         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7178         board[fromY][BOARD_LEFT] = EmptySquare;
7179     } else if (fromY == 7 && fromX == 3
7180                && board[fromY][fromX] == BlackKing
7181                && toY == 7 && toX == 5) {
7182         board[fromY][fromX] = EmptySquare;
7183         board[toY][toX] = BlackKing;
7184         board[fromY][7] = EmptySquare;
7185         board[toY][4] = BlackRook;
7186     } else if (fromY == 7 && fromX == 3
7187                && board[fromY][fromX] == BlackKing
7188                && toY == 7 && toX == 1) {
7189         board[fromY][fromX] = EmptySquare;
7190         board[toY][toX] = BlackKing;
7191         board[fromY][0] = EmptySquare;
7192         board[toY][2] = BlackRook;
7193     } else if (board[fromY][fromX] == BlackPawn
7194                && toY == 0
7195                && gameInfo.variant != VariantXiangqi
7196                ) {
7197         /* black pawn promotion */
7198         board[0][toX] = CharToPiece(ToLower(promoChar));
7199         if (board[0][toX] == EmptySquare) {
7200             board[0][toX] = BlackQueen;
7201         }
7202         if(gameInfo.variant==VariantBughouse ||
7203            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7204             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7205         board[fromY][fromX] = EmptySquare;
7206     } else if ((fromY == 3)
7207                && (toX != fromX)
7208                && gameInfo.variant != VariantXiangqi
7209                && gameInfo.variant != VariantBerolina
7210                && (board[fromY][fromX] == BlackPawn)
7211                && (board[toY][toX] == EmptySquare)) {
7212         board[fromY][fromX] = EmptySquare;
7213         board[toY][toX] = BlackPawn;
7214         captured = board[toY + 1][toX];
7215         board[toY + 1][toX] = EmptySquare;
7216     } else if ((fromY == 3)
7217                && (toX == fromX)
7218                && gameInfo.variant == VariantBerolina
7219                && (board[fromY][fromX] == BlackPawn)
7220                && (board[toY][toX] == EmptySquare)) {
7221         board[fromY][fromX] = EmptySquare;
7222         board[toY][toX] = BlackPawn;
7223         if(oldEP & EP_BEROLIN_A) {
7224                 captured = board[fromY][fromX-1];
7225                 board[fromY][fromX-1] = EmptySquare;
7226         }else{  captured = board[fromY][fromX+1];
7227                 board[fromY][fromX+1] = EmptySquare;
7228         }
7229     } else {
7230         board[toY][toX] = board[fromY][fromX];
7231         board[fromY][fromX] = EmptySquare;
7232     }
7233
7234     /* [HGM] now we promote for Shogi, if needed */
7235     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7236         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7237   }
7238
7239     if (gameInfo.holdingsWidth != 0) {
7240
7241       /* !!A lot more code needs to be written to support holdings  */
7242       /* [HGM] OK, so I have written it. Holdings are stored in the */
7243       /* penultimate board files, so they are automaticlly stored   */
7244       /* in the game history.                                       */
7245       if (fromY == DROP_RANK) {
7246         /* Delete from holdings, by decreasing count */
7247         /* and erasing image if necessary            */
7248         p = (int) fromX;
7249         if(p < (int) BlackPawn) { /* white drop */
7250              p -= (int)WhitePawn;
7251              if(p >= gameInfo.holdingsSize) p = 0;
7252              if(--board[p][BOARD_WIDTH-2] == 0)
7253                   board[p][BOARD_WIDTH-1] = EmptySquare;
7254         } else {                  /* black drop */
7255              p -= (int)BlackPawn;
7256              if(p >= gameInfo.holdingsSize) p = 0;
7257              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7258                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7259         }
7260       }
7261       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7262           && gameInfo.variant != VariantBughouse        ) {
7263         /* [HGM] holdings: Add to holdings, if holdings exist */
7264         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7265                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7266                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7267         }
7268         p = (int) captured;
7269         if (p >= (int) BlackPawn) {
7270           p -= (int)BlackPawn;
7271           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7272                   /* in Shogi restore piece to its original  first */
7273                   captured = (ChessSquare) (DEMOTED captured);
7274                   p = DEMOTED p;
7275           }
7276           p = PieceToNumber((ChessSquare)p);
7277           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7278           board[p][BOARD_WIDTH-2]++;
7279           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7280         } else {
7281           p -= (int)WhitePawn;
7282           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7283                   captured = (ChessSquare) (DEMOTED captured);
7284                   p = DEMOTED p;
7285           }
7286           p = PieceToNumber((ChessSquare)p);
7287           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7288           board[BOARD_HEIGHT-1-p][1]++;
7289           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7290         }
7291       }
7292
7293     } else if (gameInfo.variant == VariantAtomic) {
7294       if (captured != EmptySquare) {
7295         int y, x;
7296         for (y = toY-1; y <= toY+1; y++) {
7297           for (x = toX-1; x <= toX+1; x++) {
7298             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7299                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7300               board[y][x] = EmptySquare;
7301             }
7302           }
7303         }
7304         board[toY][toX] = EmptySquare;
7305       }
7306     }
7307     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7308         /* [HGM] Shogi promotions */
7309         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7310     }
7311
7312     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7313                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7314         // [HGM] superchess: take promotion piece out of holdings
7315         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7316         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7317             if(!--board[k][BOARD_WIDTH-2])
7318                 board[k][BOARD_WIDTH-1] = EmptySquare;
7319         } else {
7320             if(!--board[BOARD_HEIGHT-1-k][1])
7321                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7322         }
7323     }
7324
7325 }
7326
7327 /* Updates forwardMostMove */
7328 void
7329 MakeMove(fromX, fromY, toX, toY, promoChar)
7330      int fromX, fromY, toX, toY;
7331      int promoChar;
7332 {
7333 //    forwardMostMove++; // [HGM] bare: moved downstream
7334
7335     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7336         int timeLeft; static int lastLoadFlag=0; int king, piece;
7337         piece = boards[forwardMostMove][fromY][fromX];
7338         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7339         if(gameInfo.variant == VariantKnightmate)
7340             king += (int) WhiteUnicorn - (int) WhiteKing;
7341         if(forwardMostMove == 0) {
7342             if(blackPlaysFirst) 
7343                 fprintf(serverMoves, "%s;", second.tidy);
7344             fprintf(serverMoves, "%s;", first.tidy);
7345             if(!blackPlaysFirst) 
7346                 fprintf(serverMoves, "%s;", second.tidy);
7347         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7348         lastLoadFlag = loadFlag;
7349         // print base move
7350         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7351         // print castling suffix
7352         if( toY == fromY && piece == king ) {
7353             if(toX-fromX > 1)
7354                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7355             if(fromX-toX >1)
7356                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7357         }
7358         // e.p. suffix
7359         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7360              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7361              boards[forwardMostMove][toY][toX] == EmptySquare
7362              && fromX != toX )
7363                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7364         // promotion suffix
7365         if(promoChar != NULLCHAR)
7366                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7367         if(!loadFlag) {
7368             fprintf(serverMoves, "/%d/%d",
7369                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7370             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7371             else                      timeLeft = blackTimeRemaining/1000;
7372             fprintf(serverMoves, "/%d", timeLeft);
7373         }
7374         fflush(serverMoves);
7375     }
7376
7377     if (forwardMostMove+1 >= MAX_MOVES) {
7378       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7379                         0, 1);
7380       return;
7381     }
7382     if (commentList[forwardMostMove+1] != NULL) {
7383         free(commentList[forwardMostMove+1]);
7384         commentList[forwardMostMove+1] = NULL;
7385     }
7386     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7387     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7388     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7389                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7390     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7391     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7392     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7393     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7394     gameInfo.result = GameUnfinished;
7395     if (gameInfo.resultDetails != NULL) {
7396         free(gameInfo.resultDetails);
7397         gameInfo.resultDetails = NULL;
7398     }
7399     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7400                               moveList[forwardMostMove - 1]);
7401     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7402                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7403                              fromY, fromX, toY, toX, promoChar,
7404                              parseList[forwardMostMove - 1]);
7405     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7406                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7407                             castlingRights[forwardMostMove]) ) {
7408       case MT_NONE:
7409       case MT_STALEMATE:
7410       default:
7411         break;
7412       case MT_CHECK:
7413         if(gameInfo.variant != VariantShogi)
7414             strcat(parseList[forwardMostMove - 1], "+");
7415         break;
7416       case MT_CHECKMATE:
7417       case MT_STAINMATE:
7418         strcat(parseList[forwardMostMove - 1], "#");
7419         break;
7420     }
7421     if (appData.debugMode) {
7422         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7423     }
7424
7425 }
7426
7427 /* Updates currentMove if not pausing */
7428 void
7429 ShowMove(fromX, fromY, toX, toY)
7430 {
7431     int instant = (gameMode == PlayFromGameFile) ?
7432         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7433     if(appData.noGUI) return;
7434     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7435         if (!instant) {
7436             if (forwardMostMove == currentMove + 1) {
7437                 AnimateMove(boards[forwardMostMove - 1],
7438                             fromX, fromY, toX, toY);
7439             }
7440             if (appData.highlightLastMove) {
7441                 SetHighlights(fromX, fromY, toX, toY);
7442             }
7443         }
7444         currentMove = forwardMostMove;
7445     }
7446
7447     if (instant) return;
7448
7449     DisplayMove(currentMove - 1);
7450     DrawPosition(FALSE, boards[currentMove]);
7451     DisplayBothClocks();
7452     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7453 }
7454
7455 void SendEgtPath(ChessProgramState *cps)
7456 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7457         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7458
7459         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7460
7461         while(*p) {
7462             char c, *q = name+1, *r, *s;
7463
7464             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7465             while(*p && *p != ',') *q++ = *p++;
7466             *q++ = ':'; *q = 0;
7467             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7468                 strcmp(name, ",nalimov:") == 0 ) {
7469                 // take nalimov path from the menu-changeable option first, if it is defined
7470                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7471                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7472             } else
7473             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7474                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7475                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7476                 s = r = StrStr(s, ":") + 1; // beginning of path info
7477                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7478                 c = *r; *r = 0;             // temporarily null-terminate path info
7479                     *--q = 0;               // strip of trailig ':' from name
7480                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7481                 *r = c;
7482                 SendToProgram(buf,cps);     // send egtbpath command for this format
7483             }
7484             if(*p == ',') p++; // read away comma to position for next format name
7485         }
7486 }
7487
7488 void
7489 InitChessProgram(cps, setup)
7490      ChessProgramState *cps;
7491      int setup; /* [HGM] needed to setup FRC opening position */
7492 {
7493     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7494     if (appData.noChessProgram) return;
7495     hintRequested = FALSE;
7496     bookRequested = FALSE;
7497
7498     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7499     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7500     if(cps->memSize) { /* [HGM] memory */
7501         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7502         SendToProgram(buf, cps);
7503     }
7504     SendEgtPath(cps); /* [HGM] EGT */
7505     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7506         sprintf(buf, "cores %d\n", appData.smpCores);
7507         SendToProgram(buf, cps);
7508     }
7509
7510     SendToProgram(cps->initString, cps);
7511     if (gameInfo.variant != VariantNormal &&
7512         gameInfo.variant != VariantLoadable
7513         /* [HGM] also send variant if board size non-standard */
7514         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7515                                             ) {
7516       char *v = VariantName(gameInfo.variant);
7517       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7518         /* [HGM] in protocol 1 we have to assume all variants valid */
7519         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7520         DisplayFatalError(buf, 0, 1);
7521         return;
7522       }
7523
7524       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7525       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7526       if( gameInfo.variant == VariantXiangqi )
7527            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7528       if( gameInfo.variant == VariantShogi )
7529            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7530       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7531            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7532       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7533                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7534            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7535       if( gameInfo.variant == VariantCourier )
7536            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7537       if( gameInfo.variant == VariantSuper )
7538            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7539       if( gameInfo.variant == VariantGreat )
7540            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7541
7542       if(overruled) {
7543            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7544                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7545            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7546            if(StrStr(cps->variants, b) == NULL) { 
7547                // specific sized variant not known, check if general sizing allowed
7548                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7549                    if(StrStr(cps->variants, "boardsize") == NULL) {
7550                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7551                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7552                        DisplayFatalError(buf, 0, 1);
7553                        return;
7554                    }
7555                    /* [HGM] here we really should compare with the maximum supported board size */
7556                }
7557            }
7558       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7559       sprintf(buf, "variant %s\n", b);
7560       SendToProgram(buf, cps);
7561     }
7562     currentlyInitializedVariant = gameInfo.variant;
7563
7564     /* [HGM] send opening position in FRC to first engine */
7565     if(setup) {
7566           SendToProgram("force\n", cps);
7567           SendBoard(cps, 0);
7568           /* engine is now in force mode! Set flag to wake it up after first move. */
7569           setboardSpoiledMachineBlack = 1;
7570     }
7571
7572     if (cps->sendICS) {
7573       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7574       SendToProgram(buf, cps);
7575     }
7576     cps->maybeThinking = FALSE;
7577     cps->offeredDraw = 0;
7578     if (!appData.icsActive) {
7579         SendTimeControl(cps, movesPerSession, timeControl,
7580                         timeIncrement, appData.searchDepth,
7581                         searchTime);
7582     }
7583     if (appData.showThinking 
7584         // [HGM] thinking: four options require thinking output to be sent
7585         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7586                                 ) {
7587         SendToProgram("post\n", cps);
7588     }
7589     SendToProgram("hard\n", cps);
7590     if (!appData.ponderNextMove) {
7591         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7592            it without being sure what state we are in first.  "hard"
7593            is not a toggle, so that one is OK.
7594          */
7595         SendToProgram("easy\n", cps);
7596     }
7597     if (cps->usePing) {
7598       sprintf(buf, "ping %d\n", ++cps->lastPing);
7599       SendToProgram(buf, cps);
7600     }
7601     cps->initDone = TRUE;
7602 }   
7603
7604
7605 void
7606 StartChessProgram(cps)
7607      ChessProgramState *cps;
7608 {
7609     char buf[MSG_SIZ];
7610     int err;
7611
7612     if (appData.noChessProgram) return;
7613     cps->initDone = FALSE;
7614
7615     if (strcmp(cps->host, "localhost") == 0) {
7616         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7617     } else if (*appData.remoteShell == NULLCHAR) {
7618         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7619     } else {
7620         if (*appData.remoteUser == NULLCHAR) {
7621           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7622                     cps->program);
7623         } else {
7624           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7625                     cps->host, appData.remoteUser, cps->program);
7626         }
7627         err = StartChildProcess(buf, "", &cps->pr);
7628     }
7629     
7630     if (err != 0) {
7631         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7632         DisplayFatalError(buf, err, 1);
7633         cps->pr = NoProc;
7634         cps->isr = NULL;
7635         return;
7636     }
7637     
7638     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7639     if (cps->protocolVersion > 1) {
7640       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7641       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7642       cps->comboCnt = 0;  //                and values of combo boxes
7643       SendToProgram(buf, cps);
7644     } else {
7645       SendToProgram("xboard\n", cps);
7646     }
7647 }
7648
7649
7650 void
7651 TwoMachinesEventIfReady P((void))
7652 {
7653   if (first.lastPing != first.lastPong) {
7654     DisplayMessage("", _("Waiting for first chess program"));
7655     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7656     return;
7657   }
7658   if (second.lastPing != second.lastPong) {
7659     DisplayMessage("", _("Waiting for second chess program"));
7660     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7661     return;
7662   }
7663   ThawUI();
7664   TwoMachinesEvent();
7665 }
7666
7667 void
7668 NextMatchGame P((void))
7669 {
7670     int index; /* [HGM] autoinc: step lod index during match */
7671     Reset(FALSE, TRUE);
7672     if (*appData.loadGameFile != NULLCHAR) {
7673         index = appData.loadGameIndex;
7674         if(index < 0) { // [HGM] autoinc
7675             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7676             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7677         } 
7678         LoadGameFromFile(appData.loadGameFile,
7679                          index,
7680                          appData.loadGameFile, FALSE);
7681     } else if (*appData.loadPositionFile != NULLCHAR) {
7682         index = appData.loadPositionIndex;
7683         if(index < 0) { // [HGM] autoinc
7684             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7685             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7686         } 
7687         LoadPositionFromFile(appData.loadPositionFile,
7688                              index,
7689                              appData.loadPositionFile);
7690     }
7691     TwoMachinesEventIfReady();
7692 }
7693
7694 void UserAdjudicationEvent( int result )
7695 {
7696     ChessMove gameResult = GameIsDrawn;
7697
7698     if( result > 0 ) {
7699         gameResult = WhiteWins;
7700     }
7701     else if( result < 0 ) {
7702         gameResult = BlackWins;
7703     }
7704
7705     if( gameMode == TwoMachinesPlay ) {
7706         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7707     }
7708 }
7709
7710
7711 // [HGM] save: calculate checksum of game to make games easily identifiable
7712 int StringCheckSum(char *s)
7713 {
7714         int i = 0;
7715         if(s==NULL) return 0;
7716         while(*s) i = i*259 + *s++;
7717         return i;
7718 }
7719
7720 int GameCheckSum()
7721 {
7722         int i, sum=0;
7723         for(i=backwardMostMove; i<forwardMostMove; i++) {
7724                 sum += pvInfoList[i].depth;
7725                 sum += StringCheckSum(parseList[i]);
7726                 sum += StringCheckSum(commentList[i]);
7727                 sum *= 261;
7728         }
7729         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7730         return sum + StringCheckSum(commentList[i]);
7731 } // end of save patch
7732
7733 void
7734 GameEnds(result, resultDetails, whosays)
7735      ChessMove result;
7736      char *resultDetails;
7737      int whosays;
7738 {
7739     GameMode nextGameMode;
7740     int isIcsGame;
7741     char buf[MSG_SIZ];
7742
7743     if(endingGame) return; /* [HGM] crash: forbid recursion */
7744     endingGame = 1;
7745
7746     if (appData.debugMode) {
7747       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7748               result, resultDetails ? resultDetails : "(null)", whosays);
7749     }
7750
7751     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7752         /* If we are playing on ICS, the server decides when the
7753            game is over, but the engine can offer to draw, claim 
7754            a draw, or resign. 
7755          */
7756 #if ZIPPY
7757         if (appData.zippyPlay && first.initDone) {
7758             if (result == GameIsDrawn) {
7759                 /* In case draw still needs to be claimed */
7760                 SendToICS(ics_prefix);
7761                 SendToICS("draw\n");
7762             } else if (StrCaseStr(resultDetails, "resign")) {
7763                 SendToICS(ics_prefix);
7764                 SendToICS("resign\n");
7765             }
7766         }
7767 #endif
7768         endingGame = 0; /* [HGM] crash */
7769         return;
7770     }
7771
7772     /* If we're loading the game from a file, stop */
7773     if (whosays == GE_FILE) {
7774       (void) StopLoadGameTimer();
7775       gameFileFP = NULL;
7776     }
7777
7778     /* Cancel draw offers */
7779     first.offeredDraw = second.offeredDraw = 0;
7780
7781     /* If this is an ICS game, only ICS can really say it's done;
7782        if not, anyone can. */
7783     isIcsGame = (gameMode == IcsPlayingWhite || 
7784                  gameMode == IcsPlayingBlack || 
7785                  gameMode == IcsObserving    || 
7786                  gameMode == IcsExamining);
7787
7788     if (!isIcsGame || whosays == GE_ICS) {
7789         /* OK -- not an ICS game, or ICS said it was done */
7790         StopClocks();
7791         if (!isIcsGame && !appData.noChessProgram) 
7792           SetUserThinkingEnables();
7793     
7794         /* [HGM] if a machine claims the game end we verify this claim */
7795         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7796             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7797                 char claimer;
7798                 ChessMove trueResult = (ChessMove) -1;
7799
7800                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7801                                             first.twoMachinesColor[0] :
7802                                             second.twoMachinesColor[0] ;
7803
7804                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7805                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7806                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7807                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7808                 } else
7809                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7810                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7811                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7812                 } else
7813                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7814                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7815                 }
7816
7817                 // now verify win claims, but not in drop games, as we don't understand those yet
7818                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7819                                                  || gameInfo.variant == VariantGreat) &&
7820                     (result == WhiteWins && claimer == 'w' ||
7821                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7822                       if (appData.debugMode) {
7823                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7824                                 result, epStatus[forwardMostMove], forwardMostMove);
7825                       }
7826                       if(result != trueResult) {
7827                               sprintf(buf, "False win claim: '%s'", resultDetails);
7828                               result = claimer == 'w' ? BlackWins : WhiteWins;
7829                               resultDetails = buf;
7830                       }
7831                 } else
7832                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7833                     && (forwardMostMove <= backwardMostMove ||
7834                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7835                         (claimer=='b')==(forwardMostMove&1))
7836                                                                                   ) {
7837                       /* [HGM] verify: draws that were not flagged are false claims */
7838                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7839                       result = claimer == 'w' ? BlackWins : WhiteWins;
7840                       resultDetails = buf;
7841                 }
7842                 /* (Claiming a loss is accepted no questions asked!) */
7843             }
7844             /* [HGM] bare: don't allow bare King to win */
7845             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7846                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7847                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7848                && result != GameIsDrawn)
7849             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7850                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7851                         int p = (int)boards[forwardMostMove][i][j] - color;
7852                         if(p >= 0 && p <= (int)WhiteKing) k++;
7853                 }
7854                 if (appData.debugMode) {
7855                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7856                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7857                 }
7858                 if(k <= 1) {
7859                         result = GameIsDrawn;
7860                         sprintf(buf, "%s but bare king", resultDetails);
7861                         resultDetails = buf;
7862                 }
7863             }
7864         }
7865
7866
7867         if(serverMoves != NULL && !loadFlag) { char c = '=';
7868             if(result==WhiteWins) c = '+';
7869             if(result==BlackWins) c = '-';
7870             if(resultDetails != NULL)
7871                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7872         }
7873         if (resultDetails != NULL) {
7874             gameInfo.result = result;
7875             gameInfo.resultDetails = StrSave(resultDetails);
7876
7877             /* display last move only if game was not loaded from file */
7878             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7879                 DisplayMove(currentMove - 1);
7880     
7881             if (forwardMostMove != 0) {
7882                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7883                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7884                                                                 ) {
7885                     if (*appData.saveGameFile != NULLCHAR) {
7886                         SaveGameToFile(appData.saveGameFile, TRUE);
7887                     } else if (appData.autoSaveGames) {
7888                         AutoSaveGame();
7889                     }
7890                     if (*appData.savePositionFile != NULLCHAR) {
7891                         SavePositionToFile(appData.savePositionFile);
7892                     }
7893                 }
7894             }
7895
7896             /* Tell program how game ended in case it is learning */
7897             /* [HGM] Moved this to after saving the PGN, just in case */
7898             /* engine died and we got here through time loss. In that */
7899             /* case we will get a fatal error writing the pipe, which */
7900             /* would otherwise lose us the PGN.                       */
7901             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7902             /* output during GameEnds should never be fatal anymore   */
7903             if (gameMode == MachinePlaysWhite ||
7904                 gameMode == MachinePlaysBlack ||
7905                 gameMode == TwoMachinesPlay ||
7906                 gameMode == IcsPlayingWhite ||
7907                 gameMode == IcsPlayingBlack ||
7908                 gameMode == BeginningOfGame) {
7909                 char buf[MSG_SIZ];
7910                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7911                         resultDetails);
7912                 if (first.pr != NoProc) {
7913                     SendToProgram(buf, &first);
7914                 }
7915                 if (second.pr != NoProc &&
7916                     gameMode == TwoMachinesPlay) {
7917                     SendToProgram(buf, &second);
7918                 }
7919             }
7920         }
7921
7922         if (appData.icsActive) {
7923             if (appData.quietPlay &&
7924                 (gameMode == IcsPlayingWhite ||
7925                  gameMode == IcsPlayingBlack)) {
7926                 SendToICS(ics_prefix);
7927                 SendToICS("set shout 1\n");
7928             }
7929             nextGameMode = IcsIdle;
7930             ics_user_moved = FALSE;
7931             /* clean up premove.  It's ugly when the game has ended and the
7932              * premove highlights are still on the board.
7933              */
7934             if (gotPremove) {
7935               gotPremove = FALSE;
7936               ClearPremoveHighlights();
7937               DrawPosition(FALSE, boards[currentMove]);
7938             }
7939             if (whosays == GE_ICS) {
7940                 switch (result) {
7941                 case WhiteWins:
7942                     if (gameMode == IcsPlayingWhite)
7943                         PlayIcsWinSound();
7944                     else if(gameMode == IcsPlayingBlack)
7945                         PlayIcsLossSound();
7946                     break;
7947                 case BlackWins:
7948                     if (gameMode == IcsPlayingBlack)
7949                         PlayIcsWinSound();
7950                     else if(gameMode == IcsPlayingWhite)
7951                         PlayIcsLossSound();
7952                     break;
7953                 case GameIsDrawn:
7954                     PlayIcsDrawSound();
7955                     break;
7956                 default:
7957                     PlayIcsUnfinishedSound();
7958                 }
7959             }
7960         } else if (gameMode == EditGame ||
7961                    gameMode == PlayFromGameFile || 
7962                    gameMode == AnalyzeMode || 
7963                    gameMode == AnalyzeFile) {
7964             nextGameMode = gameMode;
7965         } else {
7966             nextGameMode = EndOfGame;
7967         }
7968         pausing = FALSE;
7969         ModeHighlight();
7970     } else {
7971         nextGameMode = gameMode;
7972     }
7973
7974     if (appData.noChessProgram) {
7975         gameMode = nextGameMode;
7976         ModeHighlight();
7977         endingGame = 0; /* [HGM] crash */
7978         return;
7979     }
7980
7981     if (first.reuse) {
7982         /* Put first chess program into idle state */
7983         if (first.pr != NoProc &&
7984             (gameMode == MachinePlaysWhite ||
7985              gameMode == MachinePlaysBlack ||
7986              gameMode == TwoMachinesPlay ||
7987              gameMode == IcsPlayingWhite ||
7988              gameMode == IcsPlayingBlack ||
7989              gameMode == BeginningOfGame)) {
7990             SendToProgram("force\n", &first);
7991             if (first.usePing) {
7992               char buf[MSG_SIZ];
7993               sprintf(buf, "ping %d\n", ++first.lastPing);
7994               SendToProgram(buf, &first);
7995             }
7996         }
7997     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7998         /* Kill off first chess program */
7999         if (first.isr != NULL)
8000           RemoveInputSource(first.isr);
8001         first.isr = NULL;
8002     
8003         if (first.pr != NoProc) {
8004             ExitAnalyzeMode();
8005             DoSleep( appData.delayBeforeQuit );
8006             SendToProgram("quit\n", &first);
8007             DoSleep( appData.delayAfterQuit );
8008             DestroyChildProcess(first.pr, first.useSigterm);
8009         }
8010         first.pr = NoProc;
8011     }
8012     if (second.reuse) {
8013         /* Put second chess program into idle state */
8014         if (second.pr != NoProc &&
8015             gameMode == TwoMachinesPlay) {
8016             SendToProgram("force\n", &second);
8017             if (second.usePing) {
8018               char buf[MSG_SIZ];
8019               sprintf(buf, "ping %d\n", ++second.lastPing);
8020               SendToProgram(buf, &second);
8021             }
8022         }
8023     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8024         /* Kill off second chess program */
8025         if (second.isr != NULL)
8026           RemoveInputSource(second.isr);
8027         second.isr = NULL;
8028     
8029         if (second.pr != NoProc) {
8030             DoSleep( appData.delayBeforeQuit );
8031             SendToProgram("quit\n", &second);
8032             DoSleep( appData.delayAfterQuit );
8033             DestroyChildProcess(second.pr, second.useSigterm);
8034         }
8035         second.pr = NoProc;
8036     }
8037
8038     if (matchMode && gameMode == TwoMachinesPlay) {
8039         switch (result) {
8040         case WhiteWins:
8041           if (first.twoMachinesColor[0] == 'w') {
8042             first.matchWins++;
8043           } else {
8044             second.matchWins++;
8045           }
8046           break;
8047         case BlackWins:
8048           if (first.twoMachinesColor[0] == 'b') {
8049             first.matchWins++;
8050           } else {
8051             second.matchWins++;
8052           }
8053           break;
8054         default:
8055           break;
8056         }
8057         if (matchGame < appData.matchGames) {
8058             char *tmp;
8059             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8060                 tmp = first.twoMachinesColor;
8061                 first.twoMachinesColor = second.twoMachinesColor;
8062                 second.twoMachinesColor = tmp;
8063             }
8064             gameMode = nextGameMode;
8065             matchGame++;
8066             if(appData.matchPause>10000 || appData.matchPause<10)
8067                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8068             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8069             endingGame = 0; /* [HGM] crash */
8070             return;
8071         } else {
8072             char buf[MSG_SIZ];
8073             gameMode = nextGameMode;
8074             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8075                     first.tidy, second.tidy,
8076                     first.matchWins, second.matchWins,
8077                     appData.matchGames - (first.matchWins + second.matchWins));
8078             DisplayFatalError(buf, 0, 0);
8079         }
8080     }
8081     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8082         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8083       ExitAnalyzeMode();
8084     gameMode = nextGameMode;
8085     ModeHighlight();
8086     endingGame = 0;  /* [HGM] crash */
8087 }
8088
8089 /* Assumes program was just initialized (initString sent).
8090    Leaves program in force mode. */
8091 void
8092 FeedMovesToProgram(cps, upto) 
8093      ChessProgramState *cps;
8094      int upto;
8095 {
8096     int i;
8097     
8098     if (appData.debugMode)
8099       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8100               startedFromSetupPosition ? "position and " : "",
8101               backwardMostMove, upto, cps->which);
8102     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8103         // [HGM] variantswitch: make engine aware of new variant
8104         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8105                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8106         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8107         SendToProgram(buf, cps);
8108         currentlyInitializedVariant = gameInfo.variant;
8109     }
8110     SendToProgram("force\n", cps);
8111     if (startedFromSetupPosition) {
8112         SendBoard(cps, backwardMostMove);
8113     if (appData.debugMode) {
8114         fprintf(debugFP, "feedMoves\n");
8115     }
8116     }
8117     for (i = backwardMostMove; i < upto; i++) {
8118         SendMoveToProgram(i, cps);
8119     }
8120 }
8121
8122
8123 void
8124 ResurrectChessProgram()
8125 {
8126      /* The chess program may have exited.
8127         If so, restart it and feed it all the moves made so far. */
8128
8129     if (appData.noChessProgram || first.pr != NoProc) return;
8130     
8131     StartChessProgram(&first);
8132     InitChessProgram(&first, FALSE);
8133     FeedMovesToProgram(&first, currentMove);
8134
8135     if (!first.sendTime) {
8136         /* can't tell gnuchess what its clock should read,
8137            so we bow to its notion. */
8138         ResetClocks();
8139         timeRemaining[0][currentMove] = whiteTimeRemaining;
8140         timeRemaining[1][currentMove] = blackTimeRemaining;
8141     }
8142
8143     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8144                 appData.icsEngineAnalyze) && first.analysisSupport) {
8145       SendToProgram("analyze\n", &first);
8146       first.analyzing = TRUE;
8147     }
8148 }
8149
8150 /*
8151  * Button procedures
8152  */
8153 void
8154 Reset(redraw, init)
8155      int redraw, init;
8156 {
8157     int i;
8158
8159     if (appData.debugMode) {
8160         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8161                 redraw, init, gameMode);
8162     }
8163     pausing = pauseExamInvalid = FALSE;
8164     startedFromSetupPosition = blackPlaysFirst = FALSE;
8165     firstMove = TRUE;
8166     whiteFlag = blackFlag = FALSE;
8167     userOfferedDraw = FALSE;
8168     hintRequested = bookRequested = FALSE;
8169     first.maybeThinking = FALSE;
8170     second.maybeThinking = FALSE;
8171     first.bookSuspend = FALSE; // [HGM] book
8172     second.bookSuspend = FALSE;
8173     thinkOutput[0] = NULLCHAR;
8174     lastHint[0] = NULLCHAR;
8175     ClearGameInfo(&gameInfo);
8176     gameInfo.variant = StringToVariant(appData.variant);
8177     ics_user_moved = ics_clock_paused = FALSE;
8178     ics_getting_history = H_FALSE;
8179     ics_gamenum = -1;
8180     white_holding[0] = black_holding[0] = NULLCHAR;
8181     ClearProgramStats();
8182     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8183     
8184     ResetFrontEnd();
8185     ClearHighlights();
8186     flipView = appData.flipView;
8187     ClearPremoveHighlights();
8188     gotPremove = FALSE;
8189     alarmSounded = FALSE;
8190
8191     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8192     if(appData.serverMovesName != NULL) {
8193         /* [HGM] prepare to make moves file for broadcasting */
8194         clock_t t = clock();
8195         if(serverMoves != NULL) fclose(serverMoves);
8196         serverMoves = fopen(appData.serverMovesName, "r");
8197         if(serverMoves != NULL) {
8198             fclose(serverMoves);
8199             /* delay 15 sec before overwriting, so all clients can see end */
8200             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8201         }
8202         serverMoves = fopen(appData.serverMovesName, "w");
8203     }
8204
8205     ExitAnalyzeMode();
8206     gameMode = BeginningOfGame;
8207     ModeHighlight();
8208     if(appData.icsActive) gameInfo.variant = VariantNormal;
8209     currentMove = forwardMostMove = backwardMostMove = 0;
8210     InitPosition(redraw);
8211     for (i = 0; i < MAX_MOVES; i++) {
8212         if (commentList[i] != NULL) {
8213             free(commentList[i]);
8214             commentList[i] = NULL;
8215         }
8216     }
8217     ResetClocks();
8218     timeRemaining[0][0] = whiteTimeRemaining;
8219     timeRemaining[1][0] = blackTimeRemaining;
8220     if (first.pr == NULL) {
8221         StartChessProgram(&first);
8222     }
8223     if (init) {
8224             InitChessProgram(&first, startedFromSetupPosition);
8225     }
8226     DisplayTitle("");
8227     DisplayMessage("", "");
8228     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8229     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8230 }
8231
8232 void
8233 AutoPlayGameLoop()
8234 {
8235     for (;;) {
8236         if (!AutoPlayOneMove())
8237           return;
8238         if (matchMode || appData.timeDelay == 0)
8239           continue;
8240         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8241           return;
8242         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8243         break;
8244     }
8245 }
8246
8247
8248 int
8249 AutoPlayOneMove()
8250 {
8251     int fromX, fromY, toX, toY;
8252
8253     if (appData.debugMode) {
8254       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8255     }
8256
8257     if (gameMode != PlayFromGameFile)
8258       return FALSE;
8259
8260     if (currentMove >= forwardMostMove) {
8261       gameMode = EditGame;
8262       ModeHighlight();
8263
8264       /* [AS] Clear current move marker at the end of a game */
8265       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8266
8267       return FALSE;
8268     }
8269     
8270     toX = moveList[currentMove][2] - AAA;
8271     toY = moveList[currentMove][3] - ONE;
8272
8273     if (moveList[currentMove][1] == '@') {
8274         if (appData.highlightLastMove) {
8275             SetHighlights(-1, -1, toX, toY);
8276         }
8277     } else {
8278         fromX = moveList[currentMove][0] - AAA;
8279         fromY = moveList[currentMove][1] - ONE;
8280
8281         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8282
8283         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8284
8285         if (appData.highlightLastMove) {
8286             SetHighlights(fromX, fromY, toX, toY);
8287         }
8288     }
8289     DisplayMove(currentMove);
8290     SendMoveToProgram(currentMove++, &first);
8291     DisplayBothClocks();
8292     DrawPosition(FALSE, boards[currentMove]);
8293     // [HGM] PV info: always display, routine tests if empty
8294     DisplayComment(currentMove - 1, commentList[currentMove]);
8295     return TRUE;
8296 }
8297
8298
8299 int
8300 LoadGameOneMove(readAhead)
8301      ChessMove readAhead;
8302 {
8303     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8304     char promoChar = NULLCHAR;
8305     ChessMove moveType;
8306     char move[MSG_SIZ];
8307     char *p, *q;
8308     
8309     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8310         gameMode != AnalyzeMode && gameMode != Training) {
8311         gameFileFP = NULL;
8312         return FALSE;
8313     }
8314     
8315     yyboardindex = forwardMostMove;
8316     if (readAhead != (ChessMove)0) {
8317       moveType = readAhead;
8318     } else {
8319       if (gameFileFP == NULL)
8320           return FALSE;
8321       moveType = (ChessMove) yylex();
8322     }
8323     
8324     done = FALSE;
8325     switch (moveType) {
8326       case Comment:
8327         if (appData.debugMode) 
8328           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8329         p = yy_text;
8330         if (*p == '{' || *p == '[' || *p == '(') {
8331             p[strlen(p) - 1] = NULLCHAR;
8332             p++;
8333         }
8334
8335         /* append the comment but don't display it */
8336         while (*p == '\n') p++;
8337         AppendComment(currentMove, p);
8338         return TRUE;
8339
8340       case WhiteCapturesEnPassant:
8341       case BlackCapturesEnPassant:
8342       case WhitePromotionChancellor:
8343       case BlackPromotionChancellor:
8344       case WhitePromotionArchbishop:
8345       case BlackPromotionArchbishop:
8346       case WhitePromotionCentaur:
8347       case BlackPromotionCentaur:
8348       case WhitePromotionQueen:
8349       case BlackPromotionQueen:
8350       case WhitePromotionRook:
8351       case BlackPromotionRook:
8352       case WhitePromotionBishop:
8353       case BlackPromotionBishop:
8354       case WhitePromotionKnight:
8355       case BlackPromotionKnight:
8356       case WhitePromotionKing:
8357       case BlackPromotionKing:
8358       case NormalMove:
8359       case WhiteKingSideCastle:
8360       case WhiteQueenSideCastle:
8361       case BlackKingSideCastle:
8362       case BlackQueenSideCastle:
8363       case WhiteKingSideCastleWild:
8364       case WhiteQueenSideCastleWild:
8365       case BlackKingSideCastleWild:
8366       case BlackQueenSideCastleWild:
8367       /* PUSH Fabien */
8368       case WhiteHSideCastleFR:
8369       case WhiteASideCastleFR:
8370       case BlackHSideCastleFR:
8371       case BlackASideCastleFR:
8372       /* POP Fabien */
8373         if (appData.debugMode)
8374           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8375         fromX = currentMoveString[0] - AAA;
8376         fromY = currentMoveString[1] - ONE;
8377         toX = currentMoveString[2] - AAA;
8378         toY = currentMoveString[3] - ONE;
8379         promoChar = currentMoveString[4];
8380         break;
8381
8382       case WhiteDrop:
8383       case BlackDrop:
8384         if (appData.debugMode)
8385           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8386         fromX = moveType == WhiteDrop ?
8387           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8388         (int) CharToPiece(ToLower(currentMoveString[0]));
8389         fromY = DROP_RANK;
8390         toX = currentMoveString[2] - AAA;
8391         toY = currentMoveString[3] - ONE;
8392         break;
8393
8394       case WhiteWins:
8395       case BlackWins:
8396       case GameIsDrawn:
8397       case GameUnfinished:
8398         if (appData.debugMode)
8399           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8400         p = strchr(yy_text, '{');
8401         if (p == NULL) p = strchr(yy_text, '(');
8402         if (p == NULL) {
8403             p = yy_text;
8404             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8405         } else {
8406             q = strchr(p, *p == '{' ? '}' : ')');
8407             if (q != NULL) *q = NULLCHAR;
8408             p++;
8409         }
8410         GameEnds(moveType, p, GE_FILE);
8411         done = TRUE;
8412         if (cmailMsgLoaded) {
8413             ClearHighlights();
8414             flipView = WhiteOnMove(currentMove);
8415             if (moveType == GameUnfinished) flipView = !flipView;
8416             if (appData.debugMode)
8417               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8418         }
8419         break;
8420
8421       case (ChessMove) 0:       /* end of file */
8422         if (appData.debugMode)
8423           fprintf(debugFP, "Parser hit end of file\n");
8424         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8425                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8426           case MT_NONE:
8427           case MT_CHECK:
8428             break;
8429           case MT_CHECKMATE:
8430           case MT_STAINMATE:
8431             if (WhiteOnMove(currentMove)) {
8432                 GameEnds(BlackWins, "Black mates", GE_FILE);
8433             } else {
8434                 GameEnds(WhiteWins, "White mates", GE_FILE);
8435             }
8436             break;
8437           case MT_STALEMATE:
8438             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8439             break;
8440         }
8441         done = TRUE;
8442         break;
8443
8444       case MoveNumberOne:
8445         if (lastLoadGameStart == GNUChessGame) {
8446             /* GNUChessGames have numbers, but they aren't move numbers */
8447             if (appData.debugMode)
8448               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8449                       yy_text, (int) moveType);
8450             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8451         }
8452         /* else fall thru */
8453
8454       case XBoardGame:
8455       case GNUChessGame:
8456       case PGNTag:
8457         /* Reached start of next game in file */
8458         if (appData.debugMode)
8459           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8460         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8461                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8462           case MT_NONE:
8463           case MT_CHECK:
8464             break;
8465           case MT_CHECKMATE:
8466           case MT_STAINMATE:
8467             if (WhiteOnMove(currentMove)) {
8468                 GameEnds(BlackWins, "Black mates", GE_FILE);
8469             } else {
8470                 GameEnds(WhiteWins, "White mates", GE_FILE);
8471             }
8472             break;
8473           case MT_STALEMATE:
8474             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8475             break;
8476         }
8477         done = TRUE;
8478         break;
8479
8480       case PositionDiagram:     /* should not happen; ignore */
8481       case ElapsedTime:         /* ignore */
8482       case NAG:                 /* ignore */
8483         if (appData.debugMode)
8484           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8485                   yy_text, (int) moveType);
8486         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8487
8488       case IllegalMove:
8489         if (appData.testLegality) {
8490             if (appData.debugMode)
8491               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8492             sprintf(move, _("Illegal move: %d.%s%s"),
8493                     (forwardMostMove / 2) + 1,
8494                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8495             DisplayError(move, 0);
8496             done = TRUE;
8497         } else {
8498             if (appData.debugMode)
8499               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8500                       yy_text, currentMoveString);
8501             fromX = currentMoveString[0] - AAA;
8502             fromY = currentMoveString[1] - ONE;
8503             toX = currentMoveString[2] - AAA;
8504             toY = currentMoveString[3] - ONE;
8505             promoChar = currentMoveString[4];
8506         }
8507         break;
8508
8509       case AmbiguousMove:
8510         if (appData.debugMode)
8511           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8512         sprintf(move, _("Ambiguous move: %d.%s%s"),
8513                 (forwardMostMove / 2) + 1,
8514                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8515         DisplayError(move, 0);
8516         done = TRUE;
8517         break;
8518
8519       default:
8520       case ImpossibleMove:
8521         if (appData.debugMode)
8522           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8523         sprintf(move, _("Illegal move: %d.%s%s"),
8524                 (forwardMostMove / 2) + 1,
8525                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8526         DisplayError(move, 0);
8527         done = TRUE;
8528         break;
8529     }
8530
8531     if (done) {
8532         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8533             DrawPosition(FALSE, boards[currentMove]);
8534             DisplayBothClocks();
8535             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8536               DisplayComment(currentMove - 1, commentList[currentMove]);
8537         }
8538         (void) StopLoadGameTimer();
8539         gameFileFP = NULL;
8540         cmailOldMove = forwardMostMove;
8541         return FALSE;
8542     } else {
8543         /* currentMoveString is set as a side-effect of yylex */
8544         strcat(currentMoveString, "\n");
8545         strcpy(moveList[forwardMostMove], currentMoveString);
8546         
8547         thinkOutput[0] = NULLCHAR;
8548         MakeMove(fromX, fromY, toX, toY, promoChar);
8549         currentMove = forwardMostMove;
8550         return TRUE;
8551     }
8552 }
8553
8554 /* Load the nth game from the given file */
8555 int
8556 LoadGameFromFile(filename, n, title, useList)
8557      char *filename;
8558      int n;
8559      char *title;
8560      /*Boolean*/ int useList;
8561 {
8562     FILE *f;
8563     char buf[MSG_SIZ];
8564
8565     if (strcmp(filename, "-") == 0) {
8566         f = stdin;
8567         title = "stdin";
8568     } else {
8569         f = fopen(filename, "rb");
8570         if (f == NULL) {
8571           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8572             DisplayError(buf, errno);
8573             return FALSE;
8574         }
8575     }
8576     if (fseek(f, 0, 0) == -1) {
8577         /* f is not seekable; probably a pipe */
8578         useList = FALSE;
8579     }
8580     if (useList && n == 0) {
8581         int error = GameListBuild(f);
8582         if (error) {
8583             DisplayError(_("Cannot build game list"), error);
8584         } else if (!ListEmpty(&gameList) &&
8585                    ((ListGame *) gameList.tailPred)->number > 1) {
8586             GameListPopUp(f, title);
8587             return TRUE;
8588         }
8589         GameListDestroy();
8590         n = 1;
8591     }
8592     if (n == 0) n = 1;
8593     return LoadGame(f, n, title, FALSE);
8594 }
8595
8596
8597 void
8598 MakeRegisteredMove()
8599 {
8600     int fromX, fromY, toX, toY;
8601     char promoChar;
8602     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8603         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8604           case CMAIL_MOVE:
8605           case CMAIL_DRAW:
8606             if (appData.debugMode)
8607               fprintf(debugFP, "Restoring %s for game %d\n",
8608                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8609     
8610             thinkOutput[0] = NULLCHAR;
8611             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8612             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8613             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8614             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8615             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8616             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8617             MakeMove(fromX, fromY, toX, toY, promoChar);
8618             ShowMove(fromX, fromY, toX, toY);
8619               
8620             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8621                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8622               case MT_NONE:
8623               case MT_CHECK:
8624                 break;
8625                 
8626               case MT_CHECKMATE:
8627               case MT_STAINMATE:
8628                 if (WhiteOnMove(currentMove)) {
8629                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8630                 } else {
8631                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8632                 }
8633                 break;
8634                 
8635               case MT_STALEMATE:
8636                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8637                 break;
8638             }
8639
8640             break;
8641             
8642           case CMAIL_RESIGN:
8643             if (WhiteOnMove(currentMove)) {
8644                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8645             } else {
8646                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8647             }
8648             break;
8649             
8650           case CMAIL_ACCEPT:
8651             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8652             break;
8653               
8654           default:
8655             break;
8656         }
8657     }
8658
8659     return;
8660 }
8661
8662 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8663 int
8664 CmailLoadGame(f, gameNumber, title, useList)
8665      FILE *f;
8666      int gameNumber;
8667      char *title;
8668      int useList;
8669 {
8670     int retVal;
8671
8672     if (gameNumber > nCmailGames) {
8673         DisplayError(_("No more games in this message"), 0);
8674         return FALSE;
8675     }
8676     if (f == lastLoadGameFP) {
8677         int offset = gameNumber - lastLoadGameNumber;
8678         if (offset == 0) {
8679             cmailMsg[0] = NULLCHAR;
8680             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8681                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8682                 nCmailMovesRegistered--;
8683             }
8684             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8685             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8686                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8687             }
8688         } else {
8689             if (! RegisterMove()) return FALSE;
8690         }
8691     }
8692
8693     retVal = LoadGame(f, gameNumber, title, useList);
8694
8695     /* Make move registered during previous look at this game, if any */
8696     MakeRegisteredMove();
8697
8698     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8699         commentList[currentMove]
8700           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8701         DisplayComment(currentMove - 1, commentList[currentMove]);
8702     }
8703
8704     return retVal;
8705 }
8706
8707 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8708 int
8709 ReloadGame(offset)
8710      int offset;
8711 {
8712     int gameNumber = lastLoadGameNumber + offset;
8713     if (lastLoadGameFP == NULL) {
8714         DisplayError(_("No game has been loaded yet"), 0);
8715         return FALSE;
8716     }
8717     if (gameNumber <= 0) {
8718         DisplayError(_("Can't back up any further"), 0);
8719         return FALSE;
8720     }
8721     if (cmailMsgLoaded) {
8722         return CmailLoadGame(lastLoadGameFP, gameNumber,
8723                              lastLoadGameTitle, lastLoadGameUseList);
8724     } else {
8725         return LoadGame(lastLoadGameFP, gameNumber,
8726                         lastLoadGameTitle, lastLoadGameUseList);
8727     }
8728 }
8729
8730
8731
8732 /* Load the nth game from open file f */
8733 int
8734 LoadGame(f, gameNumber, title, useList)
8735      FILE *f;
8736      int gameNumber;
8737      char *title;
8738      int useList;
8739 {
8740     ChessMove cm;
8741     char buf[MSG_SIZ];
8742     int gn = gameNumber;
8743     ListGame *lg = NULL;
8744     int numPGNTags = 0;
8745     int err;
8746     GameMode oldGameMode;
8747     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8748
8749     if (appData.debugMode) 
8750         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8751
8752     if (gameMode == Training )
8753         SetTrainingModeOff();
8754
8755     oldGameMode = gameMode;
8756     if (gameMode != BeginningOfGame) {
8757       Reset(FALSE, TRUE);
8758     }
8759
8760     gameFileFP = f;
8761     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8762         fclose(lastLoadGameFP);
8763     }
8764
8765     if (useList) {
8766         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8767         
8768         if (lg) {
8769             fseek(f, lg->offset, 0);
8770             GameListHighlight(gameNumber);
8771             gn = 1;
8772         }
8773         else {
8774             DisplayError(_("Game number out of range"), 0);
8775             return FALSE;
8776         }
8777     } else {
8778         GameListDestroy();
8779         if (fseek(f, 0, 0) == -1) {
8780             if (f == lastLoadGameFP ?
8781                 gameNumber == lastLoadGameNumber + 1 :
8782                 gameNumber == 1) {
8783                 gn = 1;
8784             } else {
8785                 DisplayError(_("Can't seek on game file"), 0);
8786                 return FALSE;
8787             }
8788         }
8789     }
8790     lastLoadGameFP = f;
8791     lastLoadGameNumber = gameNumber;
8792     strcpy(lastLoadGameTitle, title);
8793     lastLoadGameUseList = useList;
8794
8795     yynewfile(f);
8796
8797     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8798       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8799                 lg->gameInfo.black);
8800             DisplayTitle(buf);
8801     } else if (*title != NULLCHAR) {
8802         if (gameNumber > 1) {
8803             sprintf(buf, "%s %d", title, gameNumber);
8804             DisplayTitle(buf);
8805         } else {
8806             DisplayTitle(title);
8807         }
8808     }
8809
8810     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8811         gameMode = PlayFromGameFile;
8812         ModeHighlight();
8813     }
8814
8815     currentMove = forwardMostMove = backwardMostMove = 0;
8816     CopyBoard(boards[0], initialPosition);
8817     StopClocks();
8818
8819     /*
8820      * Skip the first gn-1 games in the file.
8821      * Also skip over anything that precedes an identifiable 
8822      * start of game marker, to avoid being confused by 
8823      * garbage at the start of the file.  Currently 
8824      * recognized start of game markers are the move number "1",
8825      * the pattern "gnuchess .* game", the pattern
8826      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8827      * A game that starts with one of the latter two patterns
8828      * will also have a move number 1, possibly
8829      * following a position diagram.
8830      * 5-4-02: Let's try being more lenient and allowing a game to
8831      * start with an unnumbered move.  Does that break anything?
8832      */
8833     cm = lastLoadGameStart = (ChessMove) 0;
8834     while (gn > 0) {
8835         yyboardindex = forwardMostMove;
8836         cm = (ChessMove) yylex();
8837         switch (cm) {
8838           case (ChessMove) 0:
8839             if (cmailMsgLoaded) {
8840                 nCmailGames = CMAIL_MAX_GAMES - gn;
8841             } else {
8842                 Reset(TRUE, TRUE);
8843                 DisplayError(_("Game not found in file"), 0);
8844             }
8845             return FALSE;
8846
8847           case GNUChessGame:
8848           case XBoardGame:
8849             gn--;
8850             lastLoadGameStart = cm;
8851             break;
8852             
8853           case MoveNumberOne:
8854             switch (lastLoadGameStart) {
8855               case GNUChessGame:
8856               case XBoardGame:
8857               case PGNTag:
8858                 break;
8859               case MoveNumberOne:
8860               case (ChessMove) 0:
8861                 gn--;           /* count this game */
8862                 lastLoadGameStart = cm;
8863                 break;
8864               default:
8865                 /* impossible */
8866                 break;
8867             }
8868             break;
8869
8870           case PGNTag:
8871             switch (lastLoadGameStart) {
8872               case GNUChessGame:
8873               case PGNTag:
8874               case MoveNumberOne:
8875               case (ChessMove) 0:
8876                 gn--;           /* count this game */
8877                 lastLoadGameStart = cm;
8878                 break;
8879               case XBoardGame:
8880                 lastLoadGameStart = cm; /* game counted already */
8881                 break;
8882               default:
8883                 /* impossible */
8884                 break;
8885             }
8886             if (gn > 0) {
8887                 do {
8888                     yyboardindex = forwardMostMove;
8889                     cm = (ChessMove) yylex();
8890                 } while (cm == PGNTag || cm == Comment);
8891             }
8892             break;
8893
8894           case WhiteWins:
8895           case BlackWins:
8896           case GameIsDrawn:
8897             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8898                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8899                     != CMAIL_OLD_RESULT) {
8900                     nCmailResults ++ ;
8901                     cmailResult[  CMAIL_MAX_GAMES
8902                                 - gn - 1] = CMAIL_OLD_RESULT;
8903                 }
8904             }
8905             break;
8906
8907           case NormalMove:
8908             /* Only a NormalMove can be at the start of a game
8909              * without a position diagram. */
8910             if (lastLoadGameStart == (ChessMove) 0) {
8911               gn--;
8912               lastLoadGameStart = MoveNumberOne;
8913             }
8914             break;
8915
8916           default:
8917             break;
8918         }
8919     }
8920     
8921     if (appData.debugMode)
8922       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8923
8924     if (cm == XBoardGame) {
8925         /* Skip any header junk before position diagram and/or move 1 */
8926         for (;;) {
8927             yyboardindex = forwardMostMove;
8928             cm = (ChessMove) yylex();
8929
8930             if (cm == (ChessMove) 0 ||
8931                 cm == GNUChessGame || cm == XBoardGame) {
8932                 /* Empty game; pretend end-of-file and handle later */
8933                 cm = (ChessMove) 0;
8934                 break;
8935             }
8936
8937             if (cm == MoveNumberOne || cm == PositionDiagram ||
8938                 cm == PGNTag || cm == Comment)
8939               break;
8940         }
8941     } else if (cm == GNUChessGame) {
8942         if (gameInfo.event != NULL) {
8943             free(gameInfo.event);
8944         }
8945         gameInfo.event = StrSave(yy_text);
8946     }   
8947
8948     startedFromSetupPosition = FALSE;
8949     while (cm == PGNTag) {
8950         if (appData.debugMode) 
8951           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8952         err = ParsePGNTag(yy_text, &gameInfo);
8953         if (!err) numPGNTags++;
8954
8955         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8956         if(gameInfo.variant != oldVariant) {
8957             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8958             InitPosition(TRUE);
8959             oldVariant = gameInfo.variant;
8960             if (appData.debugMode) 
8961               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8962         }
8963
8964
8965         if (gameInfo.fen != NULL) {
8966           Board initial_position;
8967           startedFromSetupPosition = TRUE;
8968           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8969             Reset(TRUE, TRUE);
8970             DisplayError(_("Bad FEN position in file"), 0);
8971             return FALSE;
8972           }
8973           CopyBoard(boards[0], initial_position);
8974           if (blackPlaysFirst) {
8975             currentMove = forwardMostMove = backwardMostMove = 1;
8976             CopyBoard(boards[1], initial_position);
8977             strcpy(moveList[0], "");
8978             strcpy(parseList[0], "");
8979             timeRemaining[0][1] = whiteTimeRemaining;
8980             timeRemaining[1][1] = blackTimeRemaining;
8981             if (commentList[0] != NULL) {
8982               commentList[1] = commentList[0];
8983               commentList[0] = NULL;
8984             }
8985           } else {
8986             currentMove = forwardMostMove = backwardMostMove = 0;
8987           }
8988           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8989           {   int i;
8990               initialRulePlies = FENrulePlies;
8991               epStatus[forwardMostMove] = FENepStatus;
8992               for( i=0; i< nrCastlingRights; i++ )
8993                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8994           }
8995           yyboardindex = forwardMostMove;
8996           free(gameInfo.fen);
8997           gameInfo.fen = NULL;
8998         }
8999
9000         yyboardindex = forwardMostMove;
9001         cm = (ChessMove) yylex();
9002
9003         /* Handle comments interspersed among the tags */
9004         while (cm == Comment) {
9005             char *p;
9006             if (appData.debugMode) 
9007               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9008             p = yy_text;
9009             if (*p == '{' || *p == '[' || *p == '(') {
9010                 p[strlen(p) - 1] = NULLCHAR;
9011                 p++;
9012             }
9013             while (*p == '\n') p++;
9014             AppendComment(currentMove, p);
9015             yyboardindex = forwardMostMove;
9016             cm = (ChessMove) yylex();
9017         }
9018     }
9019
9020     /* don't rely on existence of Event tag since if game was
9021      * pasted from clipboard the Event tag may not exist
9022      */
9023     if (numPGNTags > 0){
9024         char *tags;
9025         if (gameInfo.variant == VariantNormal) {
9026           gameInfo.variant = StringToVariant(gameInfo.event);
9027         }
9028         if (!matchMode) {
9029           if( appData.autoDisplayTags ) {
9030             tags = PGNTags(&gameInfo);
9031             TagsPopUp(tags, CmailMsg());
9032             free(tags);
9033           }
9034         }
9035     } else {
9036         /* Make something up, but don't display it now */
9037         SetGameInfo();
9038         TagsPopDown();
9039     }
9040
9041     if (cm == PositionDiagram) {
9042         int i, j;
9043         char *p;
9044         Board initial_position;
9045
9046         if (appData.debugMode)
9047           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9048
9049         if (!startedFromSetupPosition) {
9050             p = yy_text;
9051             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9052               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9053                 switch (*p) {
9054                   case '[':
9055                   case '-':
9056                   case ' ':
9057                   case '\t':
9058                   case '\n':
9059                   case '\r':
9060                     break;
9061                   default:
9062                     initial_position[i][j++] = CharToPiece(*p);
9063                     break;
9064                 }
9065             while (*p == ' ' || *p == '\t' ||
9066                    *p == '\n' || *p == '\r') p++;
9067         
9068             if (strncmp(p, "black", strlen("black"))==0)
9069               blackPlaysFirst = TRUE;
9070             else
9071               blackPlaysFirst = FALSE;
9072             startedFromSetupPosition = TRUE;
9073         
9074             CopyBoard(boards[0], initial_position);
9075             if (blackPlaysFirst) {
9076                 currentMove = forwardMostMove = backwardMostMove = 1;
9077                 CopyBoard(boards[1], initial_position);
9078                 strcpy(moveList[0], "");
9079                 strcpy(parseList[0], "");
9080                 timeRemaining[0][1] = whiteTimeRemaining;
9081                 timeRemaining[1][1] = blackTimeRemaining;
9082                 if (commentList[0] != NULL) {
9083                     commentList[1] = commentList[0];
9084                     commentList[0] = NULL;
9085                 }
9086             } else {
9087                 currentMove = forwardMostMove = backwardMostMove = 0;
9088             }
9089         }
9090         yyboardindex = forwardMostMove;
9091         cm = (ChessMove) yylex();
9092     }
9093
9094     if (first.pr == NoProc) {
9095         StartChessProgram(&first);
9096     }
9097     InitChessProgram(&first, FALSE);
9098     SendToProgram("force\n", &first);
9099     if (startedFromSetupPosition) {
9100         SendBoard(&first, forwardMostMove);
9101     if (appData.debugMode) {
9102         fprintf(debugFP, "Load Game\n");
9103     }
9104         DisplayBothClocks();
9105     }      
9106
9107     /* [HGM] server: flag to write setup moves in broadcast file as one */
9108     loadFlag = appData.suppressLoadMoves;
9109
9110     while (cm == Comment) {
9111         char *p;
9112         if (appData.debugMode) 
9113           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9114         p = yy_text;
9115         if (*p == '{' || *p == '[' || *p == '(') {
9116             p[strlen(p) - 1] = NULLCHAR;
9117             p++;
9118         }
9119         while (*p == '\n') p++;
9120         AppendComment(currentMove, p);
9121         yyboardindex = forwardMostMove;
9122         cm = (ChessMove) yylex();
9123     }
9124
9125     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9126         cm == WhiteWins || cm == BlackWins ||
9127         cm == GameIsDrawn || cm == GameUnfinished) {
9128         DisplayMessage("", _("No moves in game"));
9129         if (cmailMsgLoaded) {
9130             if (appData.debugMode)
9131               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9132             ClearHighlights();
9133             flipView = FALSE;
9134         }
9135         DrawPosition(FALSE, boards[currentMove]);
9136         DisplayBothClocks();
9137         gameMode = EditGame;
9138         ModeHighlight();
9139         gameFileFP = NULL;
9140         cmailOldMove = 0;
9141         return TRUE;
9142     }
9143
9144     // [HGM] PV info: routine tests if comment empty
9145     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9146         DisplayComment(currentMove - 1, commentList[currentMove]);
9147     }
9148     if (!matchMode && appData.timeDelay != 0) 
9149       DrawPosition(FALSE, boards[currentMove]);
9150
9151     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9152       programStats.ok_to_send = 1;
9153     }
9154
9155     /* if the first token after the PGN tags is a move
9156      * and not move number 1, retrieve it from the parser 
9157      */
9158     if (cm != MoveNumberOne)
9159         LoadGameOneMove(cm);
9160
9161     /* load the remaining moves from the file */
9162     while (LoadGameOneMove((ChessMove)0)) {
9163       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9164       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9165     }
9166
9167     /* rewind to the start of the game */
9168     currentMove = backwardMostMove;
9169
9170     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9171
9172     if (oldGameMode == AnalyzeFile ||
9173         oldGameMode == AnalyzeMode) {
9174       AnalyzeFileEvent();
9175     }
9176
9177     if (matchMode || appData.timeDelay == 0) {
9178       ToEndEvent();
9179       gameMode = EditGame;
9180       ModeHighlight();
9181     } else if (appData.timeDelay > 0) {
9182       AutoPlayGameLoop();
9183     }
9184
9185     if (appData.debugMode) 
9186         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9187
9188     loadFlag = 0; /* [HGM] true game starts */
9189     return TRUE;
9190 }
9191
9192 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9193 int
9194 ReloadPosition(offset)
9195      int offset;
9196 {
9197     int positionNumber = lastLoadPositionNumber + offset;
9198     if (lastLoadPositionFP == NULL) {
9199         DisplayError(_("No position has been loaded yet"), 0);
9200         return FALSE;
9201     }
9202     if (positionNumber <= 0) {
9203         DisplayError(_("Can't back up any further"), 0);
9204         return FALSE;
9205     }
9206     return LoadPosition(lastLoadPositionFP, positionNumber,
9207                         lastLoadPositionTitle);
9208 }
9209
9210 /* Load the nth position from the given file */
9211 int
9212 LoadPositionFromFile(filename, n, title)
9213      char *filename;
9214      int n;
9215      char *title;
9216 {
9217     FILE *f;
9218     char buf[MSG_SIZ];
9219
9220     if (strcmp(filename, "-") == 0) {
9221         return LoadPosition(stdin, n, "stdin");
9222     } else {
9223         f = fopen(filename, "rb");
9224         if (f == NULL) {
9225             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9226             DisplayError(buf, errno);
9227             return FALSE;
9228         } else {
9229             return LoadPosition(f, n, title);
9230         }
9231     }
9232 }
9233
9234 /* Load the nth position from the given open file, and close it */
9235 int
9236 LoadPosition(f, positionNumber, title)
9237      FILE *f;
9238      int positionNumber;
9239      char *title;
9240 {
9241     char *p, line[MSG_SIZ];
9242     Board initial_position;
9243     int i, j, fenMode, pn;
9244     
9245     if (gameMode == Training )
9246         SetTrainingModeOff();
9247
9248     if (gameMode != BeginningOfGame) {
9249         Reset(FALSE, TRUE);
9250     }
9251     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9252         fclose(lastLoadPositionFP);
9253     }
9254     if (positionNumber == 0) positionNumber = 1;
9255     lastLoadPositionFP = f;
9256     lastLoadPositionNumber = positionNumber;
9257     strcpy(lastLoadPositionTitle, title);
9258     if (first.pr == NoProc) {
9259       StartChessProgram(&first);
9260       InitChessProgram(&first, FALSE);
9261     }    
9262     pn = positionNumber;
9263     if (positionNumber < 0) {
9264         /* Negative position number means to seek to that byte offset */
9265         if (fseek(f, -positionNumber, 0) == -1) {
9266             DisplayError(_("Can't seek on position file"), 0);
9267             return FALSE;
9268         };
9269         pn = 1;
9270     } else {
9271         if (fseek(f, 0, 0) == -1) {
9272             if (f == lastLoadPositionFP ?
9273                 positionNumber == lastLoadPositionNumber + 1 :
9274                 positionNumber == 1) {
9275                 pn = 1;
9276             } else {
9277                 DisplayError(_("Can't seek on position file"), 0);
9278                 return FALSE;
9279             }
9280         }
9281     }
9282     /* See if this file is FEN or old-style xboard */
9283     if (fgets(line, MSG_SIZ, f) == NULL) {
9284         DisplayError(_("Position not found in file"), 0);
9285         return FALSE;
9286     }
9287     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9288     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9289
9290     if (pn >= 2) {
9291         if (fenMode || line[0] == '#') pn--;
9292         while (pn > 0) {
9293             /* skip positions before number pn */
9294             if (fgets(line, MSG_SIZ, f) == NULL) {
9295                 Reset(TRUE, TRUE);
9296                 DisplayError(_("Position not found in file"), 0);
9297                 return FALSE;
9298             }
9299             if (fenMode || line[0] == '#') pn--;
9300         }
9301     }
9302
9303     if (fenMode) {
9304         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9305             DisplayError(_("Bad FEN position in file"), 0);
9306             return FALSE;
9307         }
9308     } else {
9309         (void) fgets(line, MSG_SIZ, f);
9310         (void) fgets(line, MSG_SIZ, f);
9311     
9312         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9313             (void) fgets(line, MSG_SIZ, f);
9314             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9315                 if (*p == ' ')
9316                   continue;
9317                 initial_position[i][j++] = CharToPiece(*p);
9318             }
9319         }
9320     
9321         blackPlaysFirst = FALSE;
9322         if (!feof(f)) {
9323             (void) fgets(line, MSG_SIZ, f);
9324             if (strncmp(line, "black", strlen("black"))==0)
9325               blackPlaysFirst = TRUE;
9326         }
9327     }
9328     startedFromSetupPosition = TRUE;
9329     
9330     SendToProgram("force\n", &first);
9331     CopyBoard(boards[0], initial_position);
9332     if (blackPlaysFirst) {
9333         currentMove = forwardMostMove = backwardMostMove = 1;
9334         strcpy(moveList[0], "");
9335         strcpy(parseList[0], "");
9336         CopyBoard(boards[1], initial_position);
9337         DisplayMessage("", _("Black to play"));
9338     } else {
9339         currentMove = forwardMostMove = backwardMostMove = 0;
9340         DisplayMessage("", _("White to play"));
9341     }
9342           /* [HGM] copy FEN attributes as well */
9343           {   int i;
9344               initialRulePlies = FENrulePlies;
9345               epStatus[forwardMostMove] = FENepStatus;
9346               for( i=0; i< nrCastlingRights; i++ )
9347                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9348           }
9349     SendBoard(&first, forwardMostMove);
9350     if (appData.debugMode) {
9351 int i, j;
9352   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9353   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9354         fprintf(debugFP, "Load Position\n");
9355     }
9356
9357     if (positionNumber > 1) {
9358         sprintf(line, "%s %d", title, positionNumber);
9359         DisplayTitle(line);
9360     } else {
9361         DisplayTitle(title);
9362     }
9363     gameMode = EditGame;
9364     ModeHighlight();
9365     ResetClocks();
9366     timeRemaining[0][1] = whiteTimeRemaining;
9367     timeRemaining[1][1] = blackTimeRemaining;
9368     DrawPosition(FALSE, boards[currentMove]);
9369    
9370     return TRUE;
9371 }
9372
9373
9374 void
9375 CopyPlayerNameIntoFileName(dest, src)
9376      char **dest, *src;
9377 {
9378     while (*src != NULLCHAR && *src != ',') {
9379         if (*src == ' ') {
9380             *(*dest)++ = '_';
9381             src++;
9382         } else {
9383             *(*dest)++ = *src++;
9384         }
9385     }
9386 }
9387
9388 char *DefaultFileName(ext)
9389      char *ext;
9390 {
9391     static char def[MSG_SIZ];
9392     char *p;
9393
9394     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9395         p = def;
9396         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9397         *p++ = '-';
9398         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9399         *p++ = '.';
9400         strcpy(p, ext);
9401     } else {
9402         def[0] = NULLCHAR;
9403     }
9404     return def;
9405 }
9406
9407 /* Save the current game to the given file */
9408 int
9409 SaveGameToFile(filename, append)
9410      char *filename;
9411      int append;
9412 {
9413     FILE *f;
9414     char buf[MSG_SIZ];
9415
9416     if (strcmp(filename, "-") == 0) {
9417         return SaveGame(stdout, 0, NULL);
9418     } else {
9419         f = fopen(filename, append ? "a" : "w");
9420         if (f == NULL) {
9421             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9422             DisplayError(buf, errno);
9423             return FALSE;
9424         } else {
9425             return SaveGame(f, 0, NULL);
9426         }
9427     }
9428 }
9429
9430 char *
9431 SavePart(str)
9432      char *str;
9433 {
9434     static char buf[MSG_SIZ];
9435     char *p;
9436     
9437     p = strchr(str, ' ');
9438     if (p == NULL) return str;
9439     strncpy(buf, str, p - str);
9440     buf[p - str] = NULLCHAR;
9441     return buf;
9442 }
9443
9444 #define PGN_MAX_LINE 75
9445
9446 #define PGN_SIDE_WHITE  0
9447 #define PGN_SIDE_BLACK  1
9448
9449 /* [AS] */
9450 static int FindFirstMoveOutOfBook( int side )
9451 {
9452     int result = -1;
9453
9454     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9455         int index = backwardMostMove;
9456         int has_book_hit = 0;
9457
9458         if( (index % 2) != side ) {
9459             index++;
9460         }
9461
9462         while( index < forwardMostMove ) {
9463             /* Check to see if engine is in book */
9464             int depth = pvInfoList[index].depth;
9465             int score = pvInfoList[index].score;
9466             int in_book = 0;
9467
9468             if( depth <= 2 ) {
9469                 in_book = 1;
9470             }
9471             else if( score == 0 && depth == 63 ) {
9472                 in_book = 1; /* Zappa */
9473             }
9474             else if( score == 2 && depth == 99 ) {
9475                 in_book = 1; /* Abrok */
9476             }
9477
9478             has_book_hit += in_book;
9479
9480             if( ! in_book ) {
9481                 result = index;
9482
9483                 break;
9484             }
9485
9486             index += 2;
9487         }
9488     }
9489
9490     return result;
9491 }
9492
9493 /* [AS] */
9494 void GetOutOfBookInfo( char * buf )
9495 {
9496     int oob[2];
9497     int i;
9498     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9499
9500     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9501     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9502
9503     *buf = '\0';
9504
9505     if( oob[0] >= 0 || oob[1] >= 0 ) {
9506         for( i=0; i<2; i++ ) {
9507             int idx = oob[i];
9508
9509             if( idx >= 0 ) {
9510                 if( i > 0 && oob[0] >= 0 ) {
9511                     strcat( buf, "   " );
9512                 }
9513
9514                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9515                 sprintf( buf+strlen(buf), "%s%.2f", 
9516                     pvInfoList[idx].score >= 0 ? "+" : "",
9517                     pvInfoList[idx].score / 100.0 );
9518             }
9519         }
9520     }
9521 }
9522
9523 /* Save game in PGN style and close the file */
9524 int
9525 SaveGamePGN(f)
9526      FILE *f;
9527 {
9528     int i, offset, linelen, newblock;
9529     time_t tm;
9530 //    char *movetext;
9531     char numtext[32];
9532     int movelen, numlen, blank;
9533     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9534
9535     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9536     
9537     tm = time((time_t *) NULL);
9538     
9539     PrintPGNTags(f, &gameInfo);
9540     
9541     if (backwardMostMove > 0 || startedFromSetupPosition) {
9542         char *fen = PositionToFEN(backwardMostMove, NULL);
9543         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9544         fprintf(f, "\n{--------------\n");
9545         PrintPosition(f, backwardMostMove);
9546         fprintf(f, "--------------}\n");
9547         free(fen);
9548     }
9549     else {
9550         /* [AS] Out of book annotation */
9551         if( appData.saveOutOfBookInfo ) {
9552             char buf[64];
9553
9554             GetOutOfBookInfo( buf );
9555
9556             if( buf[0] != '\0' ) {
9557                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9558             }
9559         }
9560
9561         fprintf(f, "\n");
9562     }
9563
9564     i = backwardMostMove;
9565     linelen = 0;
9566     newblock = TRUE;
9567
9568     while (i < forwardMostMove) {
9569         /* Print comments preceding this move */
9570         if (commentList[i] != NULL) {
9571             if (linelen > 0) fprintf(f, "\n");
9572             fprintf(f, "{\n%s}\n", commentList[i]);
9573             linelen = 0;
9574             newblock = TRUE;
9575         }
9576
9577         /* Format move number */
9578         if ((i % 2) == 0) {
9579             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9580         } else {
9581             if (newblock) {
9582                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9583             } else {
9584                 numtext[0] = NULLCHAR;
9585             }
9586         }
9587         numlen = strlen(numtext);
9588         newblock = FALSE;
9589
9590         /* Print move number */
9591         blank = linelen > 0 && numlen > 0;
9592         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9593             fprintf(f, "\n");
9594             linelen = 0;
9595             blank = 0;
9596         }
9597         if (blank) {
9598             fprintf(f, " ");
9599             linelen++;
9600         }
9601         fprintf(f, "%s", numtext);
9602         linelen += numlen;
9603
9604         /* Get move */
9605         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9606         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9607
9608         /* Print move */
9609         blank = linelen > 0 && movelen > 0;
9610         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9611             fprintf(f, "\n");
9612             linelen = 0;
9613             blank = 0;
9614         }
9615         if (blank) {
9616             fprintf(f, " ");
9617             linelen++;
9618         }
9619         fprintf(f, "%s", move_buffer);
9620         linelen += movelen;
9621
9622         /* [AS] Add PV info if present */
9623         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9624             /* [HGM] add time */
9625             char buf[MSG_SIZ]; int seconds = 0;
9626
9627             if(i >= backwardMostMove) {
9628                 if(WhiteOnMove(i))
9629                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9630                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9631                 else
9632                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9633                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9634             }
9635             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9636
9637             if( seconds <= 0) buf[0] = 0; else
9638             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9639                 seconds = (seconds + 4)/10; // round to full seconds
9640                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9641                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9642             }
9643
9644             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9645                 pvInfoList[i].score >= 0 ? "+" : "",
9646                 pvInfoList[i].score / 100.0,
9647                 pvInfoList[i].depth,
9648                 buf );
9649
9650             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9651
9652             /* Print score/depth */
9653             blank = linelen > 0 && movelen > 0;
9654             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9655                 fprintf(f, "\n");
9656                 linelen = 0;
9657                 blank = 0;
9658             }
9659             if (blank) {
9660                 fprintf(f, " ");
9661                 linelen++;
9662             }
9663             fprintf(f, "%s", move_buffer);
9664             linelen += movelen;
9665         }
9666
9667         i++;
9668     }
9669     
9670     /* Start a new line */
9671     if (linelen > 0) fprintf(f, "\n");
9672
9673     /* Print comments after last move */
9674     if (commentList[i] != NULL) {
9675         fprintf(f, "{\n%s}\n", commentList[i]);
9676     }
9677
9678     /* Print result */
9679     if (gameInfo.resultDetails != NULL &&
9680         gameInfo.resultDetails[0] != NULLCHAR) {
9681         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9682                 PGNResult(gameInfo.result));
9683     } else {
9684         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9685     }
9686
9687     fclose(f);
9688     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9689     return TRUE;
9690 }
9691
9692 /* Save game in old style and close the file */
9693 int
9694 SaveGameOldStyle(f)
9695      FILE *f;
9696 {
9697     int i, offset;
9698     time_t tm;
9699     
9700     tm = time((time_t *) NULL);
9701     
9702     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9703     PrintOpponents(f);
9704     
9705     if (backwardMostMove > 0 || startedFromSetupPosition) {
9706         fprintf(f, "\n[--------------\n");
9707         PrintPosition(f, backwardMostMove);
9708         fprintf(f, "--------------]\n");
9709     } else {
9710         fprintf(f, "\n");
9711     }
9712
9713     i = backwardMostMove;
9714     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9715
9716     while (i < forwardMostMove) {
9717         if (commentList[i] != NULL) {
9718             fprintf(f, "[%s]\n", commentList[i]);
9719         }
9720
9721         if ((i % 2) == 1) {
9722             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9723             i++;
9724         } else {
9725             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9726             i++;
9727             if (commentList[i] != NULL) {
9728                 fprintf(f, "\n");
9729                 continue;
9730             }
9731             if (i >= forwardMostMove) {
9732                 fprintf(f, "\n");
9733                 break;
9734             }
9735             fprintf(f, "%s\n", parseList[i]);
9736             i++;
9737         }
9738     }
9739     
9740     if (commentList[i] != NULL) {
9741         fprintf(f, "[%s]\n", commentList[i]);
9742     }
9743
9744     /* This isn't really the old style, but it's close enough */
9745     if (gameInfo.resultDetails != NULL &&
9746         gameInfo.resultDetails[0] != NULLCHAR) {
9747         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9748                 gameInfo.resultDetails);
9749     } else {
9750         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9751     }
9752
9753     fclose(f);
9754     return TRUE;
9755 }
9756
9757 /* Save the current game to open file f and close the file */
9758 int
9759 SaveGame(f, dummy, dummy2)
9760      FILE *f;
9761      int dummy;
9762      char *dummy2;
9763 {
9764     if (gameMode == EditPosition) EditPositionDone();
9765     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9766     if (appData.oldSaveStyle)
9767       return SaveGameOldStyle(f);
9768     else
9769       return SaveGamePGN(f);
9770 }
9771
9772 /* Save the current position to the given file */
9773 int
9774 SavePositionToFile(filename)
9775      char *filename;
9776 {
9777     FILE *f;
9778     char buf[MSG_SIZ];
9779
9780     if (strcmp(filename, "-") == 0) {
9781         return SavePosition(stdout, 0, NULL);
9782     } else {
9783         f = fopen(filename, "a");
9784         if (f == NULL) {
9785             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9786             DisplayError(buf, errno);
9787             return FALSE;
9788         } else {
9789             SavePosition(f, 0, NULL);
9790             return TRUE;
9791         }
9792     }
9793 }
9794
9795 /* Save the current position to the given open file and close the file */
9796 int
9797 SavePosition(f, dummy, dummy2)
9798      FILE *f;
9799      int dummy;
9800      char *dummy2;
9801 {
9802     time_t tm;
9803     char *fen;
9804     
9805     if (appData.oldSaveStyle) {
9806         tm = time((time_t *) NULL);
9807     
9808         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9809         PrintOpponents(f);
9810         fprintf(f, "[--------------\n");
9811         PrintPosition(f, currentMove);
9812         fprintf(f, "--------------]\n");
9813     } else {
9814         fen = PositionToFEN(currentMove, NULL);
9815         fprintf(f, "%s\n", fen);
9816         free(fen);
9817     }
9818     fclose(f);
9819     return TRUE;
9820 }
9821
9822 void
9823 ReloadCmailMsgEvent(unregister)
9824      int unregister;
9825 {
9826 #if !WIN32
9827     static char *inFilename = NULL;
9828     static char *outFilename;
9829     int i;
9830     struct stat inbuf, outbuf;
9831     int status;
9832     
9833     /* Any registered moves are unregistered if unregister is set, */
9834     /* i.e. invoked by the signal handler */
9835     if (unregister) {
9836         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9837             cmailMoveRegistered[i] = FALSE;
9838             if (cmailCommentList[i] != NULL) {
9839                 free(cmailCommentList[i]);
9840                 cmailCommentList[i] = NULL;
9841             }
9842         }
9843         nCmailMovesRegistered = 0;
9844     }
9845
9846     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9847         cmailResult[i] = CMAIL_NOT_RESULT;
9848     }
9849     nCmailResults = 0;
9850
9851     if (inFilename == NULL) {
9852         /* Because the filenames are static they only get malloced once  */
9853         /* and they never get freed                                      */
9854         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9855         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9856
9857         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9858         sprintf(outFilename, "%s.out", appData.cmailGameName);
9859     }
9860     
9861     status = stat(outFilename, &outbuf);
9862     if (status < 0) {
9863         cmailMailedMove = FALSE;
9864     } else {
9865         status = stat(inFilename, &inbuf);
9866         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9867     }
9868     
9869     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9870        counts the games, notes how each one terminated, etc.
9871        
9872        It would be nice to remove this kludge and instead gather all
9873        the information while building the game list.  (And to keep it
9874        in the game list nodes instead of having a bunch of fixed-size
9875        parallel arrays.)  Note this will require getting each game's
9876        termination from the PGN tags, as the game list builder does
9877        not process the game moves.  --mann
9878        */
9879     cmailMsgLoaded = TRUE;
9880     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9881     
9882     /* Load first game in the file or popup game menu */
9883     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9884
9885 #endif /* !WIN32 */
9886     return;
9887 }
9888
9889 int
9890 RegisterMove()
9891 {
9892     FILE *f;
9893     char string[MSG_SIZ];
9894
9895     if (   cmailMailedMove
9896         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9897         return TRUE;            /* Allow free viewing  */
9898     }
9899
9900     /* Unregister move to ensure that we don't leave RegisterMove        */
9901     /* with the move registered when the conditions for registering no   */
9902     /* longer hold                                                       */
9903     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9904         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9905         nCmailMovesRegistered --;
9906
9907         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9908           {
9909               free(cmailCommentList[lastLoadGameNumber - 1]);
9910               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9911           }
9912     }
9913
9914     if (cmailOldMove == -1) {
9915         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9916         return FALSE;
9917     }
9918
9919     if (currentMove > cmailOldMove + 1) {
9920         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9921         return FALSE;
9922     }
9923
9924     if (currentMove < cmailOldMove) {
9925         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9926         return FALSE;
9927     }
9928
9929     if (forwardMostMove > currentMove) {
9930         /* Silently truncate extra moves */
9931         TruncateGame();
9932     }
9933
9934     if (   (currentMove == cmailOldMove + 1)
9935         || (   (currentMove == cmailOldMove)
9936             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9937                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9938         if (gameInfo.result != GameUnfinished) {
9939             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9940         }
9941
9942         if (commentList[currentMove] != NULL) {
9943             cmailCommentList[lastLoadGameNumber - 1]
9944               = StrSave(commentList[currentMove]);
9945         }
9946         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9947
9948         if (appData.debugMode)
9949           fprintf(debugFP, "Saving %s for game %d\n",
9950                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9951
9952         sprintf(string,
9953                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9954         
9955         f = fopen(string, "w");
9956         if (appData.oldSaveStyle) {
9957             SaveGameOldStyle(f); /* also closes the file */
9958             
9959             sprintf(string, "%s.pos.out", appData.cmailGameName);
9960             f = fopen(string, "w");
9961             SavePosition(f, 0, NULL); /* also closes the file */
9962         } else {
9963             fprintf(f, "{--------------\n");
9964             PrintPosition(f, currentMove);
9965             fprintf(f, "--------------}\n\n");
9966             
9967             SaveGame(f, 0, NULL); /* also closes the file*/
9968         }
9969         
9970         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9971         nCmailMovesRegistered ++;
9972     } else if (nCmailGames == 1) {
9973         DisplayError(_("You have not made a move yet"), 0);
9974         return FALSE;
9975     }
9976
9977     return TRUE;
9978 }
9979
9980 void
9981 MailMoveEvent()
9982 {
9983 #if !WIN32
9984     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9985     FILE *commandOutput;
9986     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9987     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
9988     int nBuffers;
9989     int i;
9990     int archived;
9991     char *arcDir;
9992
9993     if (! cmailMsgLoaded) {
9994         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9995         return;
9996     }
9997
9998     if (nCmailGames == nCmailResults) {
9999         DisplayError(_("No unfinished games"), 0);
10000         return;
10001     }
10002
10003 #if CMAIL_PROHIBIT_REMAIL
10004     if (cmailMailedMove) {
10005         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);
10006         DisplayError(msg, 0);
10007         return;
10008     }
10009 #endif
10010
10011     if (! (cmailMailedMove || RegisterMove())) return;
10012     
10013     if (   cmailMailedMove
10014         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10015         sprintf(string, partCommandString,
10016                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10017         commandOutput = popen(string, "r");
10018
10019         if (commandOutput == NULL) {
10020             DisplayError(_("Failed to invoke cmail"), 0);
10021         } else {
10022             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10023                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10024             }
10025             if (nBuffers > 1) {
10026                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10027                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10028                 nBytes = MSG_SIZ - 1;
10029             } else {
10030                 (void) memcpy(msg, buffer, nBytes);
10031             }
10032             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10033
10034             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10035                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10036
10037                 archived = TRUE;
10038                 for (i = 0; i < nCmailGames; i ++) {
10039                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10040                         archived = FALSE;
10041                     }
10042                 }
10043                 if (   archived
10044                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10045                         != NULL)) {
10046                     sprintf(buffer, "%s/%s.%s.archive",
10047                             arcDir,
10048                             appData.cmailGameName,
10049                             gameInfo.date);
10050                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10051                     cmailMsgLoaded = FALSE;
10052                 }
10053             }
10054
10055             DisplayInformation(msg);
10056             pclose(commandOutput);
10057         }
10058     } else {
10059         if ((*cmailMsg) != '\0') {
10060             DisplayInformation(cmailMsg);
10061         }
10062     }
10063
10064     return;
10065 #endif /* !WIN32 */
10066 }
10067
10068 char *
10069 CmailMsg()
10070 {
10071 #if WIN32
10072     return NULL;
10073 #else
10074     int  prependComma = 0;
10075     char number[5];
10076     char string[MSG_SIZ];       /* Space for game-list */
10077     int  i;
10078     
10079     if (!cmailMsgLoaded) return "";
10080
10081     if (cmailMailedMove) {
10082         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10083     } else {
10084         /* Create a list of games left */
10085         sprintf(string, "[");
10086         for (i = 0; i < nCmailGames; i ++) {
10087             if (! (   cmailMoveRegistered[i]
10088                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10089                 if (prependComma) {
10090                     sprintf(number, ",%d", i + 1);
10091                 } else {
10092                     sprintf(number, "%d", i + 1);
10093                     prependComma = 1;
10094                 }
10095                 
10096                 strcat(string, number);
10097             }
10098         }
10099         strcat(string, "]");
10100
10101         if (nCmailMovesRegistered + nCmailResults == 0) {
10102             switch (nCmailGames) {
10103               case 1:
10104                 sprintf(cmailMsg,
10105                         _("Still need to make move for game\n"));
10106                 break;
10107                 
10108               case 2:
10109                 sprintf(cmailMsg,
10110                         _("Still need to make moves for both games\n"));
10111                 break;
10112                 
10113               default:
10114                 sprintf(cmailMsg,
10115                         _("Still need to make moves for all %d games\n"),
10116                         nCmailGames);
10117                 break;
10118             }
10119         } else {
10120             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10121               case 1:
10122                 sprintf(cmailMsg,
10123                         _("Still need to make a move for game %s\n"),
10124                         string);
10125                 break;
10126                 
10127               case 0:
10128                 if (nCmailResults == nCmailGames) {
10129                     sprintf(cmailMsg, _("No unfinished games\n"));
10130                 } else {
10131                     sprintf(cmailMsg, _("Ready to send mail\n"));
10132                 }
10133                 break;
10134                 
10135               default:
10136                 sprintf(cmailMsg,
10137                         _("Still need to make moves for games %s\n"),
10138                         string);
10139             }
10140         }
10141     }
10142     return cmailMsg;
10143 #endif /* WIN32 */
10144 }
10145
10146 void
10147 ResetGameEvent()
10148 {
10149     if (gameMode == Training)
10150       SetTrainingModeOff();
10151
10152     Reset(TRUE, TRUE);
10153     cmailMsgLoaded = FALSE;
10154     if (appData.icsActive) {
10155       SendToICS(ics_prefix);
10156       SendToICS("refresh\n");
10157     }
10158 }
10159
10160 void
10161 ExitEvent(status)
10162      int status;
10163 {
10164     exiting++;
10165     if (exiting > 2) {
10166       /* Give up on clean exit */
10167       exit(status);
10168     }
10169     if (exiting > 1) {
10170       /* Keep trying for clean exit */
10171       return;
10172     }
10173
10174     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10175
10176     if (telnetISR != NULL) {
10177       RemoveInputSource(telnetISR);
10178     }
10179     if (icsPR != NoProc) {
10180       DestroyChildProcess(icsPR, TRUE);
10181     }
10182
10183     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10184     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10185
10186     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10187     /* make sure this other one finishes before killing it!                  */
10188     if(endingGame) { int count = 0;
10189         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10190         while(endingGame && count++ < 10) DoSleep(1);
10191         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10192     }
10193
10194     /* Kill off chess programs */
10195     if (first.pr != NoProc) {
10196         ExitAnalyzeMode();
10197         
10198         DoSleep( appData.delayBeforeQuit );
10199         SendToProgram("quit\n", &first);
10200         DoSleep( appData.delayAfterQuit );
10201         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10202     }
10203     if (second.pr != NoProc) {
10204         DoSleep( appData.delayBeforeQuit );
10205         SendToProgram("quit\n", &second);
10206         DoSleep( appData.delayAfterQuit );
10207         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10208     }
10209     if (first.isr != NULL) {
10210         RemoveInputSource(first.isr);
10211     }
10212     if (second.isr != NULL) {
10213         RemoveInputSource(second.isr);
10214     }
10215
10216     ShutDownFrontEnd();
10217     exit(status);
10218 }
10219
10220 void
10221 PauseEvent()
10222 {
10223     if (appData.debugMode)
10224         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10225     if (pausing) {
10226         pausing = FALSE;
10227         ModeHighlight();
10228         if (gameMode == MachinePlaysWhite ||
10229             gameMode == MachinePlaysBlack) {
10230             StartClocks();
10231         } else {
10232             DisplayBothClocks();
10233         }
10234         if (gameMode == PlayFromGameFile) {
10235             if (appData.timeDelay >= 0) 
10236                 AutoPlayGameLoop();
10237         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10238             Reset(FALSE, TRUE);
10239             SendToICS(ics_prefix);
10240             SendToICS("refresh\n");
10241         } else if (currentMove < forwardMostMove) {
10242             ForwardInner(forwardMostMove);
10243         }
10244         pauseExamInvalid = FALSE;
10245     } else {
10246         switch (gameMode) {
10247           default:
10248             return;
10249           case IcsExamining:
10250             pauseExamForwardMostMove = forwardMostMove;
10251             pauseExamInvalid = FALSE;
10252             /* fall through */
10253           case IcsObserving:
10254           case IcsPlayingWhite:
10255           case IcsPlayingBlack:
10256             pausing = TRUE;
10257             ModeHighlight();
10258             return;
10259           case PlayFromGameFile:
10260             (void) StopLoadGameTimer();
10261             pausing = TRUE;
10262             ModeHighlight();
10263             break;
10264           case BeginningOfGame:
10265             if (appData.icsActive) return;
10266             /* else fall through */
10267           case MachinePlaysWhite:
10268           case MachinePlaysBlack:
10269           case TwoMachinesPlay:
10270             if (forwardMostMove == 0)
10271               return;           /* don't pause if no one has moved */
10272             if ((gameMode == MachinePlaysWhite &&
10273                  !WhiteOnMove(forwardMostMove)) ||
10274                 (gameMode == MachinePlaysBlack &&
10275                  WhiteOnMove(forwardMostMove))) {
10276                 StopClocks();
10277             }
10278             pausing = TRUE;
10279             ModeHighlight();
10280             break;
10281         }
10282     }
10283 }
10284
10285 void
10286 EditCommentEvent()
10287 {
10288     char title[MSG_SIZ];
10289
10290     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10291         strcpy(title, _("Edit comment"));
10292     } else {
10293         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10294                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10295                 parseList[currentMove - 1]);
10296     }
10297
10298     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10299 }
10300
10301
10302 void
10303 EditTagsEvent()
10304 {
10305     char *tags = PGNTags(&gameInfo);
10306     EditTagsPopUp(tags);
10307     free(tags);
10308 }
10309
10310 void
10311 AnalyzeModeEvent()
10312 {
10313     if (appData.noChessProgram || gameMode == AnalyzeMode)
10314       return;
10315
10316     if (gameMode != AnalyzeFile) {
10317         if (!appData.icsEngineAnalyze) {
10318                EditGameEvent();
10319                if (gameMode != EditGame) return;
10320         }
10321         ResurrectChessProgram();
10322         SendToProgram("analyze\n", &first);
10323         first.analyzing = TRUE;
10324         /*first.maybeThinking = TRUE;*/
10325         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10326         EngineOutputPopUp();
10327     }
10328     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10329     pausing = FALSE;
10330     ModeHighlight();
10331     SetGameInfo();
10332
10333     StartAnalysisClock();
10334     GetTimeMark(&lastNodeCountTime);
10335     lastNodeCount = 0;
10336 }
10337
10338 void
10339 AnalyzeFileEvent()
10340 {
10341     if (appData.noChessProgram || gameMode == AnalyzeFile)
10342       return;
10343
10344     if (gameMode != AnalyzeMode) {
10345         EditGameEvent();
10346         if (gameMode != EditGame) return;
10347         ResurrectChessProgram();
10348         SendToProgram("analyze\n", &first);
10349         first.analyzing = TRUE;
10350         /*first.maybeThinking = TRUE;*/
10351         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10352         EngineOutputPopUp();
10353     }
10354     gameMode = AnalyzeFile;
10355     pausing = FALSE;
10356     ModeHighlight();
10357     SetGameInfo();
10358
10359     StartAnalysisClock();
10360     GetTimeMark(&lastNodeCountTime);
10361     lastNodeCount = 0;
10362 }
10363
10364 void
10365 MachineWhiteEvent()
10366 {
10367     char buf[MSG_SIZ];
10368     char *bookHit = NULL;
10369
10370     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10371       return;
10372
10373
10374     if (gameMode == PlayFromGameFile || 
10375         gameMode == TwoMachinesPlay  || 
10376         gameMode == Training         || 
10377         gameMode == AnalyzeMode      || 
10378         gameMode == EndOfGame)
10379         EditGameEvent();
10380
10381     if (gameMode == EditPosition) 
10382         EditPositionDone();
10383
10384     if (!WhiteOnMove(currentMove)) {
10385         DisplayError(_("It is not White's turn"), 0);
10386         return;
10387     }
10388   
10389     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10390       ExitAnalyzeMode();
10391
10392     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10393         gameMode == AnalyzeFile)
10394         TruncateGame();
10395
10396     ResurrectChessProgram();    /* in case it isn't running */
10397     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10398         gameMode = MachinePlaysWhite;
10399         ResetClocks();
10400     } else
10401     gameMode = MachinePlaysWhite;
10402     pausing = FALSE;
10403     ModeHighlight();
10404     SetGameInfo();
10405     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10406     DisplayTitle(buf);
10407     if (first.sendName) {
10408       sprintf(buf, "name %s\n", gameInfo.black);
10409       SendToProgram(buf, &first);
10410     }
10411     if (first.sendTime) {
10412       if (first.useColors) {
10413         SendToProgram("black\n", &first); /*gnu kludge*/
10414       }
10415       SendTimeRemaining(&first, TRUE);
10416     }
10417     if (first.useColors) {
10418       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10419     }
10420     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10421     SetMachineThinkingEnables();
10422     first.maybeThinking = TRUE;
10423     StartClocks();
10424     firstMove = FALSE;
10425
10426     if (appData.autoFlipView && !flipView) {
10427       flipView = !flipView;
10428       DrawPosition(FALSE, NULL);
10429       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10430     }
10431
10432     if(bookHit) { // [HGM] book: simulate book reply
10433         static char bookMove[MSG_SIZ]; // a bit generous?
10434
10435         programStats.nodes = programStats.depth = programStats.time = 
10436         programStats.score = programStats.got_only_move = 0;
10437         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10438
10439         strcpy(bookMove, "move ");
10440         strcat(bookMove, bookHit);
10441         HandleMachineMove(bookMove, &first);
10442     }
10443 }
10444
10445 void
10446 MachineBlackEvent()
10447 {
10448     char buf[MSG_SIZ];
10449    char *bookHit = NULL;
10450
10451     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10452         return;
10453
10454
10455     if (gameMode == PlayFromGameFile || 
10456         gameMode == TwoMachinesPlay  || 
10457         gameMode == Training         || 
10458         gameMode == AnalyzeMode      || 
10459         gameMode == EndOfGame)
10460         EditGameEvent();
10461
10462     if (gameMode == EditPosition) 
10463         EditPositionDone();
10464
10465     if (WhiteOnMove(currentMove)) {
10466         DisplayError(_("It is not Black's turn"), 0);
10467         return;
10468     }
10469     
10470     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10471       ExitAnalyzeMode();
10472
10473     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10474         gameMode == AnalyzeFile)
10475         TruncateGame();
10476
10477     ResurrectChessProgram();    /* in case it isn't running */
10478     gameMode = MachinePlaysBlack;
10479     pausing = FALSE;
10480     ModeHighlight();
10481     SetGameInfo();
10482     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10483     DisplayTitle(buf);
10484     if (first.sendName) {
10485       sprintf(buf, "name %s\n", gameInfo.white);
10486       SendToProgram(buf, &first);
10487     }
10488     if (first.sendTime) {
10489       if (first.useColors) {
10490         SendToProgram("white\n", &first); /*gnu kludge*/
10491       }
10492       SendTimeRemaining(&first, FALSE);
10493     }
10494     if (first.useColors) {
10495       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10496     }
10497     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10498     SetMachineThinkingEnables();
10499     first.maybeThinking = TRUE;
10500     StartClocks();
10501
10502     if (appData.autoFlipView && flipView) {
10503       flipView = !flipView;
10504       DrawPosition(FALSE, NULL);
10505       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10506     }
10507     if(bookHit) { // [HGM] book: simulate book reply
10508         static char bookMove[MSG_SIZ]; // a bit generous?
10509
10510         programStats.nodes = programStats.depth = programStats.time = 
10511         programStats.score = programStats.got_only_move = 0;
10512         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10513
10514         strcpy(bookMove, "move ");
10515         strcat(bookMove, bookHit);
10516         HandleMachineMove(bookMove, &first);
10517     }
10518 }
10519
10520
10521 void
10522 DisplayTwoMachinesTitle()
10523 {
10524     char buf[MSG_SIZ];
10525     if (appData.matchGames > 0) {
10526         if (first.twoMachinesColor[0] == 'w') {
10527             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10528                     gameInfo.white, gameInfo.black,
10529                     first.matchWins, second.matchWins,
10530                     matchGame - 1 - (first.matchWins + second.matchWins));
10531         } else {
10532             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10533                     gameInfo.white, gameInfo.black,
10534                     second.matchWins, first.matchWins,
10535                     matchGame - 1 - (first.matchWins + second.matchWins));
10536         }
10537     } else {
10538         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10539     }
10540     DisplayTitle(buf);
10541 }
10542
10543 void
10544 TwoMachinesEvent P((void))
10545 {
10546     int i;
10547     char buf[MSG_SIZ];
10548     ChessProgramState *onmove;
10549     char *bookHit = NULL;
10550     
10551     if (appData.noChessProgram) return;
10552
10553     switch (gameMode) {
10554       case TwoMachinesPlay:
10555         return;
10556       case MachinePlaysWhite:
10557       case MachinePlaysBlack:
10558         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10559             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10560             return;
10561         }
10562         /* fall through */
10563       case BeginningOfGame:
10564       case PlayFromGameFile:
10565       case EndOfGame:
10566         EditGameEvent();
10567         if (gameMode != EditGame) return;
10568         break;
10569       case EditPosition:
10570         EditPositionDone();
10571         break;
10572       case AnalyzeMode:
10573       case AnalyzeFile:
10574         ExitAnalyzeMode();
10575         break;
10576       case EditGame:
10577       default:
10578         break;
10579     }
10580
10581     forwardMostMove = currentMove;
10582     ResurrectChessProgram();    /* in case first program isn't running */
10583
10584     if (second.pr == NULL) {
10585         StartChessProgram(&second);
10586         if (second.protocolVersion == 1) {
10587           TwoMachinesEventIfReady();
10588         } else {
10589           /* kludge: allow timeout for initial "feature" command */
10590           FreezeUI();
10591           DisplayMessage("", _("Starting second chess program"));
10592           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10593         }
10594         return;
10595     }
10596     DisplayMessage("", "");
10597     InitChessProgram(&second, FALSE);
10598     SendToProgram("force\n", &second);
10599     if (startedFromSetupPosition) {
10600         SendBoard(&second, backwardMostMove);
10601     if (appData.debugMode) {
10602         fprintf(debugFP, "Two Machines\n");
10603     }
10604     }
10605     for (i = backwardMostMove; i < forwardMostMove; i++) {
10606         SendMoveToProgram(i, &second);
10607     }
10608
10609     gameMode = TwoMachinesPlay;
10610     pausing = FALSE;
10611     ModeHighlight();
10612     SetGameInfo();
10613     DisplayTwoMachinesTitle();
10614     firstMove = TRUE;
10615     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10616         onmove = &first;
10617     } else {
10618         onmove = &second;
10619     }
10620
10621     SendToProgram(first.computerString, &first);
10622     if (first.sendName) {
10623       sprintf(buf, "name %s\n", second.tidy);
10624       SendToProgram(buf, &first);
10625     }
10626     SendToProgram(second.computerString, &second);
10627     if (second.sendName) {
10628       sprintf(buf, "name %s\n", first.tidy);
10629       SendToProgram(buf, &second);
10630     }
10631
10632     ResetClocks();
10633     if (!first.sendTime || !second.sendTime) {
10634         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10635         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10636     }
10637     if (onmove->sendTime) {
10638       if (onmove->useColors) {
10639         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10640       }
10641       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10642     }
10643     if (onmove->useColors) {
10644       SendToProgram(onmove->twoMachinesColor, onmove);
10645     }
10646     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10647 //    SendToProgram("go\n", onmove);
10648     onmove->maybeThinking = TRUE;
10649     SetMachineThinkingEnables();
10650
10651     StartClocks();
10652
10653     if(bookHit) { // [HGM] book: simulate book reply
10654         static char bookMove[MSG_SIZ]; // a bit generous?
10655
10656         programStats.nodes = programStats.depth = programStats.time = 
10657         programStats.score = programStats.got_only_move = 0;
10658         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10659
10660         strcpy(bookMove, "move ");
10661         strcat(bookMove, bookHit);
10662         HandleMachineMove(bookMove, &first);
10663     }
10664 }
10665
10666 void
10667 TrainingEvent()
10668 {
10669     if (gameMode == Training) {
10670       SetTrainingModeOff();
10671       gameMode = PlayFromGameFile;
10672       DisplayMessage("", _("Training mode off"));
10673     } else {
10674       gameMode = Training;
10675       animateTraining = appData.animate;
10676
10677       /* make sure we are not already at the end of the game */
10678       if (currentMove < forwardMostMove) {
10679         SetTrainingModeOn();
10680         DisplayMessage("", _("Training mode on"));
10681       } else {
10682         gameMode = PlayFromGameFile;
10683         DisplayError(_("Already at end of game"), 0);
10684       }
10685     }
10686     ModeHighlight();
10687 }
10688
10689 void
10690 IcsClientEvent()
10691 {
10692     if (!appData.icsActive) return;
10693     switch (gameMode) {
10694       case IcsPlayingWhite:
10695       case IcsPlayingBlack:
10696       case IcsObserving:
10697       case IcsIdle:
10698       case BeginningOfGame:
10699       case IcsExamining:
10700         return;
10701
10702       case EditGame:
10703         break;
10704
10705       case EditPosition:
10706         EditPositionDone();
10707         break;
10708
10709       case AnalyzeMode:
10710       case AnalyzeFile:
10711         ExitAnalyzeMode();
10712         break;
10713         
10714       default:
10715         EditGameEvent();
10716         break;
10717     }
10718
10719     gameMode = IcsIdle;
10720     ModeHighlight();
10721     return;
10722 }
10723
10724
10725 void
10726 EditGameEvent()
10727 {
10728     int i;
10729
10730     switch (gameMode) {
10731       case Training:
10732         SetTrainingModeOff();
10733         break;
10734       case MachinePlaysWhite:
10735       case MachinePlaysBlack:
10736       case BeginningOfGame:
10737         SendToProgram("force\n", &first);
10738         SetUserThinkingEnables();
10739         break;
10740       case PlayFromGameFile:
10741         (void) StopLoadGameTimer();
10742         if (gameFileFP != NULL) {
10743             gameFileFP = NULL;
10744         }
10745         break;
10746       case EditPosition:
10747         EditPositionDone();
10748         break;
10749       case AnalyzeMode:
10750       case AnalyzeFile:
10751         ExitAnalyzeMode();
10752         SendToProgram("force\n", &first);
10753         break;
10754       case TwoMachinesPlay:
10755         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10756         ResurrectChessProgram();
10757         SetUserThinkingEnables();
10758         break;
10759       case EndOfGame:
10760         ResurrectChessProgram();
10761         break;
10762       case IcsPlayingBlack:
10763       case IcsPlayingWhite:
10764         DisplayError(_("Warning: You are still playing a game"), 0);
10765         break;
10766       case IcsObserving:
10767         DisplayError(_("Warning: You are still observing a game"), 0);
10768         break;
10769       case IcsExamining:
10770         DisplayError(_("Warning: You are still examining a game"), 0);
10771         break;
10772       case IcsIdle:
10773         break;
10774       case EditGame:
10775       default:
10776         return;
10777     }
10778     
10779     pausing = FALSE;
10780     StopClocks();
10781     first.offeredDraw = second.offeredDraw = 0;
10782
10783     if (gameMode == PlayFromGameFile) {
10784         whiteTimeRemaining = timeRemaining[0][currentMove];
10785         blackTimeRemaining = timeRemaining[1][currentMove];
10786         DisplayTitle("");
10787     }
10788
10789     if (gameMode == MachinePlaysWhite ||
10790         gameMode == MachinePlaysBlack ||
10791         gameMode == TwoMachinesPlay ||
10792         gameMode == EndOfGame) {
10793         i = forwardMostMove;
10794         while (i > currentMove) {
10795             SendToProgram("undo\n", &first);
10796             i--;
10797         }
10798         whiteTimeRemaining = timeRemaining[0][currentMove];
10799         blackTimeRemaining = timeRemaining[1][currentMove];
10800         DisplayBothClocks();
10801         if (whiteFlag || blackFlag) {
10802             whiteFlag = blackFlag = 0;
10803         }
10804         DisplayTitle("");
10805     }           
10806     
10807     gameMode = EditGame;
10808     ModeHighlight();
10809     SetGameInfo();
10810 }
10811
10812
10813 void
10814 EditPositionEvent()
10815 {
10816     if (gameMode == EditPosition) {
10817         EditGameEvent();
10818         return;
10819     }
10820     
10821     EditGameEvent();
10822     if (gameMode != EditGame) return;
10823     
10824     gameMode = EditPosition;
10825     ModeHighlight();
10826     SetGameInfo();
10827     if (currentMove > 0)
10828       CopyBoard(boards[0], boards[currentMove]);
10829     
10830     blackPlaysFirst = !WhiteOnMove(currentMove);
10831     ResetClocks();
10832     currentMove = forwardMostMove = backwardMostMove = 0;
10833     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10834     DisplayMove(-1);
10835 }
10836
10837 void
10838 ExitAnalyzeMode()
10839 {
10840     /* [DM] icsEngineAnalyze - possible call from other functions */
10841     if (appData.icsEngineAnalyze) {
10842         appData.icsEngineAnalyze = FALSE;
10843
10844         DisplayMessage("",_("Close ICS engine analyze..."));
10845     }
10846     if (first.analysisSupport && first.analyzing) {
10847       SendToProgram("exit\n", &first);
10848       first.analyzing = FALSE;
10849     }
10850     thinkOutput[0] = NULLCHAR;
10851 }
10852
10853 void
10854 EditPositionDone()
10855 {
10856     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10857
10858     startedFromSetupPosition = TRUE;
10859     InitChessProgram(&first, FALSE);
10860     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10861     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10862         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10863         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10864     } else castlingRights[0][2] = -1;
10865     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10866         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10867         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10868     } else castlingRights[0][5] = -1;
10869     SendToProgram("force\n", &first);
10870     if (blackPlaysFirst) {
10871         strcpy(moveList[0], "");
10872         strcpy(parseList[0], "");
10873         currentMove = forwardMostMove = backwardMostMove = 1;
10874         CopyBoard(boards[1], boards[0]);
10875         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10876         { int i;
10877           epStatus[1] = epStatus[0];
10878           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10879         }
10880     } else {
10881         currentMove = forwardMostMove = backwardMostMove = 0;
10882     }
10883     SendBoard(&first, forwardMostMove);
10884     if (appData.debugMode) {
10885         fprintf(debugFP, "EditPosDone\n");
10886     }
10887     DisplayTitle("");
10888     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10889     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10890     gameMode = EditGame;
10891     ModeHighlight();
10892     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10893     ClearHighlights(); /* [AS] */
10894 }
10895
10896 /* Pause for `ms' milliseconds */
10897 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10898 void
10899 TimeDelay(ms)
10900      long ms;
10901 {
10902     TimeMark m1, m2;
10903
10904     GetTimeMark(&m1);
10905     do {
10906         GetTimeMark(&m2);
10907     } while (SubtractTimeMarks(&m2, &m1) < ms);
10908 }
10909
10910 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10911 void
10912 SendMultiLineToICS(buf)
10913      char *buf;
10914 {
10915     char temp[MSG_SIZ+1], *p;
10916     int len;
10917
10918     len = strlen(buf);
10919     if (len > MSG_SIZ)
10920       len = MSG_SIZ;
10921   
10922     strncpy(temp, buf, len);
10923     temp[len] = 0;
10924
10925     p = temp;
10926     while (*p) {
10927         if (*p == '\n' || *p == '\r')
10928           *p = ' ';
10929         ++p;
10930     }
10931
10932     strcat(temp, "\n");
10933     SendToICS(temp);
10934     SendToPlayer(temp, strlen(temp));
10935 }
10936
10937 void
10938 SetWhiteToPlayEvent()
10939 {
10940     if (gameMode == EditPosition) {
10941         blackPlaysFirst = FALSE;
10942         DisplayBothClocks();    /* works because currentMove is 0 */
10943     } else if (gameMode == IcsExamining) {
10944         SendToICS(ics_prefix);
10945         SendToICS("tomove white\n");
10946     }
10947 }
10948
10949 void
10950 SetBlackToPlayEvent()
10951 {
10952     if (gameMode == EditPosition) {
10953         blackPlaysFirst = TRUE;
10954         currentMove = 1;        /* kludge */
10955         DisplayBothClocks();
10956         currentMove = 0;
10957     } else if (gameMode == IcsExamining) {
10958         SendToICS(ics_prefix);
10959         SendToICS("tomove black\n");
10960     }
10961 }
10962
10963 void
10964 EditPositionMenuEvent(selection, x, y)
10965      ChessSquare selection;
10966      int x, y;
10967 {
10968     char buf[MSG_SIZ];
10969     ChessSquare piece = boards[0][y][x];
10970
10971     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10972
10973     switch (selection) {
10974       case ClearBoard:
10975         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10976             SendToICS(ics_prefix);
10977             SendToICS("bsetup clear\n");
10978         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10979             SendToICS(ics_prefix);
10980             SendToICS("clearboard\n");
10981         } else {
10982             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10983                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10984                 for (y = 0; y < BOARD_HEIGHT; y++) {
10985                     if (gameMode == IcsExamining) {
10986                         if (boards[currentMove][y][x] != EmptySquare) {
10987                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
10988                                     AAA + x, ONE + y);
10989                             SendToICS(buf);
10990                         }
10991                     } else {
10992                         boards[0][y][x] = p;
10993                     }
10994                 }
10995             }
10996         }
10997         if (gameMode == EditPosition) {
10998             DrawPosition(FALSE, boards[0]);
10999         }
11000         break;
11001
11002       case WhitePlay:
11003         SetWhiteToPlayEvent();
11004         break;
11005
11006       case BlackPlay:
11007         SetBlackToPlayEvent();
11008         break;
11009
11010       case EmptySquare:
11011         if (gameMode == IcsExamining) {
11012             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11013             SendToICS(buf);
11014         } else {
11015             boards[0][y][x] = EmptySquare;
11016             DrawPosition(FALSE, boards[0]);
11017         }
11018         break;
11019
11020       case PromotePiece:
11021         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11022            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11023             selection = (ChessSquare) (PROMOTED piece);
11024         } else if(piece == EmptySquare) selection = WhiteSilver;
11025         else selection = (ChessSquare)((int)piece - 1);
11026         goto defaultlabel;
11027
11028       case DemotePiece:
11029         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11030            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11031             selection = (ChessSquare) (DEMOTED piece);
11032         } else if(piece == EmptySquare) selection = BlackSilver;
11033         else selection = (ChessSquare)((int)piece + 1);       
11034         goto defaultlabel;
11035
11036       case WhiteQueen:
11037       case BlackQueen:
11038         if(gameInfo.variant == VariantShatranj ||
11039            gameInfo.variant == VariantXiangqi  ||
11040            gameInfo.variant == VariantCourier    )
11041             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11042         goto defaultlabel;
11043
11044       case WhiteKing:
11045       case BlackKing:
11046         if(gameInfo.variant == VariantXiangqi)
11047             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11048         if(gameInfo.variant == VariantKnightmate)
11049             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11050       default:
11051         defaultlabel:
11052         if (gameMode == IcsExamining) {
11053             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11054                     PieceToChar(selection), AAA + x, ONE + y);
11055             SendToICS(buf);
11056         } else {
11057             boards[0][y][x] = selection;
11058             DrawPosition(FALSE, boards[0]);
11059         }
11060         break;
11061     }
11062 }
11063
11064
11065 void
11066 DropMenuEvent(selection, x, y)
11067      ChessSquare selection;
11068      int x, y;
11069 {
11070     ChessMove moveType;
11071
11072     switch (gameMode) {
11073       case IcsPlayingWhite:
11074       case MachinePlaysBlack:
11075         if (!WhiteOnMove(currentMove)) {
11076             DisplayMoveError(_("It is Black's turn"));
11077             return;
11078         }
11079         moveType = WhiteDrop;
11080         break;
11081       case IcsPlayingBlack:
11082       case MachinePlaysWhite:
11083         if (WhiteOnMove(currentMove)) {
11084             DisplayMoveError(_("It is White's turn"));
11085             return;
11086         }
11087         moveType = BlackDrop;
11088         break;
11089       case EditGame:
11090         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11091         break;
11092       default:
11093         return;
11094     }
11095
11096     if (moveType == BlackDrop && selection < BlackPawn) {
11097       selection = (ChessSquare) ((int) selection
11098                                  + (int) BlackPawn - (int) WhitePawn);
11099     }
11100     if (boards[currentMove][y][x] != EmptySquare) {
11101         DisplayMoveError(_("That square is occupied"));
11102         return;
11103     }
11104
11105     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11106 }
11107
11108 void
11109 AcceptEvent()
11110 {
11111     /* Accept a pending offer of any kind from opponent */
11112     
11113     if (appData.icsActive) {
11114         SendToICS(ics_prefix);
11115         SendToICS("accept\n");
11116     } else if (cmailMsgLoaded) {
11117         if (currentMove == cmailOldMove &&
11118             commentList[cmailOldMove] != NULL &&
11119             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11120                    "Black offers a draw" : "White offers a draw")) {
11121             TruncateGame();
11122             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11123             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11124         } else {
11125             DisplayError(_("There is no pending offer on this move"), 0);
11126             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11127         }
11128     } else {
11129         /* Not used for offers from chess program */
11130     }
11131 }
11132
11133 void
11134 DeclineEvent()
11135 {
11136     /* Decline a pending offer of any kind from opponent */
11137     
11138     if (appData.icsActive) {
11139         SendToICS(ics_prefix);
11140         SendToICS("decline\n");
11141     } else if (cmailMsgLoaded) {
11142         if (currentMove == cmailOldMove &&
11143             commentList[cmailOldMove] != NULL &&
11144             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11145                    "Black offers a draw" : "White offers a draw")) {
11146 #ifdef NOTDEF
11147             AppendComment(cmailOldMove, "Draw declined");
11148             DisplayComment(cmailOldMove - 1, "Draw declined");
11149 #endif /*NOTDEF*/
11150         } else {
11151             DisplayError(_("There is no pending offer on this move"), 0);
11152         }
11153     } else {
11154         /* Not used for offers from chess program */
11155     }
11156 }
11157
11158 void
11159 RematchEvent()
11160 {
11161     /* Issue ICS rematch command */
11162     if (appData.icsActive) {
11163         SendToICS(ics_prefix);
11164         SendToICS("rematch\n");
11165     }
11166 }
11167
11168 void
11169 CallFlagEvent()
11170 {
11171     /* Call your opponent's flag (claim a win on time) */
11172     if (appData.icsActive) {
11173         SendToICS(ics_prefix);
11174         SendToICS("flag\n");
11175     } else {
11176         switch (gameMode) {
11177           default:
11178             return;
11179           case MachinePlaysWhite:
11180             if (whiteFlag) {
11181                 if (blackFlag)
11182                   GameEnds(GameIsDrawn, "Both players ran out of time",
11183                            GE_PLAYER);
11184                 else
11185                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11186             } else {
11187                 DisplayError(_("Your opponent is not out of time"), 0);
11188             }
11189             break;
11190           case MachinePlaysBlack:
11191             if (blackFlag) {
11192                 if (whiteFlag)
11193                   GameEnds(GameIsDrawn, "Both players ran out of time",
11194                            GE_PLAYER);
11195                 else
11196                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11197             } else {
11198                 DisplayError(_("Your opponent is not out of time"), 0);
11199             }
11200             break;
11201         }
11202     }
11203 }
11204
11205 void
11206 DrawEvent()
11207 {
11208     /* Offer draw or accept pending draw offer from opponent */
11209     
11210     if (appData.icsActive) {
11211         /* Note: tournament rules require draw offers to be
11212            made after you make your move but before you punch
11213            your clock.  Currently ICS doesn't let you do that;
11214            instead, you immediately punch your clock after making
11215            a move, but you can offer a draw at any time. */
11216         
11217         SendToICS(ics_prefix);
11218         SendToICS("draw\n");
11219     } else if (cmailMsgLoaded) {
11220         if (currentMove == cmailOldMove &&
11221             commentList[cmailOldMove] != NULL &&
11222             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11223                    "Black offers a draw" : "White offers a draw")) {
11224             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11225             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11226         } else if (currentMove == cmailOldMove + 1) {
11227             char *offer = WhiteOnMove(cmailOldMove) ?
11228               "White offers a draw" : "Black offers a draw";
11229             AppendComment(currentMove, offer);
11230             DisplayComment(currentMove - 1, offer);
11231             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11232         } else {
11233             DisplayError(_("You must make your move before offering a draw"), 0);
11234             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11235         }
11236     } else if (first.offeredDraw) {
11237         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11238     } else {
11239         if (first.sendDrawOffers) {
11240             SendToProgram("draw\n", &first);
11241             userOfferedDraw = TRUE;
11242         }
11243     }
11244 }
11245
11246 void
11247 AdjournEvent()
11248 {
11249     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11250     
11251     if (appData.icsActive) {
11252         SendToICS(ics_prefix);
11253         SendToICS("adjourn\n");
11254     } else {
11255         /* Currently GNU Chess doesn't offer or accept Adjourns */
11256     }
11257 }
11258
11259
11260 void
11261 AbortEvent()
11262 {
11263     /* Offer Abort or accept pending Abort offer from opponent */
11264     
11265     if (appData.icsActive) {
11266         SendToICS(ics_prefix);
11267         SendToICS("abort\n");
11268     } else {
11269         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11270     }
11271 }
11272
11273 void
11274 ResignEvent()
11275 {
11276     /* Resign.  You can do this even if it's not your turn. */
11277     
11278     if (appData.icsActive) {
11279         SendToICS(ics_prefix);
11280         SendToICS("resign\n");
11281     } else {
11282         switch (gameMode) {
11283           case MachinePlaysWhite:
11284             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11285             break;
11286           case MachinePlaysBlack:
11287             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11288             break;
11289           case EditGame:
11290             if (cmailMsgLoaded) {
11291                 TruncateGame();
11292                 if (WhiteOnMove(cmailOldMove)) {
11293                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11294                 } else {
11295                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11296                 }
11297                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11298             }
11299             break;
11300           default:
11301             break;
11302         }
11303     }
11304 }
11305
11306
11307 void
11308 StopObservingEvent()
11309 {
11310     /* Stop observing current games */
11311     SendToICS(ics_prefix);
11312     SendToICS("unobserve\n");
11313 }
11314
11315 void
11316 StopExaminingEvent()
11317 {
11318     /* Stop observing current game */
11319     SendToICS(ics_prefix);
11320     SendToICS("unexamine\n");
11321 }
11322
11323 void
11324 ForwardInner(target)
11325      int target;
11326 {
11327     int limit;
11328
11329     if (appData.debugMode)
11330         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11331                 target, currentMove, forwardMostMove);
11332
11333     if (gameMode == EditPosition)
11334       return;
11335
11336     if (gameMode == PlayFromGameFile && !pausing)
11337       PauseEvent();
11338     
11339     if (gameMode == IcsExamining && pausing)
11340       limit = pauseExamForwardMostMove;
11341     else
11342       limit = forwardMostMove;
11343     
11344     if (target > limit) target = limit;
11345
11346     if (target > 0 && moveList[target - 1][0]) {
11347         int fromX, fromY, toX, toY;
11348         toX = moveList[target - 1][2] - AAA;
11349         toY = moveList[target - 1][3] - ONE;
11350         if (moveList[target - 1][1] == '@') {
11351             if (appData.highlightLastMove) {
11352                 SetHighlights(-1, -1, toX, toY);
11353             }
11354         } else {
11355             fromX = moveList[target - 1][0] - AAA;
11356             fromY = moveList[target - 1][1] - ONE;
11357             if (target == currentMove + 1) {
11358                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11359             }
11360             if (appData.highlightLastMove) {
11361                 SetHighlights(fromX, fromY, toX, toY);
11362             }
11363         }
11364     }
11365     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11366         gameMode == Training || gameMode == PlayFromGameFile || 
11367         gameMode == AnalyzeFile) {
11368         while (currentMove < target) {
11369             SendMoveToProgram(currentMove++, &first);
11370         }
11371     } else {
11372         currentMove = target;
11373     }
11374     
11375     if (gameMode == EditGame || gameMode == EndOfGame) {
11376         whiteTimeRemaining = timeRemaining[0][currentMove];
11377         blackTimeRemaining = timeRemaining[1][currentMove];
11378     }
11379     DisplayBothClocks();
11380     DisplayMove(currentMove - 1);
11381     DrawPosition(FALSE, boards[currentMove]);
11382     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11383     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11384         DisplayComment(currentMove - 1, commentList[currentMove]);
11385     }
11386 }
11387
11388
11389 void
11390 ForwardEvent()
11391 {
11392     if (gameMode == IcsExamining && !pausing) {
11393         SendToICS(ics_prefix);
11394         SendToICS("forward\n");
11395     } else {
11396         ForwardInner(currentMove + 1);
11397     }
11398 }
11399
11400 void
11401 ToEndEvent()
11402 {
11403     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11404         /* to optimze, we temporarily turn off analysis mode while we feed
11405          * the remaining moves to the engine. Otherwise we get analysis output
11406          * after each move.
11407          */ 
11408         if (first.analysisSupport) {
11409           SendToProgram("exit\nforce\n", &first);
11410           first.analyzing = FALSE;
11411         }
11412     }
11413         
11414     if (gameMode == IcsExamining && !pausing) {
11415         SendToICS(ics_prefix);
11416         SendToICS("forward 999999\n");
11417     } else {
11418         ForwardInner(forwardMostMove);
11419     }
11420
11421     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11422         /* we have fed all the moves, so reactivate analysis mode */
11423         SendToProgram("analyze\n", &first);
11424         first.analyzing = TRUE;
11425         /*first.maybeThinking = TRUE;*/
11426         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11427     }
11428 }
11429
11430 void
11431 BackwardInner(target)
11432      int target;
11433 {
11434     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11435
11436     if (appData.debugMode)
11437         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11438                 target, currentMove, forwardMostMove);
11439
11440     if (gameMode == EditPosition) return;
11441     if (currentMove <= backwardMostMove) {
11442         ClearHighlights();
11443         DrawPosition(full_redraw, boards[currentMove]);
11444         return;
11445     }
11446     if (gameMode == PlayFromGameFile && !pausing)
11447       PauseEvent();
11448     
11449     if (moveList[target][0]) {
11450         int fromX, fromY, toX, toY;
11451         toX = moveList[target][2] - AAA;
11452         toY = moveList[target][3] - ONE;
11453         if (moveList[target][1] == '@') {
11454             if (appData.highlightLastMove) {
11455                 SetHighlights(-1, -1, toX, toY);
11456             }
11457         } else {
11458             fromX = moveList[target][0] - AAA;
11459             fromY = moveList[target][1] - ONE;
11460             if (target == currentMove - 1) {
11461                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11462             }
11463             if (appData.highlightLastMove) {
11464                 SetHighlights(fromX, fromY, toX, toY);
11465             }
11466         }
11467     }
11468     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11469         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11470         while (currentMove > target) {
11471             SendToProgram("undo\n", &first);
11472             currentMove--;
11473         }
11474     } else {
11475         currentMove = target;
11476     }
11477     
11478     if (gameMode == EditGame || gameMode == EndOfGame) {
11479         whiteTimeRemaining = timeRemaining[0][currentMove];
11480         blackTimeRemaining = timeRemaining[1][currentMove];
11481     }
11482     DisplayBothClocks();
11483     DisplayMove(currentMove - 1);
11484     DrawPosition(full_redraw, boards[currentMove]);
11485     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11486     // [HGM] PV info: routine tests if comment empty
11487     DisplayComment(currentMove - 1, commentList[currentMove]);
11488 }
11489
11490 void
11491 BackwardEvent()
11492 {
11493     if (gameMode == IcsExamining && !pausing) {
11494         SendToICS(ics_prefix);
11495         SendToICS("backward\n");
11496     } else {
11497         BackwardInner(currentMove - 1);
11498     }
11499 }
11500
11501 void
11502 ToStartEvent()
11503 {
11504     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11505         /* to optimze, we temporarily turn off analysis mode while we undo
11506          * all the moves. Otherwise we get analysis output after each undo.
11507          */ 
11508         if (first.analysisSupport) {
11509           SendToProgram("exit\nforce\n", &first);
11510           first.analyzing = FALSE;
11511         }
11512     }
11513
11514     if (gameMode == IcsExamining && !pausing) {
11515         SendToICS(ics_prefix);
11516         SendToICS("backward 999999\n");
11517     } else {
11518         BackwardInner(backwardMostMove);
11519     }
11520
11521     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11522         /* we have fed all the moves, so reactivate analysis mode */
11523         SendToProgram("analyze\n", &first);
11524         first.analyzing = TRUE;
11525         /*first.maybeThinking = TRUE;*/
11526         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11527     }
11528 }
11529
11530 void
11531 ToNrEvent(int to)
11532 {
11533   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11534   if (to >= forwardMostMove) to = forwardMostMove;
11535   if (to <= backwardMostMove) to = backwardMostMove;
11536   if (to < currentMove) {
11537     BackwardInner(to);
11538   } else {
11539     ForwardInner(to);
11540   }
11541 }
11542
11543 void
11544 RevertEvent()
11545 {
11546     if (gameMode != IcsExamining) {
11547         DisplayError(_("You are not examining a game"), 0);
11548         return;
11549     }
11550     if (pausing) {
11551         DisplayError(_("You can't revert while pausing"), 0);
11552         return;
11553     }
11554     SendToICS(ics_prefix);
11555     SendToICS("revert\n");
11556 }
11557
11558 void
11559 RetractMoveEvent()
11560 {
11561     switch (gameMode) {
11562       case MachinePlaysWhite:
11563       case MachinePlaysBlack:
11564         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11565             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11566             return;
11567         }
11568         if (forwardMostMove < 2) return;
11569         currentMove = forwardMostMove = forwardMostMove - 2;
11570         whiteTimeRemaining = timeRemaining[0][currentMove];
11571         blackTimeRemaining = timeRemaining[1][currentMove];
11572         DisplayBothClocks();
11573         DisplayMove(currentMove - 1);
11574         ClearHighlights();/*!! could figure this out*/
11575         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11576         SendToProgram("remove\n", &first);
11577         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11578         break;
11579
11580       case BeginningOfGame:
11581       default:
11582         break;
11583
11584       case IcsPlayingWhite:
11585       case IcsPlayingBlack:
11586         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11587             SendToICS(ics_prefix);
11588             SendToICS("takeback 2\n");
11589         } else {
11590             SendToICS(ics_prefix);
11591             SendToICS("takeback 1\n");
11592         }
11593         break;
11594     }
11595 }
11596
11597 void
11598 MoveNowEvent()
11599 {
11600     ChessProgramState *cps;
11601
11602     switch (gameMode) {
11603       case MachinePlaysWhite:
11604         if (!WhiteOnMove(forwardMostMove)) {
11605             DisplayError(_("It is your turn"), 0);
11606             return;
11607         }
11608         cps = &first;
11609         break;
11610       case MachinePlaysBlack:
11611         if (WhiteOnMove(forwardMostMove)) {
11612             DisplayError(_("It is your turn"), 0);
11613             return;
11614         }
11615         cps = &first;
11616         break;
11617       case TwoMachinesPlay:
11618         if (WhiteOnMove(forwardMostMove) ==
11619             (first.twoMachinesColor[0] == 'w')) {
11620             cps = &first;
11621         } else {
11622             cps = &second;
11623         }
11624         break;
11625       case BeginningOfGame:
11626       default:
11627         return;
11628     }
11629     SendToProgram("?\n", cps);
11630 }
11631
11632 void
11633 TruncateGameEvent()
11634 {
11635     EditGameEvent();
11636     if (gameMode != EditGame) return;
11637     TruncateGame();
11638 }
11639
11640 void
11641 TruncateGame()
11642 {
11643     if (forwardMostMove > currentMove) {
11644         if (gameInfo.resultDetails != NULL) {
11645             free(gameInfo.resultDetails);
11646             gameInfo.resultDetails = NULL;
11647             gameInfo.result = GameUnfinished;
11648         }
11649         forwardMostMove = currentMove;
11650         HistorySet(parseList, backwardMostMove, forwardMostMove,
11651                    currentMove-1);
11652     }
11653 }
11654
11655 void
11656 HintEvent()
11657 {
11658     if (appData.noChessProgram) return;
11659     switch (gameMode) {
11660       case MachinePlaysWhite:
11661         if (WhiteOnMove(forwardMostMove)) {
11662             DisplayError(_("Wait until your turn"), 0);
11663             return;
11664         }
11665         break;
11666       case BeginningOfGame:
11667       case MachinePlaysBlack:
11668         if (!WhiteOnMove(forwardMostMove)) {
11669             DisplayError(_("Wait until your turn"), 0);
11670             return;
11671         }
11672         break;
11673       default:
11674         DisplayError(_("No hint available"), 0);
11675         return;
11676     }
11677     SendToProgram("hint\n", &first);
11678     hintRequested = TRUE;
11679 }
11680
11681 void
11682 BookEvent()
11683 {
11684     if (appData.noChessProgram) return;
11685     switch (gameMode) {
11686       case MachinePlaysWhite:
11687         if (WhiteOnMove(forwardMostMove)) {
11688             DisplayError(_("Wait until your turn"), 0);
11689             return;
11690         }
11691         break;
11692       case BeginningOfGame:
11693       case MachinePlaysBlack:
11694         if (!WhiteOnMove(forwardMostMove)) {
11695             DisplayError(_("Wait until your turn"), 0);
11696             return;
11697         }
11698         break;
11699       case EditPosition:
11700         EditPositionDone();
11701         break;
11702       case TwoMachinesPlay:
11703         return;
11704       default:
11705         break;
11706     }
11707     SendToProgram("bk\n", &first);
11708     bookOutput[0] = NULLCHAR;
11709     bookRequested = TRUE;
11710 }
11711
11712 void
11713 AboutGameEvent()
11714 {
11715     char *tags = PGNTags(&gameInfo);
11716     TagsPopUp(tags, CmailMsg());
11717     free(tags);
11718 }
11719
11720 /* end button procedures */
11721
11722 void
11723 PrintPosition(fp, move)
11724      FILE *fp;
11725      int move;
11726 {
11727     int i, j;
11728     
11729     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11730         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11731             char c = PieceToChar(boards[move][i][j]);
11732             fputc(c == 'x' ? '.' : c, fp);
11733             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11734         }
11735     }
11736     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11737       fprintf(fp, "white to play\n");
11738     else
11739       fprintf(fp, "black to play\n");
11740 }
11741
11742 void
11743 PrintOpponents(fp)
11744      FILE *fp;
11745 {
11746     if (gameInfo.white != NULL) {
11747         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11748     } else {
11749         fprintf(fp, "\n");
11750     }
11751 }
11752
11753 /* Find last component of program's own name, using some heuristics */
11754 void
11755 TidyProgramName(prog, host, buf)
11756      char *prog, *host, buf[MSG_SIZ];
11757 {
11758     char *p, *q;
11759     int local = (strcmp(host, "localhost") == 0);
11760     while (!local && (p = strchr(prog, ';')) != NULL) {
11761         p++;
11762         while (*p == ' ') p++;
11763         prog = p;
11764     }
11765     if (*prog == '"' || *prog == '\'') {
11766         q = strchr(prog + 1, *prog);
11767     } else {
11768         q = strchr(prog, ' ');
11769     }
11770     if (q == NULL) q = prog + strlen(prog);
11771     p = q;
11772     while (p >= prog && *p != '/' && *p != '\\') p--;
11773     p++;
11774     if(p == prog && *p == '"') p++;
11775     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11776     memcpy(buf, p, q - p);
11777     buf[q - p] = NULLCHAR;
11778     if (!local) {
11779         strcat(buf, "@");
11780         strcat(buf, host);
11781     }
11782 }
11783
11784 char *
11785 TimeControlTagValue()
11786 {
11787     char buf[MSG_SIZ];
11788     if (!appData.clockMode) {
11789         strcpy(buf, "-");
11790     } else if (movesPerSession > 0) {
11791         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11792     } else if (timeIncrement == 0) {
11793         sprintf(buf, "%ld", timeControl/1000);
11794     } else {
11795         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11796     }
11797     return StrSave(buf);
11798 }
11799
11800 void
11801 SetGameInfo()
11802 {
11803     /* This routine is used only for certain modes */
11804     VariantClass v = gameInfo.variant;
11805     ClearGameInfo(&gameInfo);
11806     gameInfo.variant = v;
11807
11808     switch (gameMode) {
11809       case MachinePlaysWhite:
11810         gameInfo.event = StrSave( appData.pgnEventHeader );
11811         gameInfo.site = StrSave(HostName());
11812         gameInfo.date = PGNDate();
11813         gameInfo.round = StrSave("-");
11814         gameInfo.white = StrSave(first.tidy);
11815         gameInfo.black = StrSave(UserName());
11816         gameInfo.timeControl = TimeControlTagValue();
11817         break;
11818
11819       case MachinePlaysBlack:
11820         gameInfo.event = StrSave( appData.pgnEventHeader );
11821         gameInfo.site = StrSave(HostName());
11822         gameInfo.date = PGNDate();
11823         gameInfo.round = StrSave("-");
11824         gameInfo.white = StrSave(UserName());
11825         gameInfo.black = StrSave(first.tidy);
11826         gameInfo.timeControl = TimeControlTagValue();
11827         break;
11828
11829       case TwoMachinesPlay:
11830         gameInfo.event = StrSave( appData.pgnEventHeader );
11831         gameInfo.site = StrSave(HostName());
11832         gameInfo.date = PGNDate();
11833         if (matchGame > 0) {
11834             char buf[MSG_SIZ];
11835             sprintf(buf, "%d", matchGame);
11836             gameInfo.round = StrSave(buf);
11837         } else {
11838             gameInfo.round = StrSave("-");
11839         }
11840         if (first.twoMachinesColor[0] == 'w') {
11841             gameInfo.white = StrSave(first.tidy);
11842             gameInfo.black = StrSave(second.tidy);
11843         } else {
11844             gameInfo.white = StrSave(second.tidy);
11845             gameInfo.black = StrSave(first.tidy);
11846         }
11847         gameInfo.timeControl = TimeControlTagValue();
11848         break;
11849
11850       case EditGame:
11851         gameInfo.event = StrSave("Edited game");
11852         gameInfo.site = StrSave(HostName());
11853         gameInfo.date = PGNDate();
11854         gameInfo.round = StrSave("-");
11855         gameInfo.white = StrSave("-");
11856         gameInfo.black = StrSave("-");
11857         break;
11858
11859       case EditPosition:
11860         gameInfo.event = StrSave("Edited position");
11861         gameInfo.site = StrSave(HostName());
11862         gameInfo.date = PGNDate();
11863         gameInfo.round = StrSave("-");
11864         gameInfo.white = StrSave("-");
11865         gameInfo.black = StrSave("-");
11866         break;
11867
11868       case IcsPlayingWhite:
11869       case IcsPlayingBlack:
11870       case IcsObserving:
11871       case IcsExamining:
11872         break;
11873
11874       case PlayFromGameFile:
11875         gameInfo.event = StrSave("Game from non-PGN file");
11876         gameInfo.site = StrSave(HostName());
11877         gameInfo.date = PGNDate();
11878         gameInfo.round = StrSave("-");
11879         gameInfo.white = StrSave("?");
11880         gameInfo.black = StrSave("?");
11881         break;
11882
11883       default:
11884         break;
11885     }
11886 }
11887
11888 void
11889 ReplaceComment(index, text)
11890      int index;
11891      char *text;
11892 {
11893     int len;
11894
11895     while (*text == '\n') text++;
11896     len = strlen(text);
11897     while (len > 0 && text[len - 1] == '\n') len--;
11898
11899     if (commentList[index] != NULL)
11900       free(commentList[index]);
11901
11902     if (len == 0) {
11903         commentList[index] = NULL;
11904         return;
11905     }
11906     commentList[index] = (char *) malloc(len + 2);
11907     strncpy(commentList[index], text, len);
11908     commentList[index][len] = '\n';
11909     commentList[index][len + 1] = NULLCHAR;
11910 }
11911
11912 void
11913 CrushCRs(text)
11914      char *text;
11915 {
11916   char *p = text;
11917   char *q = text;
11918   char ch;
11919
11920   do {
11921     ch = *p++;
11922     if (ch == '\r') continue;
11923     *q++ = ch;
11924   } while (ch != '\0');
11925 }
11926
11927 void
11928 AppendComment(index, text)
11929      int index;
11930      char *text;
11931 {
11932     int oldlen, len;
11933     char *old;
11934
11935     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11936
11937     CrushCRs(text);
11938     while (*text == '\n') text++;
11939     len = strlen(text);
11940     while (len > 0 && text[len - 1] == '\n') len--;
11941
11942     if (len == 0) return;
11943
11944     if (commentList[index] != NULL) {
11945         old = commentList[index];
11946         oldlen = strlen(old);
11947         commentList[index] = (char *) malloc(oldlen + len + 2);
11948         strcpy(commentList[index], old);
11949         free(old);
11950         strncpy(&commentList[index][oldlen], text, len);
11951         commentList[index][oldlen + len] = '\n';
11952         commentList[index][oldlen + len + 1] = NULLCHAR;
11953     } else {
11954         commentList[index] = (char *) malloc(len + 2);
11955         strncpy(commentList[index], text, len);
11956         commentList[index][len] = '\n';
11957         commentList[index][len + 1] = NULLCHAR;
11958     }
11959 }
11960
11961 static char * FindStr( char * text, char * sub_text )
11962 {
11963     char * result = strstr( text, sub_text );
11964
11965     if( result != NULL ) {
11966         result += strlen( sub_text );
11967     }
11968
11969     return result;
11970 }
11971
11972 /* [AS] Try to extract PV info from PGN comment */
11973 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11974 char *GetInfoFromComment( int index, char * text )
11975 {
11976     char * sep = text;
11977
11978     if( text != NULL && index > 0 ) {
11979         int score = 0;
11980         int depth = 0;
11981         int time = -1, sec = 0, deci;
11982         char * s_eval = FindStr( text, "[%eval " );
11983         char * s_emt = FindStr( text, "[%emt " );
11984
11985         if( s_eval != NULL || s_emt != NULL ) {
11986             /* New style */
11987             char delim;
11988
11989             if( s_eval != NULL ) {
11990                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11991                     return text;
11992                 }
11993
11994                 if( delim != ']' ) {
11995                     return text;
11996                 }
11997             }
11998
11999             if( s_emt != NULL ) {
12000             }
12001         }
12002         else {
12003             /* We expect something like: [+|-]nnn.nn/dd */
12004             int score_lo = 0;
12005
12006             sep = strchr( text, '/' );
12007             if( sep == NULL || sep < (text+4) ) {
12008                 return text;
12009             }
12010
12011             time = -1; sec = -1; deci = -1;
12012             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12013                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12014                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12015                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12016                 return text;
12017             }
12018
12019             if( score_lo < 0 || score_lo >= 100 ) {
12020                 return text;
12021             }
12022
12023             if(sec >= 0) time = 600*time + 10*sec; else
12024             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12025
12026             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12027
12028             /* [HGM] PV time: now locate end of PV info */
12029             while( *++sep >= '0' && *sep <= '9'); // strip depth
12030             if(time >= 0)
12031             while( *++sep >= '0' && *sep <= '9'); // strip time
12032             if(sec >= 0)
12033             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12034             if(deci >= 0)
12035             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12036             while(*sep == ' ') sep++;
12037         }
12038
12039         if( depth <= 0 ) {
12040             return text;
12041         }
12042
12043         if( time < 0 ) {
12044             time = -1;
12045         }
12046
12047         pvInfoList[index-1].depth = depth;
12048         pvInfoList[index-1].score = score;
12049         pvInfoList[index-1].time  = 10*time; // centi-sec
12050     }
12051     return sep;
12052 }
12053
12054 void
12055 SendToProgram(message, cps)
12056      char *message;
12057      ChessProgramState *cps;
12058 {
12059     int count, outCount, error;
12060     char buf[MSG_SIZ];
12061
12062     if (cps->pr == NULL) return;
12063     Attention(cps);
12064     
12065     if (appData.debugMode) {
12066         TimeMark now;
12067         GetTimeMark(&now);
12068         fprintf(debugFP, "%ld >%-6s: %s", 
12069                 SubtractTimeMarks(&now, &programStartTime),
12070                 cps->which, message);
12071     }
12072     
12073     count = strlen(message);
12074     outCount = OutputToProcess(cps->pr, message, count, &error);
12075     if (outCount < count && !exiting 
12076                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12077         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12078         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12079             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12080                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12081                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12082             } else {
12083                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12084             }
12085             gameInfo.resultDetails = buf;
12086         }
12087         DisplayFatalError(buf, error, 1);
12088     }
12089 }
12090
12091 void
12092 ReceiveFromProgram(isr, closure, message, count, error)
12093      InputSourceRef isr;
12094      VOIDSTAR closure;
12095      char *message;
12096      int count;
12097      int error;
12098 {
12099     char *end_str;
12100     char buf[MSG_SIZ];
12101     ChessProgramState *cps = (ChessProgramState *)closure;
12102
12103     if (isr != cps->isr) return; /* Killed intentionally */
12104     if (count <= 0) {
12105         if (count == 0) {
12106             sprintf(buf,
12107                     _("Error: %s chess program (%s) exited unexpectedly"),
12108                     cps->which, cps->program);
12109         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12110                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12111                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12112                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12113                 } else {
12114                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12115                 }
12116                 gameInfo.resultDetails = buf;
12117             }
12118             RemoveInputSource(cps->isr);
12119             DisplayFatalError(buf, 0, 1);
12120         } else {
12121             sprintf(buf,
12122                     _("Error reading from %s chess program (%s)"),
12123                     cps->which, cps->program);
12124             RemoveInputSource(cps->isr);
12125
12126             /* [AS] Program is misbehaving badly... kill it */
12127             if( count == -2 ) {
12128                 DestroyChildProcess( cps->pr, 9 );
12129                 cps->pr = NoProc;
12130             }
12131
12132             DisplayFatalError(buf, error, 1);
12133         }
12134         return;
12135     }
12136     
12137     if ((end_str = strchr(message, '\r')) != NULL)
12138       *end_str = NULLCHAR;
12139     if ((end_str = strchr(message, '\n')) != NULL)
12140       *end_str = NULLCHAR;
12141     
12142     if (appData.debugMode) {
12143         TimeMark now; int print = 1;
12144         char *quote = ""; char c; int i;
12145
12146         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12147                 char start = message[0];
12148                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12149                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12150                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12151                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12152                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12153                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12154                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12155                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12156                         { quote = "# "; print = (appData.engineComments == 2); }
12157                 message[0] = start; // restore original message
12158         }
12159         if(print) {
12160                 GetTimeMark(&now);
12161                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12162                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12163                         quote,
12164                         message);
12165         }
12166     }
12167
12168     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12169     if (appData.icsEngineAnalyze) {
12170         if (strstr(message, "whisper") != NULL ||
12171              strstr(message, "kibitz") != NULL || 
12172             strstr(message, "tellics") != NULL) return;
12173     }
12174
12175     HandleMachineMove(message, cps);
12176 }
12177
12178
12179 void
12180 SendTimeControl(cps, mps, tc, inc, sd, st)
12181      ChessProgramState *cps;
12182      int mps, inc, sd, st;
12183      long tc;
12184 {
12185     char buf[MSG_SIZ];
12186     int seconds;
12187
12188     if( timeControl_2 > 0 ) {
12189         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12190             tc = timeControl_2;
12191         }
12192     }
12193     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12194     inc /= cps->timeOdds;
12195     st  /= cps->timeOdds;
12196
12197     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12198
12199     if (st > 0) {
12200       /* Set exact time per move, normally using st command */
12201       if (cps->stKludge) {
12202         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12203         seconds = st % 60;
12204         if (seconds == 0) {
12205           sprintf(buf, "level 1 %d\n", st/60);
12206         } else {
12207           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12208         }
12209       } else {
12210         sprintf(buf, "st %d\n", st);
12211       }
12212     } else {
12213       /* Set conventional or incremental time control, using level command */
12214       if (seconds == 0) {
12215         /* Note old gnuchess bug -- minutes:seconds used to not work.
12216            Fixed in later versions, but still avoid :seconds
12217            when seconds is 0. */
12218         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12219       } else {
12220         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12221                 seconds, inc/1000);
12222       }
12223     }
12224     SendToProgram(buf, cps);
12225
12226     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12227     /* Orthogonally, limit search to given depth */
12228     if (sd > 0) {
12229       if (cps->sdKludge) {
12230         sprintf(buf, "depth\n%d\n", sd);
12231       } else {
12232         sprintf(buf, "sd %d\n", sd);
12233       }
12234       SendToProgram(buf, cps);
12235     }
12236
12237     if(cps->nps > 0) { /* [HGM] nps */
12238         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12239         else {
12240                 sprintf(buf, "nps %d\n", cps->nps);
12241               SendToProgram(buf, cps);
12242         }
12243     }
12244 }
12245
12246 ChessProgramState *WhitePlayer()
12247 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12248 {
12249     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12250        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12251         return &second;
12252     return &first;
12253 }
12254
12255 void
12256 SendTimeRemaining(cps, machineWhite)
12257      ChessProgramState *cps;
12258      int /*boolean*/ machineWhite;
12259 {
12260     char message[MSG_SIZ];
12261     long time, otime;
12262
12263     /* Note: this routine must be called when the clocks are stopped
12264        or when they have *just* been set or switched; otherwise
12265        it will be off by the time since the current tick started.
12266     */
12267     if (machineWhite) {
12268         time = whiteTimeRemaining / 10;
12269         otime = blackTimeRemaining / 10;
12270     } else {
12271         time = blackTimeRemaining / 10;
12272         otime = whiteTimeRemaining / 10;
12273     }
12274     /* [HGM] translate opponent's time by time-odds factor */
12275     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12276     if (appData.debugMode) {
12277         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12278     }
12279
12280     if (time <= 0) time = 1;
12281     if (otime <= 0) otime = 1;
12282     
12283     sprintf(message, "time %ld\n", time);
12284     SendToProgram(message, cps);
12285
12286     sprintf(message, "otim %ld\n", otime);
12287     SendToProgram(message, cps);
12288 }
12289
12290 int
12291 BoolFeature(p, name, loc, cps)
12292      char **p;
12293      char *name;
12294      int *loc;
12295      ChessProgramState *cps;
12296 {
12297   char buf[MSG_SIZ];
12298   int len = strlen(name);
12299   int val;
12300   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12301     (*p) += len + 1;
12302     sscanf(*p, "%d", &val);
12303     *loc = (val != 0);
12304     while (**p && **p != ' ') (*p)++;
12305     sprintf(buf, "accepted %s\n", name);
12306     SendToProgram(buf, cps);
12307     return TRUE;
12308   }
12309   return FALSE;
12310 }
12311
12312 int
12313 IntFeature(p, name, loc, cps)
12314      char **p;
12315      char *name;
12316      int *loc;
12317      ChessProgramState *cps;
12318 {
12319   char buf[MSG_SIZ];
12320   int len = strlen(name);
12321   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12322     (*p) += len + 1;
12323     sscanf(*p, "%d", loc);
12324     while (**p && **p != ' ') (*p)++;
12325     sprintf(buf, "accepted %s\n", name);
12326     SendToProgram(buf, cps);
12327     return TRUE;
12328   }
12329   return FALSE;
12330 }
12331
12332 int
12333 StringFeature(p, name, loc, cps)
12334      char **p;
12335      char *name;
12336      char loc[];
12337      ChessProgramState *cps;
12338 {
12339   char buf[MSG_SIZ];
12340   int len = strlen(name);
12341   if (strncmp((*p), name, len) == 0
12342       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12343     (*p) += len + 2;
12344     sscanf(*p, "%[^\"]", loc);
12345     while (**p && **p != '\"') (*p)++;
12346     if (**p == '\"') (*p)++;
12347     sprintf(buf, "accepted %s\n", name);
12348     SendToProgram(buf, cps);
12349     return TRUE;
12350   }
12351   return FALSE;
12352 }
12353
12354 int 
12355 ParseOption(Option *opt, ChessProgramState *cps)
12356 // [HGM] options: process the string that defines an engine option, and determine
12357 // name, type, default value, and allowed value range
12358 {
12359         char *p, *q, buf[MSG_SIZ];
12360         int n, min = (-1)<<31, max = 1<<31, def;
12361
12362         if(p = strstr(opt->name, " -spin ")) {
12363             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12364             if(max < min) max = min; // enforce consistency
12365             if(def < min) def = min;
12366             if(def > max) def = max;
12367             opt->value = def;
12368             opt->min = min;
12369             opt->max = max;
12370             opt->type = Spin;
12371         } else if((p = strstr(opt->name, " -slider "))) {
12372             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12373             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12374             if(max < min) max = min; // enforce consistency
12375             if(def < min) def = min;
12376             if(def > max) def = max;
12377             opt->value = def;
12378             opt->min = min;
12379             opt->max = max;
12380             opt->type = Spin; // Slider;
12381         } else if((p = strstr(opt->name, " -string "))) {
12382             opt->textValue = p+9;
12383             opt->type = TextBox;
12384         } else if((p = strstr(opt->name, " -file "))) {
12385             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12386             opt->textValue = p+7;
12387             opt->type = TextBox; // FileName;
12388         } else if((p = strstr(opt->name, " -path "))) {
12389             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12390             opt->textValue = p+7;
12391             opt->type = TextBox; // PathName;
12392         } else if(p = strstr(opt->name, " -check ")) {
12393             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12394             opt->value = (def != 0);
12395             opt->type = CheckBox;
12396         } else if(p = strstr(opt->name, " -combo ")) {
12397             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12398             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12399             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12400             opt->value = n = 0;
12401             while(q = StrStr(q, " /// ")) {
12402                 n++; *q = 0;    // count choices, and null-terminate each of them
12403                 q += 5;
12404                 if(*q == '*') { // remember default, which is marked with * prefix
12405                     q++;
12406                     opt->value = n;
12407                 }
12408                 cps->comboList[cps->comboCnt++] = q;
12409             }
12410             cps->comboList[cps->comboCnt++] = NULL;
12411             opt->max = n + 1;
12412             opt->type = ComboBox;
12413         } else if(p = strstr(opt->name, " -button")) {
12414             opt->type = Button;
12415         } else if(p = strstr(opt->name, " -save")) {
12416             opt->type = SaveButton;
12417         } else return FALSE;
12418         *p = 0; // terminate option name
12419         // now look if the command-line options define a setting for this engine option.
12420         if(cps->optionSettings && cps->optionSettings[0])
12421             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12422         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12423                 sprintf(buf, "option %s", p);
12424                 if(p = strstr(buf, ",")) *p = 0;
12425                 strcat(buf, "\n");
12426                 SendToProgram(buf, cps);
12427         }
12428         return TRUE;
12429 }
12430
12431 void
12432 FeatureDone(cps, val)
12433      ChessProgramState* cps;
12434      int val;
12435 {
12436   DelayedEventCallback cb = GetDelayedEvent();
12437   if ((cb == InitBackEnd3 && cps == &first) ||
12438       (cb == TwoMachinesEventIfReady && cps == &second)) {
12439     CancelDelayedEvent();
12440     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12441   }
12442   cps->initDone = val;
12443 }
12444
12445 /* Parse feature command from engine */
12446 void
12447 ParseFeatures(args, cps)
12448      char* args;
12449      ChessProgramState *cps;  
12450 {
12451   char *p = args;
12452   char *q;
12453   int val;
12454   char buf[MSG_SIZ];
12455
12456   for (;;) {
12457     while (*p == ' ') p++;
12458     if (*p == NULLCHAR) return;
12459
12460     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12461     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12462     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12463     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12464     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12465     if (BoolFeature(&p, "reuse", &val, cps)) {
12466       /* Engine can disable reuse, but can't enable it if user said no */
12467       if (!val) cps->reuse = FALSE;
12468       continue;
12469     }
12470     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12471     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12472       if (gameMode == TwoMachinesPlay) {
12473         DisplayTwoMachinesTitle();
12474       } else {
12475         DisplayTitle("");
12476       }
12477       continue;
12478     }
12479     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12480     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12481     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12482     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12483     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12484     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12485     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12486     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12487     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12488     if (IntFeature(&p, "done", &val, cps)) {
12489       FeatureDone(cps, val);
12490       continue;
12491     }
12492     /* Added by Tord: */
12493     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12494     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12495     /* End of additions by Tord */
12496
12497     /* [HGM] added features: */
12498     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12499     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12500     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12501     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12502     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12503     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12504     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12505         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12506             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12507             SendToProgram(buf, cps);
12508             continue;
12509         }
12510         if(cps->nrOptions >= MAX_OPTIONS) {
12511             cps->nrOptions--;
12512             sprintf(buf, "%s engine has too many options\n", cps->which);
12513             DisplayError(buf, 0);
12514         }
12515         continue;
12516     }
12517     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12518     /* End of additions by HGM */
12519
12520     /* unknown feature: complain and skip */
12521     q = p;
12522     while (*q && *q != '=') q++;
12523     sprintf(buf, "rejected %.*s\n", q-p, p);
12524     SendToProgram(buf, cps);
12525     p = q;
12526     if (*p == '=') {
12527       p++;
12528       if (*p == '\"') {
12529         p++;
12530         while (*p && *p != '\"') p++;
12531         if (*p == '\"') p++;
12532       } else {
12533         while (*p && *p != ' ') p++;
12534       }
12535     }
12536   }
12537
12538 }
12539
12540 void
12541 PeriodicUpdatesEvent(newState)
12542      int newState;
12543 {
12544     if (newState == appData.periodicUpdates)
12545       return;
12546
12547     appData.periodicUpdates=newState;
12548
12549     /* Display type changes, so update it now */
12550 //    DisplayAnalysis();
12551
12552     /* Get the ball rolling again... */
12553     if (newState) {
12554         AnalysisPeriodicEvent(1);
12555         StartAnalysisClock();
12556     }
12557 }
12558
12559 void
12560 PonderNextMoveEvent(newState)
12561      int newState;
12562 {
12563     if (newState == appData.ponderNextMove) return;
12564     if (gameMode == EditPosition) EditPositionDone();
12565     if (newState) {
12566         SendToProgram("hard\n", &first);
12567         if (gameMode == TwoMachinesPlay) {
12568             SendToProgram("hard\n", &second);
12569         }
12570     } else {
12571         SendToProgram("easy\n", &first);
12572         thinkOutput[0] = NULLCHAR;
12573         if (gameMode == TwoMachinesPlay) {
12574             SendToProgram("easy\n", &second);
12575         }
12576     }
12577     appData.ponderNextMove = newState;
12578 }
12579
12580 void
12581 NewSettingEvent(option, command, value)
12582      char *command;
12583      int option, value;
12584 {
12585     char buf[MSG_SIZ];
12586
12587     if (gameMode == EditPosition) EditPositionDone();
12588     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12589     SendToProgram(buf, &first);
12590     if (gameMode == TwoMachinesPlay) {
12591         SendToProgram(buf, &second);
12592     }
12593 }
12594
12595 void
12596 ShowThinkingEvent()
12597 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12598 {
12599     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12600     int newState = appData.showThinking
12601         // [HGM] thinking: other features now need thinking output as well
12602         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12603     
12604     if (oldState == newState) return;
12605     oldState = newState;
12606     if (gameMode == EditPosition) EditPositionDone();
12607     if (oldState) {
12608         SendToProgram("post\n", &first);
12609         if (gameMode == TwoMachinesPlay) {
12610             SendToProgram("post\n", &second);
12611         }
12612     } else {
12613         SendToProgram("nopost\n", &first);
12614         thinkOutput[0] = NULLCHAR;
12615         if (gameMode == TwoMachinesPlay) {
12616             SendToProgram("nopost\n", &second);
12617         }
12618     }
12619 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12620 }
12621
12622 void
12623 AskQuestionEvent(title, question, replyPrefix, which)
12624      char *title; char *question; char *replyPrefix; char *which;
12625 {
12626   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12627   if (pr == NoProc) return;
12628   AskQuestion(title, question, replyPrefix, pr);
12629 }
12630
12631 void
12632 DisplayMove(moveNumber)
12633      int moveNumber;
12634 {
12635     char message[MSG_SIZ];
12636     char res[MSG_SIZ];
12637     char cpThinkOutput[MSG_SIZ];
12638
12639     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12640     
12641     if (moveNumber == forwardMostMove - 1 || 
12642         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12643
12644         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12645
12646         if (strchr(cpThinkOutput, '\n')) {
12647             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12648         }
12649     } else {
12650         *cpThinkOutput = NULLCHAR;
12651     }
12652
12653     /* [AS] Hide thinking from human user */
12654     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12655         *cpThinkOutput = NULLCHAR;
12656         if( thinkOutput[0] != NULLCHAR ) {
12657             int i;
12658
12659             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12660                 cpThinkOutput[i] = '.';
12661             }
12662             cpThinkOutput[i] = NULLCHAR;
12663             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12664         }
12665     }
12666
12667     if (moveNumber == forwardMostMove - 1 &&
12668         gameInfo.resultDetails != NULL) {
12669         if (gameInfo.resultDetails[0] == NULLCHAR) {
12670             sprintf(res, " %s", PGNResult(gameInfo.result));
12671         } else {
12672             sprintf(res, " {%s} %s",
12673                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12674         }
12675     } else {
12676         res[0] = NULLCHAR;
12677     }
12678
12679     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12680         DisplayMessage(res, cpThinkOutput);
12681     } else {
12682         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12683                 WhiteOnMove(moveNumber) ? " " : ".. ",
12684                 parseList[moveNumber], res);
12685         DisplayMessage(message, cpThinkOutput);
12686     }
12687 }
12688
12689 void
12690 DisplayComment(moveNumber, text)
12691      int moveNumber;
12692      char *text;
12693 {
12694     char title[MSG_SIZ];
12695     char buf[8000]; // comment can be long!
12696     int score, depth;
12697
12698     if( appData.autoDisplayComment ) {
12699         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12700             strcpy(title, "Comment");
12701         } else {
12702             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12703                     WhiteOnMove(moveNumber) ? " " : ".. ",
12704                     parseList[moveNumber]);
12705         }
12706         // [HGM] PV info: display PV info together with (or as) comment
12707         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12708             if(text == NULL) text = "";                                           
12709             score = pvInfoList[moveNumber].score;
12710             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12711                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12712             text = buf;
12713         }
12714     } else title[0] = 0;
12715
12716     if (text != NULL)
12717         CommentPopUp(title, text);
12718 }
12719
12720 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12721  * might be busy thinking or pondering.  It can be omitted if your
12722  * gnuchess is configured to stop thinking immediately on any user
12723  * input.  However, that gnuchess feature depends on the FIONREAD
12724  * ioctl, which does not work properly on some flavors of Unix.
12725  */
12726 void
12727 Attention(cps)
12728      ChessProgramState *cps;
12729 {
12730 #if ATTENTION
12731     if (!cps->useSigint) return;
12732     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12733     switch (gameMode) {
12734       case MachinePlaysWhite:
12735       case MachinePlaysBlack:
12736       case TwoMachinesPlay:
12737       case IcsPlayingWhite:
12738       case IcsPlayingBlack:
12739       case AnalyzeMode:
12740       case AnalyzeFile:
12741         /* Skip if we know it isn't thinking */
12742         if (!cps->maybeThinking) return;
12743         if (appData.debugMode)
12744           fprintf(debugFP, "Interrupting %s\n", cps->which);
12745         InterruptChildProcess(cps->pr);
12746         cps->maybeThinking = FALSE;
12747         break;
12748       default:
12749         break;
12750     }
12751 #endif /*ATTENTION*/
12752 }
12753
12754 int
12755 CheckFlags()
12756 {
12757     if (whiteTimeRemaining <= 0) {
12758         if (!whiteFlag) {
12759             whiteFlag = TRUE;
12760             if (appData.icsActive) {
12761                 if (appData.autoCallFlag &&
12762                     gameMode == IcsPlayingBlack && !blackFlag) {
12763                   SendToICS(ics_prefix);
12764                   SendToICS("flag\n");
12765                 }
12766             } else {
12767                 if (blackFlag) {
12768                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12769                 } else {
12770                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12771                     if (appData.autoCallFlag) {
12772                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12773                         return TRUE;
12774                     }
12775                 }
12776             }
12777         }
12778     }
12779     if (blackTimeRemaining <= 0) {
12780         if (!blackFlag) {
12781             blackFlag = TRUE;
12782             if (appData.icsActive) {
12783                 if (appData.autoCallFlag &&
12784                     gameMode == IcsPlayingWhite && !whiteFlag) {
12785                   SendToICS(ics_prefix);
12786                   SendToICS("flag\n");
12787                 }
12788             } else {
12789                 if (whiteFlag) {
12790                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12791                 } else {
12792                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12793                     if (appData.autoCallFlag) {
12794                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12795                         return TRUE;
12796                     }
12797                 }
12798             }
12799         }
12800     }
12801     return FALSE;
12802 }
12803
12804 void
12805 CheckTimeControl()
12806 {
12807     if (!appData.clockMode || appData.icsActive ||
12808         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12809
12810     /*
12811      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12812      */
12813     if ( !WhiteOnMove(forwardMostMove) )
12814         /* White made time control */
12815         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12816         /* [HGM] time odds: correct new time quota for time odds! */
12817                                             / WhitePlayer()->timeOdds;
12818       else
12819         /* Black made time control */
12820         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12821                                             / WhitePlayer()->other->timeOdds;
12822 }
12823
12824 void
12825 DisplayBothClocks()
12826 {
12827     int wom = gameMode == EditPosition ?
12828       !blackPlaysFirst : WhiteOnMove(currentMove);
12829     DisplayWhiteClock(whiteTimeRemaining, wom);
12830     DisplayBlackClock(blackTimeRemaining, !wom);
12831 }
12832
12833
12834 /* Timekeeping seems to be a portability nightmare.  I think everyone
12835    has ftime(), but I'm really not sure, so I'm including some ifdefs
12836    to use other calls if you don't.  Clocks will be less accurate if
12837    you have neither ftime nor gettimeofday.
12838 */
12839
12840 /* VS 2008 requires the #include outside of the function */
12841 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12842 #include <sys/timeb.h>
12843 #endif
12844
12845 /* Get the current time as a TimeMark */
12846 void
12847 GetTimeMark(tm)
12848      TimeMark *tm;
12849 {
12850 #if HAVE_GETTIMEOFDAY
12851
12852     struct timeval timeVal;
12853     struct timezone timeZone;
12854
12855     gettimeofday(&timeVal, &timeZone);
12856     tm->sec = (long) timeVal.tv_sec; 
12857     tm->ms = (int) (timeVal.tv_usec / 1000L);
12858
12859 #else /*!HAVE_GETTIMEOFDAY*/
12860 #if HAVE_FTIME
12861
12862 // include <sys/timeb.h> / moved to just above start of function
12863     struct timeb timeB;
12864
12865     ftime(&timeB);
12866     tm->sec = (long) timeB.time;
12867     tm->ms = (int) timeB.millitm;
12868
12869 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12870     tm->sec = (long) time(NULL);
12871     tm->ms = 0;
12872 #endif
12873 #endif
12874 }
12875
12876 /* Return the difference in milliseconds between two
12877    time marks.  We assume the difference will fit in a long!
12878 */
12879 long
12880 SubtractTimeMarks(tm2, tm1)
12881      TimeMark *tm2, *tm1;
12882 {
12883     return 1000L*(tm2->sec - tm1->sec) +
12884            (long) (tm2->ms - tm1->ms);
12885 }
12886
12887
12888 /*
12889  * Code to manage the game clocks.
12890  *
12891  * In tournament play, black starts the clock and then white makes a move.
12892  * We give the human user a slight advantage if he is playing white---the
12893  * clocks don't run until he makes his first move, so it takes zero time.
12894  * Also, we don't account for network lag, so we could get out of sync
12895  * with GNU Chess's clock -- but then, referees are always right.  
12896  */
12897
12898 static TimeMark tickStartTM;
12899 static long intendedTickLength;
12900
12901 long
12902 NextTickLength(timeRemaining)
12903      long timeRemaining;
12904 {
12905     long nominalTickLength, nextTickLength;
12906
12907     if (timeRemaining > 0L && timeRemaining <= 10000L)
12908       nominalTickLength = 100L;
12909     else
12910       nominalTickLength = 1000L;
12911     nextTickLength = timeRemaining % nominalTickLength;
12912     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12913
12914     return nextTickLength;
12915 }
12916
12917 /* Adjust clock one minute up or down */
12918 void
12919 AdjustClock(Boolean which, int dir)
12920 {
12921     if(which) blackTimeRemaining += 60000*dir;
12922     else      whiteTimeRemaining += 60000*dir;
12923     DisplayBothClocks();
12924 }
12925
12926 /* Stop clocks and reset to a fresh time control */
12927 void
12928 ResetClocks() 
12929 {
12930     (void) StopClockTimer();
12931     if (appData.icsActive) {
12932         whiteTimeRemaining = blackTimeRemaining = 0;
12933     } else { /* [HGM] correct new time quote for time odds */
12934         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
12935         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
12936     }
12937     if (whiteFlag || blackFlag) {
12938         DisplayTitle("");
12939         whiteFlag = blackFlag = FALSE;
12940     }
12941     DisplayBothClocks();
12942 }
12943
12944 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
12945
12946 /* Decrement running clock by amount of time that has passed */
12947 void
12948 DecrementClocks()
12949 {
12950     long timeRemaining;
12951     long lastTickLength, fudge;
12952     TimeMark now;
12953
12954     if (!appData.clockMode) return;
12955     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
12956         
12957     GetTimeMark(&now);
12958
12959     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
12960
12961     /* Fudge if we woke up a little too soon */
12962     fudge = intendedTickLength - lastTickLength;
12963     if (fudge < 0 || fudge > FUDGE) fudge = 0;
12964
12965     if (WhiteOnMove(forwardMostMove)) {
12966         if(whiteNPS >= 0) lastTickLength = 0;
12967         timeRemaining = whiteTimeRemaining -= lastTickLength;
12968         DisplayWhiteClock(whiteTimeRemaining - fudge,
12969                           WhiteOnMove(currentMove));
12970     } else {
12971         if(blackNPS >= 0) lastTickLength = 0;
12972         timeRemaining = blackTimeRemaining -= lastTickLength;
12973         DisplayBlackClock(blackTimeRemaining - fudge,
12974                           !WhiteOnMove(currentMove));
12975     }
12976
12977     if (CheckFlags()) return;
12978         
12979     tickStartTM = now;
12980     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
12981     StartClockTimer(intendedTickLength);
12982
12983     /* if the time remaining has fallen below the alarm threshold, sound the
12984      * alarm. if the alarm has sounded and (due to a takeback or time control
12985      * with increment) the time remaining has increased to a level above the
12986      * threshold, reset the alarm so it can sound again. 
12987      */
12988     
12989     if (appData.icsActive && appData.icsAlarm) {
12990
12991         /* make sure we are dealing with the user's clock */
12992         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
12993                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
12994            )) return;
12995
12996         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
12997             alarmSounded = FALSE;
12998         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
12999             PlayAlarmSound();
13000             alarmSounded = TRUE;
13001         }
13002     }
13003 }
13004
13005
13006 /* A player has just moved, so stop the previously running
13007    clock and (if in clock mode) start the other one.
13008    We redisplay both clocks in case we're in ICS mode, because
13009    ICS gives us an update to both clocks after every move.
13010    Note that this routine is called *after* forwardMostMove
13011    is updated, so the last fractional tick must be subtracted
13012    from the color that is *not* on move now.
13013 */
13014 void
13015 SwitchClocks()
13016 {
13017     long lastTickLength;
13018     TimeMark now;
13019     int flagged = FALSE;
13020
13021     GetTimeMark(&now);
13022
13023     if (StopClockTimer() && appData.clockMode) {
13024         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13025         if (WhiteOnMove(forwardMostMove)) {
13026             if(blackNPS >= 0) lastTickLength = 0;
13027             blackTimeRemaining -= lastTickLength;
13028            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13029 //         if(pvInfoList[forwardMostMove-1].time == -1)
13030                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13031                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13032         } else {
13033            if(whiteNPS >= 0) lastTickLength = 0;
13034            whiteTimeRemaining -= lastTickLength;
13035            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13036 //         if(pvInfoList[forwardMostMove-1].time == -1)
13037                  pvInfoList[forwardMostMove-1].time = 
13038                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13039         }
13040         flagged = CheckFlags();
13041     }
13042     CheckTimeControl();
13043
13044     if (flagged || !appData.clockMode) return;
13045
13046     switch (gameMode) {
13047       case MachinePlaysBlack:
13048       case MachinePlaysWhite:
13049       case BeginningOfGame:
13050         if (pausing) return;
13051         break;
13052
13053       case EditGame:
13054       case PlayFromGameFile:
13055       case IcsExamining:
13056         return;
13057
13058       default:
13059         break;
13060     }
13061
13062     tickStartTM = now;
13063     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13064       whiteTimeRemaining : blackTimeRemaining);
13065     StartClockTimer(intendedTickLength);
13066 }
13067         
13068
13069 /* Stop both clocks */
13070 void
13071 StopClocks()
13072 {       
13073     long lastTickLength;
13074     TimeMark now;
13075
13076     if (!StopClockTimer()) return;
13077     if (!appData.clockMode) return;
13078
13079     GetTimeMark(&now);
13080
13081     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13082     if (WhiteOnMove(forwardMostMove)) {
13083         if(whiteNPS >= 0) lastTickLength = 0;
13084         whiteTimeRemaining -= lastTickLength;
13085         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13086     } else {
13087         if(blackNPS >= 0) lastTickLength = 0;
13088         blackTimeRemaining -= lastTickLength;
13089         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13090     }
13091     CheckFlags();
13092 }
13093         
13094 /* Start clock of player on move.  Time may have been reset, so
13095    if clock is already running, stop and restart it. */
13096 void
13097 StartClocks()
13098 {
13099     (void) StopClockTimer(); /* in case it was running already */
13100     DisplayBothClocks();
13101     if (CheckFlags()) return;
13102
13103     if (!appData.clockMode) return;
13104     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13105
13106     GetTimeMark(&tickStartTM);
13107     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13108       whiteTimeRemaining : blackTimeRemaining);
13109
13110    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13111     whiteNPS = blackNPS = -1; 
13112     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13113        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13114         whiteNPS = first.nps;
13115     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13116        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13117         blackNPS = first.nps;
13118     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13119         whiteNPS = second.nps;
13120     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13121         blackNPS = second.nps;
13122     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13123
13124     StartClockTimer(intendedTickLength);
13125 }
13126
13127 char *
13128 TimeString(ms)
13129      long ms;
13130 {
13131     long second, minute, hour, day;
13132     char *sign = "";
13133     static char buf[32];
13134     
13135     if (ms > 0 && ms <= 9900) {
13136       /* convert milliseconds to tenths, rounding up */
13137       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13138
13139       sprintf(buf, " %03.1f ", tenths/10.0);
13140       return buf;
13141     }
13142
13143     /* convert milliseconds to seconds, rounding up */
13144     /* use floating point to avoid strangeness of integer division
13145        with negative dividends on many machines */
13146     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13147
13148     if (second < 0) {
13149         sign = "-";
13150         second = -second;
13151     }
13152     
13153     day = second / (60 * 60 * 24);
13154     second = second % (60 * 60 * 24);
13155     hour = second / (60 * 60);
13156     second = second % (60 * 60);
13157     minute = second / 60;
13158     second = second % 60;
13159     
13160     if (day > 0)
13161       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13162               sign, day, hour, minute, second);
13163     else if (hour > 0)
13164       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13165     else
13166       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13167     
13168     return buf;
13169 }
13170
13171
13172 /*
13173  * This is necessary because some C libraries aren't ANSI C compliant yet.
13174  */
13175 char *
13176 StrStr(string, match)
13177      char *string, *match;
13178 {
13179     int i, length;
13180     
13181     length = strlen(match);
13182     
13183     for (i = strlen(string) - length; i >= 0; i--, string++)
13184       if (!strncmp(match, string, length))
13185         return string;
13186     
13187     return NULL;
13188 }
13189
13190 char *
13191 StrCaseStr(string, match)
13192      char *string, *match;
13193 {
13194     int i, j, length;
13195     
13196     length = strlen(match);
13197     
13198     for (i = strlen(string) - length; i >= 0; i--, string++) {
13199         for (j = 0; j < length; j++) {
13200             if (ToLower(match[j]) != ToLower(string[j]))
13201               break;
13202         }
13203         if (j == length) return string;
13204     }
13205
13206     return NULL;
13207 }
13208
13209 #ifndef _amigados
13210 int
13211 StrCaseCmp(s1, s2)
13212      char *s1, *s2;
13213 {
13214     char c1, c2;
13215     
13216     for (;;) {
13217         c1 = ToLower(*s1++);
13218         c2 = ToLower(*s2++);
13219         if (c1 > c2) return 1;
13220         if (c1 < c2) return -1;
13221         if (c1 == NULLCHAR) return 0;
13222     }
13223 }
13224
13225
13226 int
13227 ToLower(c)
13228      int c;
13229 {
13230     return isupper(c) ? tolower(c) : c;
13231 }
13232
13233
13234 int
13235 ToUpper(c)
13236      int c;
13237 {
13238     return islower(c) ? toupper(c) : c;
13239 }
13240 #endif /* !_amigados    */
13241
13242 char *
13243 StrSave(s)
13244      char *s;
13245 {
13246     char *ret;
13247
13248     if ((ret = (char *) malloc(strlen(s) + 1))) {
13249         strcpy(ret, s);
13250     }
13251     return ret;
13252 }
13253
13254 char *
13255 StrSavePtr(s, savePtr)
13256      char *s, **savePtr;
13257 {
13258     if (*savePtr) {
13259         free(*savePtr);
13260     }
13261     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13262         strcpy(*savePtr, s);
13263     }
13264     return(*savePtr);
13265 }
13266
13267 char *
13268 PGNDate()
13269 {
13270     time_t clock;
13271     struct tm *tm;
13272     char buf[MSG_SIZ];
13273
13274     clock = time((time_t *)NULL);
13275     tm = localtime(&clock);
13276     sprintf(buf, "%04d.%02d.%02d",
13277             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13278     return StrSave(buf);
13279 }
13280
13281
13282 char *
13283 PositionToFEN(move, overrideCastling)
13284      int move;
13285      char *overrideCastling;
13286 {
13287     int i, j, fromX, fromY, toX, toY;
13288     int whiteToPlay;
13289     char buf[128];
13290     char *p, *q;
13291     int emptycount;
13292     ChessSquare piece;
13293
13294     whiteToPlay = (gameMode == EditPosition) ?
13295       !blackPlaysFirst : (move % 2 == 0);
13296     p = buf;
13297
13298     /* Piece placement data */
13299     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13300         emptycount = 0;
13301         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13302             if (boards[move][i][j] == EmptySquare) {
13303                 emptycount++;
13304             } else { ChessSquare piece = boards[move][i][j];
13305                 if (emptycount > 0) {
13306                     if(emptycount<10) /* [HGM] can be >= 10 */
13307                         *p++ = '0' + emptycount;
13308                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13309                     emptycount = 0;
13310                 }
13311                 if(PieceToChar(piece) == '+') {
13312                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13313                     *p++ = '+';
13314                     piece = (ChessSquare)(DEMOTED piece);
13315                 } 
13316                 *p++ = PieceToChar(piece);
13317                 if(p[-1] == '~') {
13318                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13319                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13320                     *p++ = '~';
13321                 }
13322             }
13323         }
13324         if (emptycount > 0) {
13325             if(emptycount<10) /* [HGM] can be >= 10 */
13326                 *p++ = '0' + emptycount;
13327             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13328             emptycount = 0;
13329         }
13330         *p++ = '/';
13331     }
13332     *(p - 1) = ' ';
13333
13334     /* [HGM] print Crazyhouse or Shogi holdings */
13335     if( gameInfo.holdingsWidth ) {
13336         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13337         q = p;
13338         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13339             piece = boards[move][i][BOARD_WIDTH-1];
13340             if( piece != EmptySquare )
13341               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13342                   *p++ = PieceToChar(piece);
13343         }
13344         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13345             piece = boards[move][BOARD_HEIGHT-i-1][0];
13346             if( piece != EmptySquare )
13347               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13348                   *p++ = PieceToChar(piece);
13349         }
13350
13351         if( q == p ) *p++ = '-';
13352         *p++ = ']';
13353         *p++ = ' ';
13354     }
13355
13356     /* Active color */
13357     *p++ = whiteToPlay ? 'w' : 'b';
13358     *p++ = ' ';
13359
13360   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13361     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13362   } else {
13363   if(nrCastlingRights) {
13364      q = p;
13365      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13366        /* [HGM] write directly from rights */
13367            if(castlingRights[move][2] >= 0 &&
13368               castlingRights[move][0] >= 0   )
13369                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13370            if(castlingRights[move][2] >= 0 &&
13371               castlingRights[move][1] >= 0   )
13372                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13373            if(castlingRights[move][5] >= 0 &&
13374               castlingRights[move][3] >= 0   )
13375                 *p++ = castlingRights[move][3] + AAA;
13376            if(castlingRights[move][5] >= 0 &&
13377               castlingRights[move][4] >= 0   )
13378                 *p++ = castlingRights[move][4] + AAA;
13379      } else {
13380
13381         /* [HGM] write true castling rights */
13382         if( nrCastlingRights == 6 ) {
13383             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13384                castlingRights[move][2] >= 0  ) *p++ = 'K';
13385             if(castlingRights[move][1] == BOARD_LEFT &&
13386                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13387             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13388                castlingRights[move][5] >= 0  ) *p++ = 'k';
13389             if(castlingRights[move][4] == BOARD_LEFT &&
13390                castlingRights[move][5] >= 0  ) *p++ = 'q';
13391         }
13392      }
13393      if (q == p) *p++ = '-'; /* No castling rights */
13394      *p++ = ' ';
13395   }
13396
13397   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13398      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13399     /* En passant target square */
13400     if (move > backwardMostMove) {
13401         fromX = moveList[move - 1][0] - AAA;
13402         fromY = moveList[move - 1][1] - ONE;
13403         toX = moveList[move - 1][2] - AAA;
13404         toY = moveList[move - 1][3] - ONE;
13405         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13406             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13407             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13408             fromX == toX) {
13409             /* 2-square pawn move just happened */
13410             *p++ = toX + AAA;
13411             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13412         } else {
13413             *p++ = '-';
13414         }
13415     } else if(move == backwardMostMove) {
13416         // [HGM] perhaps we should always do it like this, and forget the above?
13417         if(epStatus[move] >= 0) {
13418             *p++ = epStatus[move] + AAA;
13419             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13420         } else {
13421             *p++ = '-';
13422         }
13423     } else {
13424         *p++ = '-';
13425     }
13426     *p++ = ' ';
13427   }
13428   }
13429
13430     /* [HGM] find reversible plies */
13431     {   int i = 0, j=move;
13432
13433         if (appData.debugMode) { int k;
13434             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13435             for(k=backwardMostMove; k<=forwardMostMove; k++)
13436                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13437
13438         }
13439
13440         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13441         if( j == backwardMostMove ) i += initialRulePlies;
13442         sprintf(p, "%d ", i);
13443         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13444     }
13445     /* Fullmove number */
13446     sprintf(p, "%d", (move / 2) + 1);
13447     
13448     return StrSave(buf);
13449 }
13450
13451 Boolean
13452 ParseFEN(board, blackPlaysFirst, fen)
13453     Board board;
13454      int *blackPlaysFirst;
13455      char *fen;
13456 {
13457     int i, j;
13458     char *p;
13459     int emptycount;
13460     ChessSquare piece;
13461
13462     p = fen;
13463
13464     /* [HGM] by default clear Crazyhouse holdings, if present */
13465     if(gameInfo.holdingsWidth) {
13466        for(i=0; i<BOARD_HEIGHT; i++) {
13467            board[i][0]             = EmptySquare; /* black holdings */
13468            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13469            board[i][1]             = (ChessSquare) 0; /* black counts */
13470            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13471        }
13472     }
13473
13474     /* Piece placement data */
13475     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13476         j = 0;
13477         for (;;) {
13478             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13479                 if (*p == '/') p++;
13480                 emptycount = gameInfo.boardWidth - j;
13481                 while (emptycount--)
13482                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13483                 break;
13484 #if(BOARD_SIZE >= 10)
13485             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13486                 p++; emptycount=10;
13487                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13488                 while (emptycount--)
13489                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13490 #endif
13491             } else if (isdigit(*p)) {
13492                 emptycount = *p++ - '0';
13493                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13494                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13495                 while (emptycount--)
13496                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13497             } else if (*p == '+' || isalpha(*p)) {
13498                 if (j >= gameInfo.boardWidth) return FALSE;
13499                 if(*p=='+') {
13500                     piece = CharToPiece(*++p);
13501                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13502                     piece = (ChessSquare) (PROMOTED piece ); p++;
13503                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13504                 } else piece = CharToPiece(*p++);
13505
13506                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13507                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13508                     piece = (ChessSquare) (PROMOTED piece);
13509                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13510                     p++;
13511                 }
13512                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13513             } else {
13514                 return FALSE;
13515             }
13516         }
13517     }
13518     while (*p == '/' || *p == ' ') p++;
13519
13520     /* [HGM] look for Crazyhouse holdings here */
13521     while(*p==' ') p++;
13522     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13523         if(*p == '[') p++;
13524         if(*p == '-' ) *p++; /* empty holdings */ else {
13525             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13526             /* if we would allow FEN reading to set board size, we would   */
13527             /* have to add holdings and shift the board read so far here   */
13528             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13529                 *p++;
13530                 if((int) piece >= (int) BlackPawn ) {
13531                     i = (int)piece - (int)BlackPawn;
13532                     i = PieceToNumber((ChessSquare)i);
13533                     if( i >= gameInfo.holdingsSize ) return FALSE;
13534                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13535                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13536                 } else {
13537                     i = (int)piece - (int)WhitePawn;
13538                     i = PieceToNumber((ChessSquare)i);
13539                     if( i >= gameInfo.holdingsSize ) return FALSE;
13540                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13541                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13542                 }
13543             }
13544         }
13545         if(*p == ']') *p++;
13546     }
13547
13548     while(*p == ' ') p++;
13549
13550     /* Active color */
13551     switch (*p++) {
13552       case 'w':
13553         *blackPlaysFirst = FALSE;
13554         break;
13555       case 'b': 
13556         *blackPlaysFirst = TRUE;
13557         break;
13558       default:
13559         return FALSE;
13560     }
13561
13562     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13563     /* return the extra info in global variiables             */
13564
13565     /* set defaults in case FEN is incomplete */
13566     FENepStatus = EP_UNKNOWN;
13567     for(i=0; i<nrCastlingRights; i++ ) {
13568         FENcastlingRights[i] =
13569             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13570     }   /* assume possible unless obviously impossible */
13571     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13572     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13573     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13574     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13575     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13576     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13577     FENrulePlies = 0;
13578
13579     while(*p==' ') p++;
13580     if(nrCastlingRights) {
13581       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13582           /* castling indicator present, so default becomes no castlings */
13583           for(i=0; i<nrCastlingRights; i++ ) {
13584                  FENcastlingRights[i] = -1;
13585           }
13586       }
13587       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13588              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13589              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13590              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13591         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13592
13593         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13594             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13595             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13596         }
13597         switch(c) {
13598           case'K':
13599               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13600               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13601               FENcastlingRights[2] = whiteKingFile;
13602               break;
13603           case'Q':
13604               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13605               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13606               FENcastlingRights[2] = whiteKingFile;
13607               break;
13608           case'k':
13609               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13610               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13611               FENcastlingRights[5] = blackKingFile;
13612               break;
13613           case'q':
13614               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13615               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13616               FENcastlingRights[5] = blackKingFile;
13617           case '-':
13618               break;
13619           default: /* FRC castlings */
13620               if(c >= 'a') { /* black rights */
13621                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13622                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13623                   if(i == BOARD_RGHT) break;
13624                   FENcastlingRights[5] = i;
13625                   c -= AAA;
13626                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13627                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13628                   if(c > i)
13629                       FENcastlingRights[3] = c;
13630                   else
13631                       FENcastlingRights[4] = c;
13632               } else { /* white rights */
13633                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13634                     if(board[0][i] == WhiteKing) break;
13635                   if(i == BOARD_RGHT) break;
13636                   FENcastlingRights[2] = i;
13637                   c -= AAA - 'a' + 'A';
13638                   if(board[0][c] >= WhiteKing) break;
13639                   if(c > i)
13640                       FENcastlingRights[0] = c;
13641                   else
13642                       FENcastlingRights[1] = c;
13643               }
13644         }
13645       }
13646     if (appData.debugMode) {
13647         fprintf(debugFP, "FEN castling rights:");
13648         for(i=0; i<nrCastlingRights; i++)
13649         fprintf(debugFP, " %d", FENcastlingRights[i]);
13650         fprintf(debugFP, "\n");
13651     }
13652
13653       while(*p==' ') p++;
13654     }
13655
13656     /* read e.p. field in games that know e.p. capture */
13657     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13658        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13659       if(*p=='-') {
13660         p++; FENepStatus = EP_NONE;
13661       } else {
13662          char c = *p++ - AAA;
13663
13664          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13665          if(*p >= '0' && *p <='9') *p++;
13666          FENepStatus = c;
13667       }
13668     }
13669
13670
13671     if(sscanf(p, "%d", &i) == 1) {
13672         FENrulePlies = i; /* 50-move ply counter */
13673         /* (The move number is still ignored)    */
13674     }
13675
13676     return TRUE;
13677 }
13678       
13679 void
13680 EditPositionPasteFEN(char *fen)
13681 {
13682   if (fen != NULL) {
13683     Board initial_position;
13684
13685     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13686       DisplayError(_("Bad FEN position in clipboard"), 0);
13687       return ;
13688     } else {
13689       int savedBlackPlaysFirst = blackPlaysFirst;
13690       EditPositionEvent();
13691       blackPlaysFirst = savedBlackPlaysFirst;
13692       CopyBoard(boards[0], initial_position);
13693           /* [HGM] copy FEN attributes as well */
13694           {   int i;
13695               initialRulePlies = FENrulePlies;
13696               epStatus[0] = FENepStatus;
13697               for( i=0; i<nrCastlingRights; i++ )
13698                   castlingRights[0][i] = FENcastlingRights[i];
13699           }
13700       EditPositionDone();
13701       DisplayBothClocks();
13702       DrawPosition(FALSE, boards[currentMove]);
13703     }
13704   }
13705 }