a112d57e86d38f1408dbb6a0bdd25e0a45aa10f7
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 #else /* not STDC_HEADERS */
81 # if HAVE_STRING_H
82 #  include <string.h>
83 # else /* not HAVE_STRING_H */
84 #  include <strings.h>
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
87
88 #if HAVE_SYS_FCNTL_H
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
91 # if HAVE_FCNTL_H
92 #  include <fcntl.h>
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
95
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
98 # include <time.h>
99 #else
100 # if HAVE_SYS_TIME_H
101 #  include <sys/time.h>
102 # else
103 #  include <time.h>
104 # endif
105 #endif
106
107 #if defined(_amigados) && !defined(__GNUC__)
108 struct timezone {
109     int tz_minuteswest;
110     int tz_dsttime;
111 };
112 extern int gettimeofday(struct timeval *, struct timezone *);
113 #endif
114
115 #if HAVE_UNISTD_H
116 # include <unistd.h>
117 #endif
118
119 #include "common.h"
120 #include "frontend.h"
121 #include "backend.h"
122 #include "parser.h"
123 #include "moves.h"
124 #if ZIPPY
125 # include "zippy.h"
126 #endif
127 #include "backendz.h"
128 #include "gettext.h" 
129  
130 #ifdef ENABLE_NLS 
131 # define _(s) gettext (s) 
132 # define N_(s) gettext_noop (s) 
133 #else 
134 # define _(s) (s) 
135 # define N_(s) s 
136 #endif 
137
138
139 /* A point in time */
140 typedef struct {
141     long sec;  /* Assuming this is >= 32 bits */
142     int ms;    /* Assuming this is >= 16 bits */
143 } TimeMark;
144
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147                          char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149                       char *buf, int count, int error));
150 void ics_printf P((char *format, ...));
151 void SendToICS P((char *s));
152 void SendToICSDelayed P((char *s, long msdelay));
153 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
154                       int toX, int toY));
155 void HandleMachineMove P((char *message, ChessProgramState *cps));
156 int AutoPlayOneMove P((void));
157 int LoadGameOneMove P((ChessMove readAhead));
158 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
159 int LoadPositionFromFile P((char *filename, int n, char *title));
160 int SavePositionToFile P((char *filename));
161 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162                   Board board, char *castle, char *ep));
163 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
164 void ShowMove P((int fromX, int fromY, int toX, int toY));
165 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
166                    /*char*/int promoChar));
167 void BackwardInner P((int target));
168 void ForwardInner P((int target));
169 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
170 void EditPositionDone P((void));
171 void PrintOpponents P((FILE *fp));
172 void PrintPosition P((FILE *fp, int move));
173 void StartChessProgram P((ChessProgramState *cps));
174 void SendToProgram P((char *message, ChessProgramState *cps));
175 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
176 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
177                            char *buf, int count, int error));
178 void SendTimeControl P((ChessProgramState *cps,
179                         int mps, long tc, int inc, int sd, int st));
180 char *TimeControlTagValue P((void));
181 void Attention P((ChessProgramState *cps));
182 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
183 void ResurrectChessProgram P((void));
184 void DisplayComment P((int moveNumber, char *text));
185 void DisplayMove P((int moveNumber));
186 void DisplayAnalysis P((void));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom: 
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /* 
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  epStatus[MAX_MOVES];
445 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int   initialRulePlies, FENrulePlies;
450 char  FENepStatus;
451 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
452 int loadFlag = 0; 
453 int shuffleOpenings;
454 int mute; // mute all sounds
455
456 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
457     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460         BlackKing, BlackBishop, BlackKnight, BlackRook }
461 };
462
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467         BlackKing, BlackKing, BlackKnight, BlackRook }
468 };
469
470 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473     { BlackRook, BlackMan, BlackBishop, BlackQueen,
474         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
475 };
476
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481         BlackKing, BlackBishop, BlackKnight, BlackRook }
482 };
483
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
489 };
490
491
492 #if (BOARD_SIZE>=10)
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
498 };
499
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
505 };
506
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
509         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
511         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
512 };
513
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
516         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
518         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
519 };
520
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
523         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
525         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
526 };
527
528 #ifdef GOTHIC
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
531         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
533         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
534 };
535 #else // !GOTHIC
536 #define GothicArray CapablancaArray
537 #endif // !GOTHIC
538
539 #ifdef FALCON
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
542         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
544         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !FALCON
547 #define FalconArray CapablancaArray
548 #endif // !FALCON
549
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
556
557 #if (BOARD_SIZE>=12)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
563 };
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
567
568
569 Board initialPosition;
570
571
572 /* Convert str to a rating. Checks for special cases of "----",
573
574    "++++", etc. Also strips ()'s */
575 int
576 string_to_rating(str)
577   char *str;
578 {
579   while(*str && !isdigit(*str)) ++str;
580   if (!*str)
581     return 0;   /* One of the special "no rating" cases */
582   else
583     return atoi(str);
584 }
585
586 void
587 ClearProgramStats()
588 {
589     /* Init programStats */
590     programStats.movelist[0] = 0;
591     programStats.depth = 0;
592     programStats.nr_moves = 0;
593     programStats.moves_left = 0;
594     programStats.nodes = 0;
595     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
596     programStats.score = 0;
597     programStats.got_only_move = 0;
598     programStats.got_fail = 0;
599     programStats.line_is_book = 0;
600 }
601
602 void
603 InitBackEnd1()
604 {
605     int matched, min, sec;
606
607     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
608
609     GetTimeMark(&programStartTime);
610     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
611
612     ClearProgramStats();
613     programStats.ok_to_send = 1;
614     programStats.seen_stat = 0;
615
616     /*
617      * Initialize game list
618      */
619     ListNew(&gameList);
620
621
622     /*
623      * Internet chess server status
624      */
625     if (appData.icsActive) {
626         appData.matchMode = FALSE;
627         appData.matchGames = 0;
628 #if ZIPPY       
629         appData.noChessProgram = !appData.zippyPlay;
630 #else
631         appData.zippyPlay = FALSE;
632         appData.zippyTalk = FALSE;
633         appData.noChessProgram = TRUE;
634 #endif
635         if (*appData.icsHelper != NULLCHAR) {
636             appData.useTelnet = TRUE;
637             appData.telnetProgram = appData.icsHelper;
638         }
639     } else {
640         appData.zippyTalk = appData.zippyPlay = FALSE;
641     }
642
643     /* [AS] Initialize pv info list [HGM] and game state */
644     {
645         int i, j;
646
647         for( i=0; i<MAX_MOVES; i++ ) {
648             pvInfoList[i].depth = -1;
649             epStatus[i]=EP_NONE;
650             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
651         }
652     }
653
654     /*
655      * Parse timeControl resource
656      */
657     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658                           appData.movesPerSession)) {
659         char buf[MSG_SIZ];
660         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661         DisplayFatalError(buf, 0, 2);
662     }
663
664     /*
665      * Parse searchTime resource
666      */
667     if (*appData.searchTime != NULLCHAR) {
668         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
669         if (matched == 1) {
670             searchTime = min * 60;
671         } else if (matched == 2) {
672             searchTime = min * 60 + sec;
673         } else {
674             char buf[MSG_SIZ];
675             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676             DisplayFatalError(buf, 0, 2);
677         }
678     }
679
680     /* [AS] Adjudication threshold */
681     adjudicateLossThreshold = appData.adjudicateLossThreshold;
682     
683     first.which = "first";
684     second.which = "second";
685     first.maybeThinking = second.maybeThinking = FALSE;
686     first.pr = second.pr = NoProc;
687     first.isr = second.isr = NULL;
688     first.sendTime = second.sendTime = 2;
689     first.sendDrawOffers = 1;
690     if (appData.firstPlaysBlack) {
691         first.twoMachinesColor = "black\n";
692         second.twoMachinesColor = "white\n";
693     } else {
694         first.twoMachinesColor = "white\n";
695         second.twoMachinesColor = "black\n";
696     }
697     first.program = appData.firstChessProgram;
698     second.program = appData.secondChessProgram;
699     first.host = appData.firstHost;
700     second.host = appData.secondHost;
701     first.dir = appData.firstDirectory;
702     second.dir = appData.secondDirectory;
703     first.other = &second;
704     second.other = &first;
705     first.initString = appData.initString;
706     second.initString = appData.secondInitString;
707     first.computerString = appData.firstComputerString;
708     second.computerString = appData.secondComputerString;
709     first.useSigint = second.useSigint = TRUE;
710     first.useSigterm = second.useSigterm = TRUE;
711     first.reuse = appData.reuseFirst;
712     second.reuse = appData.reuseSecond;
713     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
714     second.nps = appData.secondNPS;
715     first.useSetboard = second.useSetboard = FALSE;
716     first.useSAN = second.useSAN = FALSE;
717     first.usePing = second.usePing = FALSE;
718     first.lastPing = second.lastPing = 0;
719     first.lastPong = second.lastPong = 0;
720     first.usePlayother = second.usePlayother = FALSE;
721     first.useColors = second.useColors = TRUE;
722     first.useUsermove = second.useUsermove = FALSE;
723     first.sendICS = second.sendICS = FALSE;
724     first.sendName = second.sendName = appData.icsActive;
725     first.sdKludge = second.sdKludge = FALSE;
726     first.stKludge = second.stKludge = FALSE;
727     TidyProgramName(first.program, first.host, first.tidy);
728     TidyProgramName(second.program, second.host, second.tidy);
729     first.matchWins = second.matchWins = 0;
730     strcpy(first.variants, appData.variant);
731     strcpy(second.variants, appData.variant);
732     first.analysisSupport = second.analysisSupport = 2; /* detect */
733     first.analyzing = second.analyzing = FALSE;
734     first.initDone = second.initDone = FALSE;
735
736     /* New features added by Tord: */
737     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739     /* End of new features added by Tord. */
740     first.fenOverride  = appData.fenOverride1;
741     second.fenOverride = appData.fenOverride2;
742
743     /* [HGM] time odds: set factor for each machine */
744     first.timeOdds  = appData.firstTimeOdds;
745     second.timeOdds = appData.secondTimeOdds;
746     { int norm = 1;
747         if(appData.timeOddsMode) {
748             norm = first.timeOdds;
749             if(norm > second.timeOdds) norm = second.timeOdds;
750         }
751         first.timeOdds /= norm;
752         second.timeOdds /= norm;
753     }
754
755     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756     first.accumulateTC = appData.firstAccumulateTC;
757     second.accumulateTC = appData.secondAccumulateTC;
758     first.maxNrOfSessions = second.maxNrOfSessions = 1;
759
760     /* [HGM] debug */
761     first.debug = second.debug = FALSE;
762     first.supportsNPS = second.supportsNPS = UNKNOWN;
763
764     /* [HGM] options */
765     first.optionSettings  = appData.firstOptions;
766     second.optionSettings = appData.secondOptions;
767
768     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770     first.isUCI = appData.firstIsUCI; /* [AS] */
771     second.isUCI = appData.secondIsUCI; /* [AS] */
772     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
774
775     if (appData.firstProtocolVersion > PROTOVER ||
776         appData.firstProtocolVersion < 1) {
777       char buf[MSG_SIZ];
778       sprintf(buf, _("protocol version %d not supported"),
779               appData.firstProtocolVersion);
780       DisplayFatalError(buf, 0, 2);
781     } else {
782       first.protocolVersion = appData.firstProtocolVersion;
783     }
784
785     if (appData.secondProtocolVersion > PROTOVER ||
786         appData.secondProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.secondProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       second.protocolVersion = appData.secondProtocolVersion;
793     }
794
795     if (appData.icsActive) {
796         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
797     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798         appData.clockMode = FALSE;
799         first.sendTime = second.sendTime = 0;
800     }
801     
802 #if ZIPPY
803     /* Override some settings from environment variables, for backward
804        compatibility.  Unfortunately it's not feasible to have the env
805        vars just set defaults, at least in xboard.  Ugh.
806     */
807     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
808       ZippyInit();
809     }
810 #endif
811     
812     if (appData.noChessProgram) {
813         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814         sprintf(programVersion, "%s", PACKAGE_STRING);
815     } else {
816       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
819     }
820
821     if (!appData.icsActive) {
822       char buf[MSG_SIZ];
823       /* Check for variants that are supported only in ICS mode,
824          or not at all.  Some that are accepted here nevertheless
825          have bugs; see comments below.
826       */
827       VariantClass variant = StringToVariant(appData.variant);
828       switch (variant) {
829       case VariantBughouse:     /* need four players and two boards */
830       case VariantKriegspiel:   /* need to hide pieces and move details */
831       /* case VariantFischeRandom: (Fabien: moved below) */
832         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833         DisplayFatalError(buf, 0, 2);
834         return;
835
836       case VariantUnknown:
837       case VariantLoadable:
838       case Variant29:
839       case Variant30:
840       case Variant31:
841       case Variant32:
842       case Variant33:
843       case Variant34:
844       case Variant35:
845       case Variant36:
846       default:
847         sprintf(buf, _("Unknown variant name %s"), appData.variant);
848         DisplayFatalError(buf, 0, 2);
849         return;
850
851       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
852       case VariantFairy:      /* [HGM] TestLegality definitely off! */
853       case VariantGothic:     /* [HGM] should work */
854       case VariantCapablanca: /* [HGM] should work */
855       case VariantCourier:    /* [HGM] initial forced moves not implemented */
856       case VariantShogi:      /* [HGM] drops not tested for legality */
857       case VariantKnightmate: /* [HGM] should work */
858       case VariantCylinder:   /* [HGM] untested */
859       case VariantFalcon:     /* [HGM] untested */
860       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861                                  offboard interposition not understood */
862       case VariantNormal:     /* definitely works! */
863       case VariantWildCastle: /* pieces not automatically shuffled */
864       case VariantNoCastle:   /* pieces not automatically shuffled */
865       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866       case VariantLosers:     /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantSuicide:    /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantGiveaway:   /* should work except for win condition,
871                                  and doesn't know captures are mandatory */
872       case VariantTwoKings:   /* should work */
873       case VariantAtomic:     /* should work except for win condition */
874       case Variant3Check:     /* should work except for win condition */
875       case VariantShatranj:   /* should work except for all win conditions */
876       case VariantBerolina:   /* might work if TestLegality is off */
877       case VariantCapaRandom: /* should work */
878       case VariantJanus:      /* should work */
879       case VariantSuper:      /* experimental */
880       case VariantGreat:      /* experimental, requires legality testing to be off */
881         break;
882       }
883     }
884
885     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
886     InitEngineUCI( installDir, &second );
887 }
888
889 int NextIntegerFromString( char ** str, long * value )
890 {
891     int result = -1;
892     char * s = *str;
893
894     while( *s == ' ' || *s == '\t' ) {
895         s++;
896     }
897
898     *value = 0;
899
900     if( *s >= '0' && *s <= '9' ) {
901         while( *s >= '0' && *s <= '9' ) {
902             *value = *value * 10 + (*s - '0');
903             s++;
904         }
905
906         result = 0;
907     }
908
909     *str = s;
910
911     return result;
912 }
913
914 int NextTimeControlFromString( char ** str, long * value )
915 {
916     long temp;
917     int result = NextIntegerFromString( str, &temp );
918
919     if( result == 0 ) {
920         *value = temp * 60; /* Minutes */
921         if( **str == ':' ) {
922             (*str)++;
923             result = NextIntegerFromString( str, &temp );
924             *value += temp; /* Seconds */
925         }
926     }
927
928     return result;
929 }
930
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
933     int result = -1; long temp, temp2;
934
935     if(**str != '+') return -1; // old params remain in force!
936     (*str)++;
937     if( NextTimeControlFromString( str, &temp ) ) return -1;
938
939     if(**str != '/') {
940         /* time only: incremental or sudden-death time control */
941         if(**str == '+') { /* increment follows; read it */
942             (*str)++;
943             if(result = NextIntegerFromString( str, &temp2)) return -1;
944             *inc = temp2 * 1000;
945         } else *inc = 0;
946         *moves = 0; *tc = temp * 1000; 
947         return 0;
948     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
949
950     (*str)++; /* classical time control */
951     result = NextTimeControlFromString( str, &temp2);
952     if(result == 0) {
953         *moves = temp/60;
954         *tc    = temp2 * 1000;
955         *inc   = 0;
956     }
957     return result;
958 }
959
960 int GetTimeQuota(int movenr)
961 {   /* [HGM] get time to add from the multi-session time-control string */
962     int moves=1; /* kludge to force reading of first session */
963     long time, increment;
964     char *s = fullTimeControlString;
965
966     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
967     do {
968         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970         if(movenr == -1) return time;    /* last move before new session     */
971         if(!moves) return increment;     /* current session is incremental   */
972         if(movenr >= 0) movenr -= moves; /* we already finished this session */
973     } while(movenr >= -1);               /* try again for next session       */
974
975     return 0; // no new time quota on this move
976 }
977
978 int
979 ParseTimeControl(tc, ti, mps)
980      char *tc;
981      int ti;
982      int mps;
983 {
984   long tc1;
985   long tc2;
986   char buf[MSG_SIZ];
987   
988   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
989   if(ti > 0) {
990     if(mps)
991       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992     else sprintf(buf, "+%s+%d", tc, ti);
993   } else {
994     if(mps)
995              sprintf(buf, "+%d/%s", mps, tc);
996     else sprintf(buf, "+%s", tc);
997   }
998   fullTimeControlString = StrSave(buf);
999   
1000   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1001     return FALSE;
1002   }
1003   
1004   if( *tc == '/' ) {
1005     /* Parse second time control */
1006     tc++;
1007     
1008     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1009       return FALSE;
1010     }
1011     
1012     if( tc2 == 0 ) {
1013       return FALSE;
1014     }
1015     
1016     timeControl_2 = tc2 * 1000;
1017   }
1018   else {
1019     timeControl_2 = 0;
1020   }
1021   
1022   if( tc1 == 0 ) {
1023     return FALSE;
1024   }
1025   
1026   timeControl = tc1 * 1000;
1027   
1028   if (ti >= 0) {
1029     timeIncrement = ti * 1000;  /* convert to ms */
1030     movesPerSession = 0;
1031   } else {
1032     timeIncrement = 0;
1033     movesPerSession = mps;
1034   }
1035   return TRUE;
1036 }
1037
1038 void
1039 InitBackEnd2()
1040 {
1041     if (appData.debugMode) {
1042         fprintf(debugFP, "%s\n", programVersion);
1043     }
1044
1045     if (appData.matchGames > 0) {
1046         appData.matchMode = TRUE;
1047     } else if (appData.matchMode) {
1048         appData.matchGames = 1;
1049     }
1050     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1051         appData.matchGames = appData.sameColorGames;
1052     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1053         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1054         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1055     }
1056     Reset(TRUE, FALSE);
1057     if (appData.noChessProgram || first.protocolVersion == 1) {
1058       InitBackEnd3();
1059     } else {
1060       /* kludge: allow timeout for initial "feature" commands */
1061       FreezeUI();
1062       DisplayMessage("", _("Starting chess program"));
1063       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1064     }
1065 }
1066
1067 void
1068 InitBackEnd3 P((void))
1069 {
1070     GameMode initialMode;
1071     char buf[MSG_SIZ];
1072     int err;
1073
1074     InitChessProgram(&first, startedFromSetupPosition);
1075
1076
1077     if (appData.icsActive) {
1078 #ifdef WIN32
1079         /* [DM] Make a console window if needed [HGM] merged ifs */
1080         ConsoleCreate(); 
1081 #endif
1082         err = establish();
1083         if (err != 0) {
1084             if (*appData.icsCommPort != NULLCHAR) {
1085                 sprintf(buf, _("Could not open comm port %s"),  
1086                         appData.icsCommPort);
1087             } else {
1088                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1089                         appData.icsHost, appData.icsPort);
1090             }
1091             DisplayFatalError(buf, err, 1);
1092             return;
1093         }
1094         SetICSMode();
1095         telnetISR =
1096           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1097         fromUserISR =
1098           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1099     } else if (appData.noChessProgram) {
1100         SetNCPMode();
1101     } else {
1102         SetGNUMode();
1103     }
1104
1105     if (*appData.cmailGameName != NULLCHAR) {
1106         SetCmailMode();
1107         OpenLoopback(&cmailPR);
1108         cmailISR =
1109           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1110     }
1111     
1112     ThawUI();
1113     DisplayMessage("", "");
1114     if (StrCaseCmp(appData.initialMode, "") == 0) {
1115       initialMode = BeginningOfGame;
1116     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1117       initialMode = TwoMachinesPlay;
1118     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1119       initialMode = AnalyzeFile; 
1120     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1121       initialMode = AnalyzeMode;
1122     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1123       initialMode = MachinePlaysWhite;
1124     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1125       initialMode = MachinePlaysBlack;
1126     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1127       initialMode = EditGame;
1128     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1129       initialMode = EditPosition;
1130     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1131       initialMode = Training;
1132     } else {
1133       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1134       DisplayFatalError(buf, 0, 2);
1135       return;
1136     }
1137
1138     if (appData.matchMode) {
1139         /* Set up machine vs. machine match */
1140         if (appData.noChessProgram) {
1141             DisplayFatalError(_("Can't have a match with no chess programs"),
1142                               0, 2);
1143             return;
1144         }
1145         matchMode = TRUE;
1146         matchGame = 1;
1147         if (*appData.loadGameFile != NULLCHAR) {
1148             int index = appData.loadGameIndex; // [HGM] autoinc
1149             if(index<0) lastIndex = index = 1;
1150             if (!LoadGameFromFile(appData.loadGameFile,
1151                                   index,
1152                                   appData.loadGameFile, FALSE)) {
1153                 DisplayFatalError(_("Bad game file"), 0, 1);
1154                 return;
1155             }
1156         } else if (*appData.loadPositionFile != NULLCHAR) {
1157             int index = appData.loadPositionIndex; // [HGM] autoinc
1158             if(index<0) lastIndex = index = 1;
1159             if (!LoadPositionFromFile(appData.loadPositionFile,
1160                                       index,
1161                                       appData.loadPositionFile)) {
1162                 DisplayFatalError(_("Bad position file"), 0, 1);
1163                 return;
1164             }
1165         }
1166         TwoMachinesEvent();
1167     } else if (*appData.cmailGameName != NULLCHAR) {
1168         /* Set up cmail mode */
1169         ReloadCmailMsgEvent(TRUE);
1170     } else {
1171         /* Set up other modes */
1172         if (initialMode == AnalyzeFile) {
1173           if (*appData.loadGameFile == NULLCHAR) {
1174             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1175             return;
1176           }
1177         }
1178         if (*appData.loadGameFile != NULLCHAR) {
1179             (void) LoadGameFromFile(appData.loadGameFile,
1180                                     appData.loadGameIndex,
1181                                     appData.loadGameFile, TRUE);
1182         } else if (*appData.loadPositionFile != NULLCHAR) {
1183             (void) LoadPositionFromFile(appData.loadPositionFile,
1184                                         appData.loadPositionIndex,
1185                                         appData.loadPositionFile);
1186             /* [HGM] try to make self-starting even after FEN load */
1187             /* to allow automatic setup of fairy variants with wtm */
1188             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1189                 gameMode = BeginningOfGame;
1190                 setboardSpoiledMachineBlack = 1;
1191             }
1192             /* [HGM] loadPos: make that every new game uses the setup */
1193             /* from file as long as we do not switch variant          */
1194             if(!blackPlaysFirst) { int i;
1195                 startedFromPositionFile = TRUE;
1196                 CopyBoard(filePosition, boards[0]);
1197                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1198             }
1199         }
1200         if (initialMode == AnalyzeMode) {
1201           if (appData.noChessProgram) {
1202             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1203             return;
1204           }
1205           if (appData.icsActive) {
1206             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1207             return;
1208           }
1209           AnalyzeModeEvent();
1210         } else if (initialMode == AnalyzeFile) {
1211           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1212           ShowThinkingEvent();
1213           AnalyzeFileEvent();
1214           AnalysisPeriodicEvent(1);
1215         } else if (initialMode == MachinePlaysWhite) {
1216           if (appData.noChessProgram) {
1217             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1218                               0, 2);
1219             return;
1220           }
1221           if (appData.icsActive) {
1222             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1223                               0, 2);
1224             return;
1225           }
1226           MachineWhiteEvent();
1227         } else if (initialMode == MachinePlaysBlack) {
1228           if (appData.noChessProgram) {
1229             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1230                               0, 2);
1231             return;
1232           }
1233           if (appData.icsActive) {
1234             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1235                               0, 2);
1236             return;
1237           }
1238           MachineBlackEvent();
1239         } else if (initialMode == TwoMachinesPlay) {
1240           if (appData.noChessProgram) {
1241             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1242                               0, 2);
1243             return;
1244           }
1245           if (appData.icsActive) {
1246             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1247                               0, 2);
1248             return;
1249           }
1250           TwoMachinesEvent();
1251         } else if (initialMode == EditGame) {
1252           EditGameEvent();
1253         } else if (initialMode == EditPosition) {
1254           EditPositionEvent();
1255         } else if (initialMode == Training) {
1256           if (*appData.loadGameFile == NULLCHAR) {
1257             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1258             return;
1259           }
1260           TrainingEvent();
1261         }
1262     }
1263 }
1264
1265 /*
1266  * Establish will establish a contact to a remote host.port.
1267  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1268  *  used to talk to the host.
1269  * Returns 0 if okay, error code if not.
1270  */
1271 int
1272 establish()
1273 {
1274     char buf[MSG_SIZ];
1275
1276     if (*appData.icsCommPort != NULLCHAR) {
1277         /* Talk to the host through a serial comm port */
1278         return OpenCommPort(appData.icsCommPort, &icsPR);
1279
1280     } else if (*appData.gateway != NULLCHAR) {
1281         if (*appData.remoteShell == NULLCHAR) {
1282             /* Use the rcmd protocol to run telnet program on a gateway host */
1283             snprintf(buf, sizeof(buf), "%s %s %s",
1284                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1285             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1286
1287         } else {
1288             /* Use the rsh program to run telnet program on a gateway host */
1289             if (*appData.remoteUser == NULLCHAR) {
1290                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1291                         appData.gateway, appData.telnetProgram,
1292                         appData.icsHost, appData.icsPort);
1293             } else {
1294                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1295                         appData.remoteShell, appData.gateway, 
1296                         appData.remoteUser, appData.telnetProgram,
1297                         appData.icsHost, appData.icsPort);
1298             }
1299             return StartChildProcess(buf, "", &icsPR);
1300
1301         }
1302     } else if (appData.useTelnet) {
1303         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1304
1305     } else {
1306         /* TCP socket interface differs somewhat between
1307            Unix and NT; handle details in the front end.
1308            */
1309         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1310     }
1311 }
1312
1313 void
1314 show_bytes(fp, buf, count)
1315      FILE *fp;
1316      char *buf;
1317      int count;
1318 {
1319     while (count--) {
1320         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1321             fprintf(fp, "\\%03o", *buf & 0xff);
1322         } else {
1323             putc(*buf, fp);
1324         }
1325         buf++;
1326     }
1327     fflush(fp);
1328 }
1329
1330 /* Returns an errno value */
1331 int
1332 OutputMaybeTelnet(pr, message, count, outError)
1333      ProcRef pr;
1334      char *message;
1335      int count;
1336      int *outError;
1337 {
1338     char buf[8192], *p, *q, *buflim;
1339     int left, newcount, outcount;
1340
1341     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1342         *appData.gateway != NULLCHAR) {
1343         if (appData.debugMode) {
1344             fprintf(debugFP, ">ICS: ");
1345             show_bytes(debugFP, message, count);
1346             fprintf(debugFP, "\n");
1347         }
1348         return OutputToProcess(pr, message, count, outError);
1349     }
1350
1351     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1352     p = message;
1353     q = buf;
1354     left = count;
1355     newcount = 0;
1356     while (left) {
1357         if (q >= buflim) {
1358             if (appData.debugMode) {
1359                 fprintf(debugFP, ">ICS: ");
1360                 show_bytes(debugFP, buf, newcount);
1361                 fprintf(debugFP, "\n");
1362             }
1363             outcount = OutputToProcess(pr, buf, newcount, outError);
1364             if (outcount < newcount) return -1; /* to be sure */
1365             q = buf;
1366             newcount = 0;
1367         }
1368         if (*p == '\n') {
1369             *q++ = '\r';
1370             newcount++;
1371         } else if (((unsigned char) *p) == TN_IAC) {
1372             *q++ = (char) TN_IAC;
1373             newcount ++;
1374         }
1375         *q++ = *p++;
1376         newcount++;
1377         left--;
1378     }
1379     if (appData.debugMode) {
1380         fprintf(debugFP, ">ICS: ");
1381         show_bytes(debugFP, buf, newcount);
1382         fprintf(debugFP, "\n");
1383     }
1384     outcount = OutputToProcess(pr, buf, newcount, outError);
1385     if (outcount < newcount) return -1; /* to be sure */
1386     return count;
1387 }
1388
1389 void
1390 read_from_player(isr, closure, message, count, error)
1391      InputSourceRef isr;
1392      VOIDSTAR closure;
1393      char *message;
1394      int count;
1395      int error;
1396 {
1397     int outError, outCount;
1398     static int gotEof = 0;
1399
1400     /* Pass data read from player on to ICS */
1401     if (count > 0) {
1402         gotEof = 0;
1403         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1404         if (outCount < count) {
1405             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1406         }
1407     } else if (count < 0) {
1408         RemoveInputSource(isr);
1409         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1410     } else if (gotEof++ > 0) {
1411         RemoveInputSource(isr);
1412         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1413     }
1414 }
1415
1416 void
1417 KeepAlive()
1418 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1419     SendToICS("date\n");
1420     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1421 }
1422
1423 /* added routine for printf style output to ics */
1424 void ics_printf(char *format, ...)
1425 {
1426         char buffer[MSG_SIZ], *args;
1427         
1428         args = (char *)&format + sizeof(format);
1429         vsnprintf(buffer, sizeof(buffer), format, args);
1430         buffer[sizeof(buffer)-1] = '\0';
1431         SendToICS(buffer);
1432 }
1433
1434 void
1435 SendToICS(s)
1436      char *s;
1437 {
1438     int count, outCount, outError;
1439
1440     if (icsPR == NULL) return;
1441
1442     count = strlen(s);
1443     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1444     if (outCount < count) {
1445         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1446     }
1447 }
1448
1449 /* This is used for sending logon scripts to the ICS. Sending
1450    without a delay causes problems when using timestamp on ICC
1451    (at least on my machine). */
1452 void
1453 SendToICSDelayed(s,msdelay)
1454      char *s;
1455      long msdelay;
1456 {
1457     int count, outCount, outError;
1458
1459     if (icsPR == NULL) return;
1460
1461     count = strlen(s);
1462     if (appData.debugMode) {
1463         fprintf(debugFP, ">ICS: ");
1464         show_bytes(debugFP, s, count);
1465         fprintf(debugFP, "\n");
1466     }
1467     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1468                                       msdelay);
1469     if (outCount < count) {
1470         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1471     }
1472 }
1473
1474
1475 /* Remove all highlighting escape sequences in s
1476    Also deletes any suffix starting with '(' 
1477    */
1478 char *
1479 StripHighlightAndTitle(s)
1480      char *s;
1481 {
1482     static char retbuf[MSG_SIZ];
1483     char *p = retbuf;
1484
1485     while (*s != NULLCHAR) {
1486         while (*s == '\033') {
1487             while (*s != NULLCHAR && !isalpha(*s)) s++;
1488             if (*s != NULLCHAR) s++;
1489         }
1490         while (*s != NULLCHAR && *s != '\033') {
1491             if (*s == '(' || *s == '[') {
1492                 *p = NULLCHAR;
1493                 return retbuf;
1494             }
1495             *p++ = *s++;
1496         }
1497     }
1498     *p = NULLCHAR;
1499     return retbuf;
1500 }
1501
1502 /* Remove all highlighting escape sequences in s */
1503 char *
1504 StripHighlight(s)
1505      char *s;
1506 {
1507     static char retbuf[MSG_SIZ];
1508     char *p = retbuf;
1509
1510     while (*s != NULLCHAR) {
1511         while (*s == '\033') {
1512             while (*s != NULLCHAR && !isalpha(*s)) s++;
1513             if (*s != NULLCHAR) s++;
1514         }
1515         while (*s != NULLCHAR && *s != '\033') {
1516             *p++ = *s++;
1517         }
1518     }
1519     *p = NULLCHAR;
1520     return retbuf;
1521 }
1522
1523 char *variantNames[] = VARIANT_NAMES;
1524 char *
1525 VariantName(v)
1526      VariantClass v;
1527 {
1528     return variantNames[v];
1529 }
1530
1531
1532 /* Identify a variant from the strings the chess servers use or the
1533    PGN Variant tag names we use. */
1534 VariantClass
1535 StringToVariant(e)
1536      char *e;
1537 {
1538     char *p;
1539     int wnum = -1;
1540     VariantClass v = VariantNormal;
1541     int i, found = FALSE;
1542     char buf[MSG_SIZ];
1543
1544     if (!e) return v;
1545
1546     /* [HGM] skip over optional board-size prefixes */
1547     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1548         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1549         while( *e++ != '_');
1550     }
1551
1552     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1553         v = VariantNormal;
1554         found = TRUE;
1555     } else
1556     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1557       if (StrCaseStr(e, variantNames[i])) {
1558         v = (VariantClass) i;
1559         found = TRUE;
1560         break;
1561       }
1562     }
1563
1564     if (!found) {
1565       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1566           || StrCaseStr(e, "wild/fr") 
1567           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1568         v = VariantFischeRandom;
1569       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1570                  (i = 1, p = StrCaseStr(e, "w"))) {
1571         p += i;
1572         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1573         if (isdigit(*p)) {
1574           wnum = atoi(p);
1575         } else {
1576           wnum = -1;
1577         }
1578         switch (wnum) {
1579         case 0: /* FICS only, actually */
1580         case 1:
1581           /* Castling legal even if K starts on d-file */
1582           v = VariantWildCastle;
1583           break;
1584         case 2:
1585         case 3:
1586         case 4:
1587           /* Castling illegal even if K & R happen to start in
1588              normal positions. */
1589           v = VariantNoCastle;
1590           break;
1591         case 5:
1592         case 7:
1593         case 8:
1594         case 10:
1595         case 11:
1596         case 12:
1597         case 13:
1598         case 14:
1599         case 15:
1600         case 18:
1601         case 19:
1602           /* Castling legal iff K & R start in normal positions */
1603           v = VariantNormal;
1604           break;
1605         case 6:
1606         case 20:
1607         case 21:
1608           /* Special wilds for position setup; unclear what to do here */
1609           v = VariantLoadable;
1610           break;
1611         case 9:
1612           /* Bizarre ICC game */
1613           v = VariantTwoKings;
1614           break;
1615         case 16:
1616           v = VariantKriegspiel;
1617           break;
1618         case 17:
1619           v = VariantLosers;
1620           break;
1621         case 22:
1622           v = VariantFischeRandom;
1623           break;
1624         case 23:
1625           v = VariantCrazyhouse;
1626           break;
1627         case 24:
1628           v = VariantBughouse;
1629           break;
1630         case 25:
1631           v = Variant3Check;
1632           break;
1633         case 26:
1634           /* Not quite the same as FICS suicide! */
1635           v = VariantGiveaway;
1636           break;
1637         case 27:
1638           v = VariantAtomic;
1639           break;
1640         case 28:
1641           v = VariantShatranj;
1642           break;
1643
1644         /* Temporary names for future ICC types.  The name *will* change in 
1645            the next xboard/WinBoard release after ICC defines it. */
1646         case 29:
1647           v = Variant29;
1648           break;
1649         case 30:
1650           v = Variant30;
1651           break;
1652         case 31:
1653           v = Variant31;
1654           break;
1655         case 32:
1656           v = Variant32;
1657           break;
1658         case 33:
1659           v = Variant33;
1660           break;
1661         case 34:
1662           v = Variant34;
1663           break;
1664         case 35:
1665           v = Variant35;
1666           break;
1667         case 36:
1668           v = Variant36;
1669           break;
1670         case 37:
1671           v = VariantShogi;
1672           break;
1673         case 38:
1674           v = VariantXiangqi;
1675           break;
1676         case 39:
1677           v = VariantCourier;
1678           break;
1679         case 40:
1680           v = VariantGothic;
1681           break;
1682         case 41:
1683           v = VariantCapablanca;
1684           break;
1685         case 42:
1686           v = VariantKnightmate;
1687           break;
1688         case 43:
1689           v = VariantFairy;
1690           break;
1691         case 44:
1692           v = VariantCylinder;
1693           break;
1694         case 45:
1695           v = VariantFalcon;
1696           break;
1697         case 46:
1698           v = VariantCapaRandom;
1699           break;
1700         case 47:
1701           v = VariantBerolina;
1702           break;
1703         case 48:
1704           v = VariantJanus;
1705           break;
1706         case 49:
1707           v = VariantSuper;
1708           break;
1709         case 50:
1710           v = VariantGreat;
1711           break;
1712         case -1:
1713           /* Found "wild" or "w" in the string but no number;
1714              must assume it's normal chess. */
1715           v = VariantNormal;
1716           break;
1717         default:
1718           sprintf(buf, _("Unknown wild type %d"), wnum);
1719           DisplayError(buf, 0);
1720           v = VariantUnknown;
1721           break;
1722         }
1723       }
1724     }
1725     if (appData.debugMode) {
1726       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1727               e, wnum, VariantName(v));
1728     }
1729     return v;
1730 }
1731
1732 static int leftover_start = 0, leftover_len = 0;
1733 char star_match[STAR_MATCH_N][MSG_SIZ];
1734
1735 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1736    advance *index beyond it, and set leftover_start to the new value of
1737    *index; else return FALSE.  If pattern contains the character '*', it
1738    matches any sequence of characters not containing '\r', '\n', or the
1739    character following the '*' (if any), and the matched sequence(s) are
1740    copied into star_match.
1741    */
1742 int
1743 looking_at(buf, index, pattern)
1744      char *buf;
1745      int *index;
1746      char *pattern;
1747 {
1748     char *bufp = &buf[*index], *patternp = pattern;
1749     int star_count = 0;
1750     char *matchp = star_match[0];
1751     
1752     for (;;) {
1753         if (*patternp == NULLCHAR) {
1754             *index = leftover_start = bufp - buf;
1755             *matchp = NULLCHAR;
1756             return TRUE;
1757         }
1758         if (*bufp == NULLCHAR) return FALSE;
1759         if (*patternp == '*') {
1760             if (*bufp == *(patternp + 1)) {
1761                 *matchp = NULLCHAR;
1762                 matchp = star_match[++star_count];
1763                 patternp += 2;
1764                 bufp++;
1765                 continue;
1766             } else if (*bufp == '\n' || *bufp == '\r') {
1767                 patternp++;
1768                 if (*patternp == NULLCHAR)
1769                   continue;
1770                 else
1771                   return FALSE;
1772             } else {
1773                 *matchp++ = *bufp++;
1774                 continue;
1775             }
1776         }
1777         if (*patternp != *bufp) return FALSE;
1778         patternp++;
1779         bufp++;
1780     }
1781 }
1782
1783 void
1784 SendToPlayer(data, length)
1785      char *data;
1786      int length;
1787 {
1788     int error, outCount;
1789     outCount = OutputToProcess(NoProc, data, length, &error);
1790     if (outCount < length) {
1791         DisplayFatalError(_("Error writing to display"), error, 1);
1792     }
1793 }
1794
1795 void
1796 PackHolding(packed, holding)
1797      char packed[];
1798      char *holding;
1799 {
1800     char *p = holding;
1801     char *q = packed;
1802     int runlength = 0;
1803     int curr = 9999;
1804     do {
1805         if (*p == curr) {
1806             runlength++;
1807         } else {
1808             switch (runlength) {
1809               case 0:
1810                 break;
1811               case 1:
1812                 *q++ = curr;
1813                 break;
1814               case 2:
1815                 *q++ = curr;
1816                 *q++ = curr;
1817                 break;
1818               default:
1819                 sprintf(q, "%d", runlength);
1820                 while (*q) q++;
1821                 *q++ = curr;
1822                 break;
1823             }
1824             runlength = 1;
1825             curr = *p;
1826         }
1827     } while (*p++);
1828     *q = NULLCHAR;
1829 }
1830
1831 /* Telnet protocol requests from the front end */
1832 void
1833 TelnetRequest(ddww, option)
1834      unsigned char ddww, option;
1835 {
1836     unsigned char msg[3];
1837     int outCount, outError;
1838
1839     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1840
1841     if (appData.debugMode) {
1842         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1843         switch (ddww) {
1844           case TN_DO:
1845             ddwwStr = "DO";
1846             break;
1847           case TN_DONT:
1848             ddwwStr = "DONT";
1849             break;
1850           case TN_WILL:
1851             ddwwStr = "WILL";
1852             break;
1853           case TN_WONT:
1854             ddwwStr = "WONT";
1855             break;
1856           default:
1857             ddwwStr = buf1;
1858             sprintf(buf1, "%d", ddww);
1859             break;
1860         }
1861         switch (option) {
1862           case TN_ECHO:
1863             optionStr = "ECHO";
1864             break;
1865           default:
1866             optionStr = buf2;
1867             sprintf(buf2, "%d", option);
1868             break;
1869         }
1870         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1871     }
1872     msg[0] = TN_IAC;
1873     msg[1] = ddww;
1874     msg[2] = option;
1875     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1876     if (outCount < 3) {
1877         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1878     }
1879 }
1880
1881 void
1882 DoEcho()
1883 {
1884     if (!appData.icsActive) return;
1885     TelnetRequest(TN_DO, TN_ECHO);
1886 }
1887
1888 void
1889 DontEcho()
1890 {
1891     if (!appData.icsActive) return;
1892     TelnetRequest(TN_DONT, TN_ECHO);
1893 }
1894
1895 void
1896 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1897 {
1898     /* put the holdings sent to us by the server on the board holdings area */
1899     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1900     char p;
1901     ChessSquare piece;
1902
1903     if(gameInfo.holdingsWidth < 2)  return;
1904
1905     if( (int)lowestPiece >= BlackPawn ) {
1906         holdingsColumn = 0;
1907         countsColumn = 1;
1908         holdingsStartRow = BOARD_HEIGHT-1;
1909         direction = -1;
1910     } else {
1911         holdingsColumn = BOARD_WIDTH-1;
1912         countsColumn = BOARD_WIDTH-2;
1913         holdingsStartRow = 0;
1914         direction = 1;
1915     }
1916
1917     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1918         board[i][holdingsColumn] = EmptySquare;
1919         board[i][countsColumn]   = (ChessSquare) 0;
1920     }
1921     while( (p=*holdings++) != NULLCHAR ) {
1922         piece = CharToPiece( ToUpper(p) );
1923         if(piece == EmptySquare) continue;
1924         /*j = (int) piece - (int) WhitePawn;*/
1925         j = PieceToNumber(piece);
1926         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1927         if(j < 0) continue;               /* should not happen */
1928         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1929         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1930         board[holdingsStartRow+j*direction][countsColumn]++;
1931     }
1932
1933 }
1934
1935
1936 void
1937 VariantSwitch(Board board, VariantClass newVariant)
1938 {
1939    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1940    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1941 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
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             case VariantShogi:
1968               newWidth = 9;  newHeight = 9;
1969               gameInfo.holdingsSize = 7;
1970             case VariantBughouse:
1971             case VariantCrazyhouse:
1972               newHoldingsWidth = 2; break;
1973             default:
1974               newHoldingsWidth = gameInfo.holdingsSize = 0;
1975     }
1976
1977     if(newWidth  != gameInfo.boardWidth  ||
1978        newHeight != gameInfo.boardHeight ||
1979        newHoldingsWidth != gameInfo.holdingsWidth ) {
1980
1981         /* shift position to new playing area, if needed */
1982         if(newHoldingsWidth > gameInfo.holdingsWidth) {
1983            for(i=0; i<BOARD_HEIGHT; i++) 
1984                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1985                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1986                                                      board[i][j];
1987            for(i=0; i<newHeight; i++) {
1988                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1989                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1990            }
1991         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1992            for(i=0; i<BOARD_HEIGHT; i++)
1993                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1994                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1995                                                  board[i][j];
1996         }
1997
1998         gameInfo.boardWidth  = newWidth;
1999         gameInfo.boardHeight = newHeight;
2000         gameInfo.holdingsWidth = newHoldingsWidth;
2001         gameInfo.variant = newVariant;
2002         InitDrawingSizes(-2, 0);
2003
2004         /* [HGM] The following should definitely be solved in a better way */
2005         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2006     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2007
2008     forwardMostMove = oldForwardMostMove;
2009     backwardMostMove = oldBackwardMostMove;
2010     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2011 }
2012
2013 static int loggedOn = FALSE;
2014
2015 /*-- Game start info cache: --*/
2016 int gs_gamenum;
2017 char gs_kind[MSG_SIZ];
2018 static char player1Name[128] = "";
2019 static char player2Name[128] = "";
2020 static int player1Rating = -1;
2021 static int player2Rating = -1;
2022 /*----------------------------*/
2023
2024 ColorClass curColor = ColorNormal;
2025 int suppressKibitz = 0;
2026
2027 void
2028 read_from_ics(isr, closure, data, count, error)
2029      InputSourceRef isr;
2030      VOIDSTAR closure;
2031      char *data;
2032      int count;
2033      int error;
2034 {
2035 #define BUF_SIZE 8192
2036 #define STARTED_NONE 0
2037 #define STARTED_MOVES 1
2038 #define STARTED_BOARD 2
2039 #define STARTED_OBSERVE 3
2040 #define STARTED_HOLDINGS 4
2041 #define STARTED_CHATTER 5
2042 #define STARTED_COMMENT 6
2043 #define STARTED_MOVES_NOHIDE 7
2044     
2045     static int started = STARTED_NONE;
2046     static char parse[20000];
2047     static int parse_pos = 0;
2048     static char buf[BUF_SIZE + 1];
2049     static int firstTime = TRUE, intfSet = FALSE;
2050     static ColorClass prevColor = ColorNormal;
2051     static int savingComment = FALSE;
2052     char str[500];
2053     int i, oldi;
2054     int buf_len;
2055     int next_out;
2056     int tkind;
2057     int backup;    /* [DM] For zippy color lines */
2058     char *p;
2059     char talker[MSG_SIZ]; // [HGM] chat
2060     int channel;
2061
2062     if (appData.debugMode) {
2063       if (!error) {
2064         fprintf(debugFP, "<ICS: ");
2065         show_bytes(debugFP, data, count);
2066         fprintf(debugFP, "\n");
2067       }
2068     }
2069
2070     if (appData.debugMode) { int f = forwardMostMove;
2071         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2072                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2073     }
2074     if (count > 0) {
2075         /* If last read ended with a partial line that we couldn't parse,
2076            prepend it to the new read and try again. */
2077         if (leftover_len > 0) {
2078             for (i=0; i<leftover_len; i++)
2079               buf[i] = buf[leftover_start + i];
2080         }
2081
2082         /* Copy in new characters, removing nulls and \r's */
2083         buf_len = leftover_len;
2084         for (i = 0; i < count; i++) {
2085             if (data[i] != NULLCHAR && data[i] != '\r')
2086               buf[buf_len++] = data[i];
2087             if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2088                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2089                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2090                 if(buf_len == 0 || buf[buf_len-1] != ' ')
2091                    buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2092             }
2093         }
2094
2095         buf[buf_len] = NULLCHAR;
2096         next_out = leftover_len;
2097         leftover_start = 0;
2098         
2099         i = 0;
2100         while (i < buf_len) {
2101             /* Deal with part of the TELNET option negotiation
2102                protocol.  We refuse to do anything beyond the
2103                defaults, except that we allow the WILL ECHO option,
2104                which ICS uses to turn off password echoing when we are
2105                directly connected to it.  We reject this option
2106                if localLineEditing mode is on (always on in xboard)
2107                and we are talking to port 23, which might be a real
2108                telnet server that will try to keep WILL ECHO on permanently.
2109              */
2110             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2111                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2112                 unsigned char option;
2113                 oldi = i;
2114                 switch ((unsigned char) buf[++i]) {
2115                   case TN_WILL:
2116                     if (appData.debugMode)
2117                       fprintf(debugFP, "\n<WILL ");
2118                     switch (option = (unsigned char) buf[++i]) {
2119                       case TN_ECHO:
2120                         if (appData.debugMode)
2121                           fprintf(debugFP, "ECHO ");
2122                         /* Reply only if this is a change, according
2123                            to the protocol rules. */
2124                         if (remoteEchoOption) break;
2125                         if (appData.localLineEditing &&
2126                             atoi(appData.icsPort) == TN_PORT) {
2127                             TelnetRequest(TN_DONT, TN_ECHO);
2128                         } else {
2129                             EchoOff();
2130                             TelnetRequest(TN_DO, TN_ECHO);
2131                             remoteEchoOption = TRUE;
2132                         }
2133                         break;
2134                       default:
2135                         if (appData.debugMode)
2136                           fprintf(debugFP, "%d ", option);
2137                         /* Whatever this is, we don't want it. */
2138                         TelnetRequest(TN_DONT, option);
2139                         break;
2140                     }
2141                     break;
2142                   case TN_WONT:
2143                     if (appData.debugMode)
2144                       fprintf(debugFP, "\n<WONT ");
2145                     switch (option = (unsigned char) buf[++i]) {
2146                       case TN_ECHO:
2147                         if (appData.debugMode)
2148                           fprintf(debugFP, "ECHO ");
2149                         /* Reply only if this is a change, according
2150                            to the protocol rules. */
2151                         if (!remoteEchoOption) break;
2152                         EchoOn();
2153                         TelnetRequest(TN_DONT, TN_ECHO);
2154                         remoteEchoOption = FALSE;
2155                         break;
2156                       default:
2157                         if (appData.debugMode)
2158                           fprintf(debugFP, "%d ", (unsigned char) option);
2159                         /* Whatever this is, it must already be turned
2160                            off, because we never agree to turn on
2161                            anything non-default, so according to the
2162                            protocol rules, we don't reply. */
2163                         break;
2164                     }
2165                     break;
2166                   case TN_DO:
2167                     if (appData.debugMode)
2168                       fprintf(debugFP, "\n<DO ");
2169                     switch (option = (unsigned char) buf[++i]) {
2170                       default:
2171                         /* Whatever this is, we refuse to do it. */
2172                         if (appData.debugMode)
2173                           fprintf(debugFP, "%d ", option);
2174                         TelnetRequest(TN_WONT, option);
2175                         break;
2176                     }
2177                     break;
2178                   case TN_DONT:
2179                     if (appData.debugMode)
2180                       fprintf(debugFP, "\n<DONT ");
2181                     switch (option = (unsigned char) buf[++i]) {
2182                       default:
2183                         if (appData.debugMode)
2184                           fprintf(debugFP, "%d ", option);
2185                         /* Whatever this is, we are already not doing
2186                            it, because we never agree to do anything
2187                            non-default, so according to the protocol
2188                            rules, we don't reply. */
2189                         break;
2190                     }
2191                     break;
2192                   case TN_IAC:
2193                     if (appData.debugMode)
2194                       fprintf(debugFP, "\n<IAC ");
2195                     /* Doubled IAC; pass it through */
2196                     i--;
2197                     break;
2198                   default:
2199                     if (appData.debugMode)
2200                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2201                     /* Drop all other telnet commands on the floor */
2202                     break;
2203                 }
2204                 if (oldi > next_out)
2205                   SendToPlayer(&buf[next_out], oldi - next_out);
2206                 if (++i > next_out)
2207                   next_out = i;
2208                 continue;
2209             }
2210                 
2211             /* OK, this at least will *usually* work */
2212             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2213                 loggedOn = TRUE;
2214             }
2215             
2216             if (loggedOn && !intfSet) {
2217                 if (ics_type == ICS_ICC) {
2218                   sprintf(str,
2219                           "/set-quietly interface %s\n/set-quietly style 12\n",
2220                           programVersion);
2221           if (!appData.noJoin)
2222               strcat(str, "/set-quietly wrap 0\n");
2223                 } else if (ics_type == ICS_CHESSNET) {
2224                   sprintf(str, "/style 12\n");
2225                 } else {
2226                   strcpy(str, "alias $ @\n$set interface ");
2227                   strcat(str, programVersion);
2228                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2229 #ifdef WIN32
2230                   strcat(str, "$iset nohighlight 1\n");
2231 #endif
2232           if (!appData.noJoin)
2233               strcat(str, "$iset nowrap 1\n");
2234                   strcat(str, "$iset lock 1\n$style 12\n");
2235                 }
2236                 SendToICS(str);
2237                 NotifyFrontendLogin();
2238                 intfSet = TRUE;
2239             }
2240
2241             if (started == STARTED_COMMENT) {
2242                 /* Accumulate characters in comment */
2243                 parse[parse_pos++] = buf[i];
2244                 if (buf[i] == '\n') {
2245                     parse[parse_pos] = NULLCHAR;
2246                     if(chattingPartner>=0) {
2247                         char mess[MSG_SIZ];
2248                         sprintf(mess, "%s%s", talker, parse);
2249                         OutputChatMessage(chattingPartner, mess);
2250                         chattingPartner = -1;
2251                     } else
2252                     if(!suppressKibitz) // [HGM] kibitz
2253                         AppendComment(forwardMostMove, StripHighlight(parse));
2254                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2255                         int nrDigit = 0, nrAlph = 0, i;
2256                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2257                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2258                         parse[parse_pos] = NULLCHAR;
2259                         // try to be smart: if it does not look like search info, it should go to
2260                         // ICS interaction window after all, not to engine-output window.
2261                         for(i=0; i<parse_pos; i++) { // count letters and digits
2262                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2263                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2264                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2265                         }
2266                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2267                             int depth=0; float score;
2268                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2269                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2270                                 pvInfoList[forwardMostMove-1].depth = depth;
2271                                 pvInfoList[forwardMostMove-1].score = 100*score;
2272                             }
2273                             OutputKibitz(suppressKibitz, parse);
2274                         } else {
2275                             char tmp[MSG_SIZ];
2276                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2277                             SendToPlayer(tmp, strlen(tmp));
2278                         }
2279                     }
2280                     started = STARTED_NONE;
2281                 } else {
2282                     /* Don't match patterns against characters in chatter */
2283                     i++;
2284                     continue;
2285                 }
2286             }
2287             if (started == STARTED_CHATTER) {
2288                 if (buf[i] != '\n') {
2289                     /* Don't match patterns against characters in chatter */
2290                     i++;
2291                     continue;
2292                 }
2293                 started = STARTED_NONE;
2294             }
2295
2296             /* Kludge to deal with rcmd protocol */
2297             if (firstTime && looking_at(buf, &i, "\001*")) {
2298                 DisplayFatalError(&buf[1], 0, 1);
2299                 continue;
2300             } else {
2301                 firstTime = FALSE;
2302             }
2303
2304             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2305                 ics_type = ICS_ICC;
2306                 ics_prefix = "/";
2307                 if (appData.debugMode)
2308                   fprintf(debugFP, "ics_type %d\n", ics_type);
2309                 continue;
2310             }
2311             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2312                 ics_type = ICS_FICS;
2313                 ics_prefix = "$";
2314                 if (appData.debugMode)
2315                   fprintf(debugFP, "ics_type %d\n", ics_type);
2316                 continue;
2317             }
2318             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2319                 ics_type = ICS_CHESSNET;
2320                 ics_prefix = "/";
2321                 if (appData.debugMode)
2322                   fprintf(debugFP, "ics_type %d\n", ics_type);
2323                 continue;
2324             }
2325
2326             if (!loggedOn &&
2327                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2328                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2329                  looking_at(buf, &i, "will be \"*\""))) {
2330               strcpy(ics_handle, star_match[0]);
2331               continue;
2332             }
2333
2334             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2335               char buf[MSG_SIZ];
2336               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2337               DisplayIcsInteractionTitle(buf);
2338               have_set_title = TRUE;
2339             }
2340
2341             /* skip finger notes */
2342             if (started == STARTED_NONE &&
2343                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2344                  (buf[i] == '1' && buf[i+1] == '0')) &&
2345                 buf[i+2] == ':' && buf[i+3] == ' ') {
2346               started = STARTED_CHATTER;
2347               i += 3;
2348               continue;
2349             }
2350
2351             /* skip formula vars */
2352             if (started == STARTED_NONE &&
2353                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2354               started = STARTED_CHATTER;
2355               i += 3;
2356               continue;
2357             }
2358
2359             oldi = i;
2360             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2361             if (appData.autoKibitz && started == STARTED_NONE && 
2362                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2363                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2364                 if(looking_at(buf, &i, "* kibitzes: ") &&
2365                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2366                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2367                         suppressKibitz = TRUE;
2368                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2369                                 && (gameMode == IcsPlayingWhite)) ||
2370                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2371                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2372                             started = STARTED_CHATTER; // own kibitz we simply discard
2373                         else {
2374                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2375                             parse_pos = 0; parse[0] = NULLCHAR;
2376                             savingComment = TRUE;
2377                             suppressKibitz = gameMode != IcsObserving ? 2 :
2378                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2379                         } 
2380                         continue;
2381                 } else
2382                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2383                     started = STARTED_CHATTER;
2384                     suppressKibitz = TRUE;
2385                 }
2386             } // [HGM] kibitz: end of patch
2387
2388 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2389
2390             // [HGM] chat: intercept tells by users for which we have an open chat window
2391             channel = -1;
2392             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2393                                            looking_at(buf, &i, "* whispers:") ||
2394                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2395                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2396                 int p;
2397                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2398                 chattingPartner = -1;
2399
2400                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2401                 for(p=0; p<MAX_CHAT; p++) {
2402                     if(channel == atoi(chatPartner[p])) {
2403                     talker[0] = '['; strcat(talker, "]");
2404                     chattingPartner = p; break;
2405                     }
2406                 } else
2407                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2408                 for(p=0; p<MAX_CHAT; p++) {
2409                     if(!strcmp("WHISPER", chatPartner[p])) {
2410                         talker[0] = '['; strcat(talker, "]");
2411                         chattingPartner = p; break;
2412                     }
2413                 }
2414                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2415                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2416                     talker[0] = 0;
2417                     chattingPartner = p; break;
2418                 }
2419                 if(chattingPartner<0) i = oldi; else {
2420                     started = STARTED_COMMENT;
2421                     parse_pos = 0; parse[0] = NULLCHAR;
2422                     savingComment = TRUE;
2423                     suppressKibitz = TRUE;
2424                 }
2425             } // [HGM] chat: end of patch
2426
2427             if (appData.zippyTalk || appData.zippyPlay) {
2428                 /* [DM] Backup address for color zippy lines */
2429                 backup = i;
2430 #if ZIPPY
2431        #ifdef WIN32
2432                if (loggedOn == TRUE)
2433                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2434                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2435        #else
2436                 if (ZippyControl(buf, &i) ||
2437                     ZippyConverse(buf, &i) ||
2438                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2439                       loggedOn = TRUE;
2440                       if (!appData.colorize) continue;
2441                 }
2442        #endif
2443 #endif
2444             } // [DM] 'else { ' deleted
2445                 if (
2446                     /* Regular tells and says */
2447                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2448                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2449                     looking_at(buf, &i, "* says: ") ||
2450                     /* Don't color "message" or "messages" output */
2451                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2452                     looking_at(buf, &i, "*. * at *:*: ") ||
2453                     looking_at(buf, &i, "--* (*:*): ") ||
2454                     /* Message notifications (same color as tells) */
2455                     looking_at(buf, &i, "* has left a message ") ||
2456                     looking_at(buf, &i, "* just sent you a message:\n") ||
2457                     /* Whispers and kibitzes */
2458                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2459                     looking_at(buf, &i, "* kibitzes: ") ||
2460                     /* Channel tells */
2461                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2462
2463                   if (tkind == 1 && strchr(star_match[0], ':')) {
2464                       /* Avoid "tells you:" spoofs in channels */
2465                      tkind = 3;
2466                   }
2467                   if (star_match[0][0] == NULLCHAR ||
2468                       strchr(star_match[0], ' ') ||
2469                       (tkind == 3 && strchr(star_match[1], ' '))) {
2470                     /* Reject bogus matches */
2471                     i = oldi;
2472                   } else {
2473                     if (appData.colorize) {
2474                       if (oldi > next_out) {
2475                         SendToPlayer(&buf[next_out], oldi - next_out);
2476                         next_out = oldi;
2477                       }
2478                       switch (tkind) {
2479                       case 1:
2480                         Colorize(ColorTell, FALSE);
2481                         curColor = ColorTell;
2482                         break;
2483                       case 2:
2484                         Colorize(ColorKibitz, FALSE);
2485                         curColor = ColorKibitz;
2486                         break;
2487                       case 3:
2488                         p = strrchr(star_match[1], '(');
2489                         if (p == NULL) {
2490                           p = star_match[1];
2491                         } else {
2492                           p++;
2493                         }
2494                         if (atoi(p) == 1) {
2495                           Colorize(ColorChannel1, FALSE);
2496                           curColor = ColorChannel1;
2497                         } else {
2498                           Colorize(ColorChannel, FALSE);
2499                           curColor = ColorChannel;
2500                         }
2501                         break;
2502                       case 5:
2503                         curColor = ColorNormal;
2504                         break;
2505                       }
2506                     }
2507                     if (started == STARTED_NONE && appData.autoComment &&
2508                         (gameMode == IcsObserving ||
2509                          gameMode == IcsPlayingWhite ||
2510                          gameMode == IcsPlayingBlack)) {
2511                       parse_pos = i - oldi;
2512                       memcpy(parse, &buf[oldi], parse_pos);
2513                       parse[parse_pos] = NULLCHAR;
2514                       started = STARTED_COMMENT;
2515                       savingComment = TRUE;
2516                     } else {
2517                       started = STARTED_CHATTER;
2518                       savingComment = FALSE;
2519                     }
2520                     loggedOn = TRUE;
2521                     continue;
2522                   }
2523                 }
2524
2525                 if (looking_at(buf, &i, "* s-shouts: ") ||
2526                     looking_at(buf, &i, "* c-shouts: ")) {
2527                     if (appData.colorize) {
2528                         if (oldi > next_out) {
2529                             SendToPlayer(&buf[next_out], oldi - next_out);
2530                             next_out = oldi;
2531                         }
2532                         Colorize(ColorSShout, FALSE);
2533                         curColor = ColorSShout;
2534                     }
2535                     loggedOn = TRUE;
2536                     started = STARTED_CHATTER;
2537                     continue;
2538                 }
2539
2540                 if (looking_at(buf, &i, "--->")) {
2541                     loggedOn = TRUE;
2542                     continue;
2543                 }
2544
2545                 if (looking_at(buf, &i, "* shouts: ") ||
2546                     looking_at(buf, &i, "--> ")) {
2547                     if (appData.colorize) {
2548                         if (oldi > next_out) {
2549                             SendToPlayer(&buf[next_out], oldi - next_out);
2550                             next_out = oldi;
2551                         }
2552                         Colorize(ColorShout, FALSE);
2553                         curColor = ColorShout;
2554                     }
2555                     loggedOn = TRUE;
2556                     started = STARTED_CHATTER;
2557                     continue;
2558                 }
2559
2560                 if (looking_at( buf, &i, "Challenge:")) {
2561                     if (appData.colorize) {
2562                         if (oldi > next_out) {
2563                             SendToPlayer(&buf[next_out], oldi - next_out);
2564                             next_out = oldi;
2565                         }
2566                         Colorize(ColorChallenge, FALSE);
2567                         curColor = ColorChallenge;
2568                     }
2569                     loggedOn = TRUE;
2570                     continue;
2571                 }
2572
2573                 if (looking_at(buf, &i, "* offers you") ||
2574                     looking_at(buf, &i, "* offers to be") ||
2575                     looking_at(buf, &i, "* would like to") ||
2576                     looking_at(buf, &i, "* requests to") ||
2577                     looking_at(buf, &i, "Your opponent offers") ||
2578                     looking_at(buf, &i, "Your opponent requests")) {
2579
2580                     if (appData.colorize) {
2581                         if (oldi > next_out) {
2582                             SendToPlayer(&buf[next_out], oldi - next_out);
2583                             next_out = oldi;
2584                         }
2585                         Colorize(ColorRequest, FALSE);
2586                         curColor = ColorRequest;
2587                     }
2588                     continue;
2589                 }
2590
2591                 if (looking_at(buf, &i, "* (*) seeking")) {
2592                     if (appData.colorize) {
2593                         if (oldi > next_out) {
2594                             SendToPlayer(&buf[next_out], oldi - next_out);
2595                             next_out = oldi;
2596                         }
2597                         Colorize(ColorSeek, FALSE);
2598                         curColor = ColorSeek;
2599                     }
2600                     continue;
2601             }
2602
2603             if (looking_at(buf, &i, "\\   ")) {
2604                 if (prevColor != ColorNormal) {
2605                     if (oldi > next_out) {
2606                         SendToPlayer(&buf[next_out], oldi - next_out);
2607                         next_out = oldi;
2608                     }
2609                     Colorize(prevColor, TRUE);
2610                     curColor = prevColor;
2611                 }
2612                 if (savingComment) {
2613                     parse_pos = i - oldi;
2614                     memcpy(parse, &buf[oldi], parse_pos);
2615                     parse[parse_pos] = NULLCHAR;
2616                     started = STARTED_COMMENT;
2617                 } else {
2618                     started = STARTED_CHATTER;
2619                 }
2620                 continue;
2621             }
2622
2623             if (looking_at(buf, &i, "Black Strength :") ||
2624                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2625                 looking_at(buf, &i, "<10>") ||
2626                 looking_at(buf, &i, "#@#")) {
2627                 /* Wrong board style */
2628                 loggedOn = TRUE;
2629                 SendToICS(ics_prefix);
2630                 SendToICS("set style 12\n");
2631                 SendToICS(ics_prefix);
2632                 SendToICS("refresh\n");
2633                 continue;
2634             }
2635             
2636             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2637                 ICSInitScript();
2638                 have_sent_ICS_logon = 1;
2639                 continue;
2640             }
2641               
2642             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2643                 (looking_at(buf, &i, "\n<12> ") ||
2644                  looking_at(buf, &i, "<12> "))) {
2645                 loggedOn = TRUE;
2646                 if (oldi > next_out) {
2647                     SendToPlayer(&buf[next_out], oldi - next_out);
2648                 }
2649                 next_out = i;
2650                 started = STARTED_BOARD;
2651                 parse_pos = 0;
2652                 continue;
2653             }
2654
2655             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2656                 looking_at(buf, &i, "<b1> ")) {
2657                 if (oldi > next_out) {
2658                     SendToPlayer(&buf[next_out], oldi - next_out);
2659                 }
2660                 next_out = i;
2661                 started = STARTED_HOLDINGS;
2662                 parse_pos = 0;
2663                 continue;
2664             }
2665
2666             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2667                 loggedOn = TRUE;
2668                 /* Header for a move list -- first line */
2669
2670                 switch (ics_getting_history) {
2671                   case H_FALSE:
2672                     switch (gameMode) {
2673                       case IcsIdle:
2674                       case BeginningOfGame:
2675                         /* User typed "moves" or "oldmoves" while we
2676                            were idle.  Pretend we asked for these
2677                            moves and soak them up so user can step
2678                            through them and/or save them.
2679                            */
2680                         Reset(FALSE, TRUE);
2681                         gameMode = IcsObserving;
2682                         ModeHighlight();
2683                         ics_gamenum = -1;
2684                         ics_getting_history = H_GOT_UNREQ_HEADER;
2685                         break;
2686                       case EditGame: /*?*/
2687                       case EditPosition: /*?*/
2688                         /* Should above feature work in these modes too? */
2689                         /* For now it doesn't */
2690                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2691                         break;
2692                       default:
2693                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2694                         break;
2695                     }
2696                     break;
2697                   case H_REQUESTED:
2698                     /* Is this the right one? */
2699                     if (gameInfo.white && gameInfo.black &&
2700                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2701                         strcmp(gameInfo.black, star_match[2]) == 0) {
2702                         /* All is well */
2703                         ics_getting_history = H_GOT_REQ_HEADER;
2704                     }
2705                     break;
2706                   case H_GOT_REQ_HEADER:
2707                   case H_GOT_UNREQ_HEADER:
2708                   case H_GOT_UNWANTED_HEADER:
2709                   case H_GETTING_MOVES:
2710                     /* Should not happen */
2711                     DisplayError(_("Error gathering move list: two headers"), 0);
2712                     ics_getting_history = H_FALSE;
2713                     break;
2714                 }
2715
2716                 /* Save player ratings into gameInfo if needed */
2717                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2718                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2719                     (gameInfo.whiteRating == -1 ||
2720                      gameInfo.blackRating == -1)) {
2721
2722                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2723                     gameInfo.blackRating = string_to_rating(star_match[3]);
2724                     if (appData.debugMode)
2725                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2726                               gameInfo.whiteRating, gameInfo.blackRating);
2727                 }
2728                 continue;
2729             }
2730
2731             if (looking_at(buf, &i,
2732               "* * match, initial time: * minute*, increment: * second")) {
2733                 /* Header for a move list -- second line */
2734                 /* Initial board will follow if this is a wild game */
2735                 if (gameInfo.event != NULL) free(gameInfo.event);
2736                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2737                 gameInfo.event = StrSave(str);
2738                 /* [HGM] we switched variant. Translate boards if needed. */
2739                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2740                 continue;
2741             }
2742
2743             if (looking_at(buf, &i, "Move  ")) {
2744                 /* Beginning of a move list */
2745                 switch (ics_getting_history) {
2746                   case H_FALSE:
2747                     /* Normally should not happen */
2748                     /* Maybe user hit reset while we were parsing */
2749                     break;
2750                   case H_REQUESTED:
2751                     /* Happens if we are ignoring a move list that is not
2752                      * the one we just requested.  Common if the user
2753                      * tries to observe two games without turning off
2754                      * getMoveList */
2755                     break;
2756                   case H_GETTING_MOVES:
2757                     /* Should not happen */
2758                     DisplayError(_("Error gathering move list: nested"), 0);
2759                     ics_getting_history = H_FALSE;
2760                     break;
2761                   case H_GOT_REQ_HEADER:
2762                     ics_getting_history = H_GETTING_MOVES;
2763                     started = STARTED_MOVES;
2764                     parse_pos = 0;
2765                     if (oldi > next_out) {
2766                         SendToPlayer(&buf[next_out], oldi - next_out);
2767                     }
2768                     break;
2769                   case H_GOT_UNREQ_HEADER:
2770                     ics_getting_history = H_GETTING_MOVES;
2771                     started = STARTED_MOVES_NOHIDE;
2772                     parse_pos = 0;
2773                     break;
2774                   case H_GOT_UNWANTED_HEADER:
2775                     ics_getting_history = H_FALSE;
2776                     break;
2777                 }
2778                 continue;
2779             }                           
2780             
2781             if (looking_at(buf, &i, "% ") ||
2782                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2783                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2784                 savingComment = FALSE;
2785                 switch (started) {
2786                   case STARTED_MOVES:
2787                   case STARTED_MOVES_NOHIDE:
2788                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2789                     parse[parse_pos + i - oldi] = NULLCHAR;
2790                     ParseGameHistory(parse);
2791 #if ZIPPY
2792                     if (appData.zippyPlay && first.initDone) {
2793                         FeedMovesToProgram(&first, forwardMostMove);
2794                         if (gameMode == IcsPlayingWhite) {
2795                             if (WhiteOnMove(forwardMostMove)) {
2796                                 if (first.sendTime) {
2797                                   if (first.useColors) {
2798                                     SendToProgram("black\n", &first); 
2799                                   }
2800                                   SendTimeRemaining(&first, TRUE);
2801                                 }
2802                                 if (first.useColors) {
2803                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2804                                 }
2805                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2806                                 first.maybeThinking = TRUE;
2807                             } else {
2808                                 if (first.usePlayother) {
2809                                   if (first.sendTime) {
2810                                     SendTimeRemaining(&first, TRUE);
2811                                   }
2812                                   SendToProgram("playother\n", &first);
2813                                   firstMove = FALSE;
2814                                 } else {
2815                                   firstMove = TRUE;
2816                                 }
2817                             }
2818                         } else if (gameMode == IcsPlayingBlack) {
2819                             if (!WhiteOnMove(forwardMostMove)) {
2820                                 if (first.sendTime) {
2821                                   if (first.useColors) {
2822                                     SendToProgram("white\n", &first);
2823                                   }
2824                                   SendTimeRemaining(&first, FALSE);
2825                                 }
2826                                 if (first.useColors) {
2827                                   SendToProgram("black\n", &first);
2828                                 }
2829                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2830                                 first.maybeThinking = TRUE;
2831                             } else {
2832                                 if (first.usePlayother) {
2833                                   if (first.sendTime) {
2834                                     SendTimeRemaining(&first, FALSE);
2835                                   }
2836                                   SendToProgram("playother\n", &first);
2837                                   firstMove = FALSE;
2838                                 } else {
2839                                   firstMove = TRUE;
2840                                 }
2841                             }
2842                         }                       
2843                     }
2844 #endif
2845                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2846                         /* Moves came from oldmoves or moves command
2847                            while we weren't doing anything else.
2848                            */
2849                         currentMove = forwardMostMove;
2850                         ClearHighlights();/*!!could figure this out*/
2851                         flipView = appData.flipView;
2852                         DrawPosition(FALSE, boards[currentMove]);
2853                         DisplayBothClocks();
2854                         sprintf(str, "%s vs. %s",
2855                                 gameInfo.white, gameInfo.black);
2856                         DisplayTitle(str);
2857                         gameMode = IcsIdle;
2858                     } else {
2859                         /* Moves were history of an active game */
2860                         if (gameInfo.resultDetails != NULL) {
2861                             free(gameInfo.resultDetails);
2862                             gameInfo.resultDetails = NULL;
2863                         }
2864                     }
2865                     HistorySet(parseList, backwardMostMove,
2866                                forwardMostMove, currentMove-1);
2867                     DisplayMove(currentMove - 1);
2868                     if (started == STARTED_MOVES) next_out = i;
2869                     started = STARTED_NONE;
2870                     ics_getting_history = H_FALSE;
2871                     break;
2872
2873                   case STARTED_OBSERVE:
2874                     started = STARTED_NONE;
2875                     SendToICS(ics_prefix);
2876                     SendToICS("refresh\n");
2877                     break;
2878
2879                   default:
2880                     break;
2881                 }
2882                 if(bookHit) { // [HGM] book: simulate book reply
2883                     static char bookMove[MSG_SIZ]; // a bit generous?
2884
2885                     programStats.nodes = programStats.depth = programStats.time = 
2886                     programStats.score = programStats.got_only_move = 0;
2887                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2888
2889                     strcpy(bookMove, "move ");
2890                     strcat(bookMove, bookHit);
2891                     HandleMachineMove(bookMove, &first);
2892                 }
2893                 continue;
2894             }
2895             
2896             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2897                  started == STARTED_HOLDINGS ||
2898                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2899                 /* Accumulate characters in move list or board */
2900                 parse[parse_pos++] = buf[i];
2901             }
2902             
2903             /* Start of game messages.  Mostly we detect start of game
2904                when the first board image arrives.  On some versions
2905                of the ICS, though, we need to do a "refresh" after starting
2906                to observe in order to get the current board right away. */
2907             if (looking_at(buf, &i, "Adding game * to observation list")) {
2908                 started = STARTED_OBSERVE;
2909                 continue;
2910             }
2911
2912             /* Handle auto-observe */
2913             if (appData.autoObserve &&
2914                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2915                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2916                 char *player;
2917                 /* Choose the player that was highlighted, if any. */
2918                 if (star_match[0][0] == '\033' ||
2919                     star_match[1][0] != '\033') {
2920                     player = star_match[0];
2921                 } else {
2922                     player = star_match[2];
2923                 }
2924                 sprintf(str, "%sobserve %s\n",
2925                         ics_prefix, StripHighlightAndTitle(player));
2926                 SendToICS(str);
2927
2928                 /* Save ratings from notify string */
2929                 strcpy(player1Name, star_match[0]);
2930                 player1Rating = string_to_rating(star_match[1]);
2931                 strcpy(player2Name, star_match[2]);
2932                 player2Rating = string_to_rating(star_match[3]);
2933
2934                 if (appData.debugMode)
2935                   fprintf(debugFP, 
2936                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2937                           player1Name, player1Rating,
2938                           player2Name, player2Rating);
2939
2940                 continue;
2941             }
2942
2943             /* Deal with automatic examine mode after a game,
2944                and with IcsObserving -> IcsExamining transition */
2945             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2946                 looking_at(buf, &i, "has made you an examiner of game *")) {
2947
2948                 int gamenum = atoi(star_match[0]);
2949                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2950                     gamenum == ics_gamenum) {
2951                     /* We were already playing or observing this game;
2952                        no need to refetch history */
2953                     gameMode = IcsExamining;
2954                     if (pausing) {
2955                         pauseExamForwardMostMove = forwardMostMove;
2956                     } else if (currentMove < forwardMostMove) {
2957                         ForwardInner(forwardMostMove);
2958                     }
2959                 } else {
2960                     /* I don't think this case really can happen */
2961                     SendToICS(ics_prefix);
2962                     SendToICS("refresh\n");
2963                 }
2964                 continue;
2965             }    
2966             
2967             /* Error messages */
2968 //          if (ics_user_moved) {
2969             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2970                 if (looking_at(buf, &i, "Illegal move") ||
2971                     looking_at(buf, &i, "Not a legal move") ||
2972                     looking_at(buf, &i, "Your king is in check") ||
2973                     looking_at(buf, &i, "It isn't your turn") ||
2974                     looking_at(buf, &i, "It is not your move")) {
2975                     /* Illegal move */
2976                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2977                         currentMove = --forwardMostMove;
2978                         DisplayMove(currentMove - 1); /* before DMError */
2979                         DrawPosition(FALSE, boards[currentMove]);
2980                         SwitchClocks();
2981                         DisplayBothClocks();
2982                     }
2983                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2984                     ics_user_moved = 0;
2985                     continue;
2986                 }
2987             }
2988
2989             if (looking_at(buf, &i, "still have time") ||
2990                 looking_at(buf, &i, "not out of time") ||
2991                 looking_at(buf, &i, "either player is out of time") ||
2992                 looking_at(buf, &i, "has timeseal; checking")) {
2993                 /* We must have called his flag a little too soon */
2994                 whiteFlag = blackFlag = FALSE;
2995                 continue;
2996             }
2997
2998             if (looking_at(buf, &i, "added * seconds to") ||
2999                 looking_at(buf, &i, "seconds were added to")) {
3000                 /* Update the clocks */
3001                 SendToICS(ics_prefix);
3002                 SendToICS("refresh\n");
3003                 continue;
3004             }
3005
3006             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3007                 ics_clock_paused = TRUE;
3008                 StopClocks();
3009                 continue;
3010             }
3011
3012             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3013                 ics_clock_paused = FALSE;
3014                 StartClocks();
3015                 continue;
3016             }
3017
3018             /* Grab player ratings from the Creating: message.
3019                Note we have to check for the special case when
3020                the ICS inserts things like [white] or [black]. */
3021             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3022                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3023                 /* star_matches:
3024                    0    player 1 name (not necessarily white)
3025                    1    player 1 rating
3026                    2    empty, white, or black (IGNORED)
3027                    3    player 2 name (not necessarily black)
3028                    4    player 2 rating
3029                    
3030                    The names/ratings are sorted out when the game
3031                    actually starts (below).
3032                 */
3033                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3034                 player1Rating = string_to_rating(star_match[1]);
3035                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3036                 player2Rating = string_to_rating(star_match[4]);
3037
3038                 if (appData.debugMode)
3039                   fprintf(debugFP, 
3040                           "Ratings from 'Creating:' %s %d, %s %d\n",
3041                           player1Name, player1Rating,
3042                           player2Name, player2Rating);
3043
3044                 continue;
3045             }
3046             
3047             /* Improved generic start/end-of-game messages */
3048             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3049                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3050                 /* If tkind == 0: */
3051                 /* star_match[0] is the game number */
3052                 /*           [1] is the white player's name */
3053                 /*           [2] is the black player's name */
3054                 /* For end-of-game: */
3055                 /*           [3] is the reason for the game end */
3056                 /*           [4] is a PGN end game-token, preceded by " " */
3057                 /* For start-of-game: */
3058                 /*           [3] begins with "Creating" or "Continuing" */
3059                 /*           [4] is " *" or empty (don't care). */
3060                 int gamenum = atoi(star_match[0]);
3061                 char *whitename, *blackname, *why, *endtoken;
3062                 ChessMove endtype = (ChessMove) 0;
3063
3064                 if (tkind == 0) {
3065                   whitename = star_match[1];
3066                   blackname = star_match[2];
3067                   why = star_match[3];
3068                   endtoken = star_match[4];
3069                 } else {
3070                   whitename = star_match[1];
3071                   blackname = star_match[3];
3072                   why = star_match[5];
3073                   endtoken = star_match[6];
3074                 }
3075
3076                 /* Game start messages */
3077                 if (strncmp(why, "Creating ", 9) == 0 ||
3078                     strncmp(why, "Continuing ", 11) == 0) {
3079                     gs_gamenum = gamenum;
3080                     strcpy(gs_kind, strchr(why, ' ') + 1);
3081 #if ZIPPY
3082                     if (appData.zippyPlay) {
3083                         ZippyGameStart(whitename, blackname);
3084                     }
3085 #endif /*ZIPPY*/
3086                     continue;
3087                 }
3088
3089                 /* Game end messages */
3090                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3091                     ics_gamenum != gamenum) {
3092                     continue;
3093                 }
3094                 while (endtoken[0] == ' ') endtoken++;
3095                 switch (endtoken[0]) {
3096                   case '*':
3097                   default:
3098                     endtype = GameUnfinished;
3099                     break;
3100                   case '0':
3101                     endtype = BlackWins;
3102                     break;
3103                   case '1':
3104                     if (endtoken[1] == '/')
3105                       endtype = GameIsDrawn;
3106                     else
3107                       endtype = WhiteWins;
3108                     break;
3109                 }
3110                 GameEnds(endtype, why, GE_ICS);
3111 #if ZIPPY
3112                 if (appData.zippyPlay && first.initDone) {
3113                     ZippyGameEnd(endtype, why);
3114                     if (first.pr == NULL) {
3115                       /* Start the next process early so that we'll
3116                          be ready for the next challenge */
3117                       StartChessProgram(&first);
3118                     }
3119                     /* Send "new" early, in case this command takes
3120                        a long time to finish, so that we'll be ready
3121                        for the next challenge. */
3122                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3123                     Reset(TRUE, TRUE);
3124                 }
3125 #endif /*ZIPPY*/
3126                 continue;
3127             }
3128
3129             if (looking_at(buf, &i, "Removing game * from observation") ||
3130                 looking_at(buf, &i, "no longer observing game *") ||
3131                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3132                 if (gameMode == IcsObserving &&
3133                     atoi(star_match[0]) == ics_gamenum)
3134                   {
3135                       /* icsEngineAnalyze */
3136                       if (appData.icsEngineAnalyze) {
3137                             ExitAnalyzeMode();
3138                             ModeHighlight();
3139                       }
3140                       StopClocks();
3141                       gameMode = IcsIdle;
3142                       ics_gamenum = -1;
3143                       ics_user_moved = FALSE;
3144                   }
3145                 continue;
3146             }
3147
3148             if (looking_at(buf, &i, "no longer examining game *")) {
3149                 if (gameMode == IcsExamining &&
3150                     atoi(star_match[0]) == ics_gamenum)
3151                   {
3152                       gameMode = IcsIdle;
3153                       ics_gamenum = -1;
3154                       ics_user_moved = FALSE;
3155                   }
3156                 continue;
3157             }
3158
3159             /* Advance leftover_start past any newlines we find,
3160                so only partial lines can get reparsed */
3161             if (looking_at(buf, &i, "\n")) {
3162                 prevColor = curColor;
3163                 if (curColor != ColorNormal) {
3164                     if (oldi > next_out) {
3165                         SendToPlayer(&buf[next_out], oldi - next_out);
3166                         next_out = oldi;
3167                     }
3168                     Colorize(ColorNormal, FALSE);
3169                     curColor = ColorNormal;
3170                 }
3171                 if (started == STARTED_BOARD) {
3172                     started = STARTED_NONE;
3173                     parse[parse_pos] = NULLCHAR;
3174                     ParseBoard12(parse);
3175                     ics_user_moved = 0;
3176
3177                     /* Send premove here */
3178                     if (appData.premove) {
3179                       char str[MSG_SIZ];
3180                       if (currentMove == 0 &&
3181                           gameMode == IcsPlayingWhite &&
3182                           appData.premoveWhite) {
3183                         sprintf(str, "%s%s\n", ics_prefix,
3184                                 appData.premoveWhiteText);
3185                         if (appData.debugMode)
3186                           fprintf(debugFP, "Sending premove:\n");
3187                         SendToICS(str);
3188                       } else if (currentMove == 1 &&
3189                                  gameMode == IcsPlayingBlack &&
3190                                  appData.premoveBlack) {
3191                         sprintf(str, "%s%s\n", ics_prefix,
3192                                 appData.premoveBlackText);
3193                         if (appData.debugMode)
3194                           fprintf(debugFP, "Sending premove:\n");
3195                         SendToICS(str);
3196                       } else if (gotPremove) {
3197                         gotPremove = 0;
3198                         ClearPremoveHighlights();
3199                         if (appData.debugMode)
3200                           fprintf(debugFP, "Sending premove:\n");
3201                           UserMoveEvent(premoveFromX, premoveFromY, 
3202                                         premoveToX, premoveToY, 
3203                                         premovePromoChar);
3204                       }
3205                     }
3206
3207                     /* Usually suppress following prompt */
3208                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3209                         if (looking_at(buf, &i, "*% ")) {
3210                             savingComment = FALSE;
3211                         }
3212                     }
3213                     next_out = i;
3214                 } else if (started == STARTED_HOLDINGS) {
3215                     int gamenum;
3216                     char new_piece[MSG_SIZ];
3217                     started = STARTED_NONE;
3218                     parse[parse_pos] = NULLCHAR;
3219                     if (appData.debugMode)
3220                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3221                                                         parse, currentMove);
3222                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3223                         gamenum == ics_gamenum) {
3224                         if (gameInfo.variant == VariantNormal) {
3225                           /* [HGM] We seem to switch variant during a game!
3226                            * Presumably no holdings were displayed, so we have
3227                            * to move the position two files to the right to
3228                            * create room for them!
3229                            */
3230                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3231                           /* Get a move list just to see the header, which
3232                              will tell us whether this is really bug or zh */
3233                           if (ics_getting_history == H_FALSE) {
3234                             ics_getting_history = H_REQUESTED;
3235                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3236                             SendToICS(str);
3237                           }
3238                         }
3239                         new_piece[0] = NULLCHAR;
3240                         sscanf(parse, "game %d white [%s black [%s <- %s",
3241                                &gamenum, white_holding, black_holding,
3242                                new_piece);
3243                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3244                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3245                         /* [HGM] copy holdings to board holdings area */
3246                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3247                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3248 #if ZIPPY
3249                         if (appData.zippyPlay && first.initDone) {
3250                             ZippyHoldings(white_holding, black_holding,
3251                                           new_piece);
3252                         }
3253 #endif /*ZIPPY*/
3254                         if (tinyLayout || smallLayout) {
3255                             char wh[16], bh[16];
3256                             PackHolding(wh, white_holding);
3257                             PackHolding(bh, black_holding);
3258                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3259                                     gameInfo.white, gameInfo.black);
3260                         } else {
3261                             sprintf(str, "%s [%s] vs. %s [%s]",
3262                                     gameInfo.white, white_holding,
3263                                     gameInfo.black, black_holding);
3264                         }
3265
3266                         DrawPosition(FALSE, boards[currentMove]);
3267                         DisplayTitle(str);
3268                     }
3269                     /* Suppress following prompt */
3270                     if (looking_at(buf, &i, "*% ")) {
3271                         savingComment = FALSE;
3272                     }
3273                     next_out = i;
3274                 }
3275                 continue;
3276             }
3277
3278             i++;                /* skip unparsed character and loop back */
3279         }
3280         
3281         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3282             started != STARTED_HOLDINGS && i > next_out) {
3283             SendToPlayer(&buf[next_out], i - next_out);
3284             next_out = i;
3285         }
3286         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3287         
3288         leftover_len = buf_len - leftover_start;
3289         /* if buffer ends with something we couldn't parse,
3290            reparse it after appending the next read */
3291         
3292     } else if (count == 0) {
3293         RemoveInputSource(isr);
3294         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3295     } else {
3296         DisplayFatalError(_("Error reading from ICS"), error, 1);
3297     }
3298 }
3299
3300
3301 /* Board style 12 looks like this:
3302    
3303    <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
3304    
3305  * The "<12> " is stripped before it gets to this routine.  The two
3306  * trailing 0's (flip state and clock ticking) are later addition, and
3307  * some chess servers may not have them, or may have only the first.
3308  * Additional trailing fields may be added in the future.  
3309  */
3310
3311 #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"
3312
3313 #define RELATION_OBSERVING_PLAYED    0
3314 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3315 #define RELATION_PLAYING_MYMOVE      1
3316 #define RELATION_PLAYING_NOTMYMOVE  -1
3317 #define RELATION_EXAMINING           2
3318 #define RELATION_ISOLATED_BOARD     -3
3319 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3320
3321 void
3322 ParseBoard12(string)
3323      char *string;
3324
3325     GameMode newGameMode;
3326     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3327     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3328     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3329     char to_play, board_chars[200];
3330     char move_str[500], str[500], elapsed_time[500];
3331     char black[32], white[32];
3332     Board board;
3333     int prevMove = currentMove;
3334     int ticking = 2;
3335     ChessMove moveType;
3336     int fromX, fromY, toX, toY;
3337     char promoChar;
3338     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3339     char *bookHit = NULL; // [HGM] book
3340
3341     fromX = fromY = toX = toY = -1;
3342     
3343     newGame = FALSE;
3344
3345     if (appData.debugMode)
3346       fprintf(debugFP, _("Parsing board: %s\n"), string);
3347
3348     move_str[0] = NULLCHAR;
3349     elapsed_time[0] = NULLCHAR;
3350     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3351         int  i = 0, j;
3352         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3353             if(string[i] == ' ') { ranks++; files = 0; }
3354             else files++;
3355             i++;
3356         }
3357         for(j = 0; j <i; j++) board_chars[j] = string[j];
3358         board_chars[i] = '\0';
3359         string += i + 1;
3360     }
3361     n = sscanf(string, PATTERN, &to_play, &double_push,
3362                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3363                &gamenum, white, black, &relation, &basetime, &increment,
3364                &white_stren, &black_stren, &white_time, &black_time,
3365                &moveNum, str, elapsed_time, move_str, &ics_flip,
3366                &ticking);
3367
3368     if (n < 21) {
3369         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3370         DisplayError(str, 0);
3371         return;
3372     }
3373
3374     /* Convert the move number to internal form */
3375     moveNum = (moveNum - 1) * 2;
3376     if (to_play == 'B') moveNum++;
3377     if (moveNum >= MAX_MOVES) {
3378       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3379                         0, 1);
3380       return;
3381     }
3382     
3383     switch (relation) {
3384       case RELATION_OBSERVING_PLAYED:
3385       case RELATION_OBSERVING_STATIC:
3386         if (gamenum == -1) {
3387             /* Old ICC buglet */
3388             relation = RELATION_OBSERVING_STATIC;
3389         }
3390         newGameMode = IcsObserving;
3391         break;
3392       case RELATION_PLAYING_MYMOVE:
3393       case RELATION_PLAYING_NOTMYMOVE:
3394         newGameMode =
3395           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3396             IcsPlayingWhite : IcsPlayingBlack;
3397         break;
3398       case RELATION_EXAMINING:
3399         newGameMode = IcsExamining;
3400         break;
3401       case RELATION_ISOLATED_BOARD:
3402       default:
3403         /* Just display this board.  If user was doing something else,
3404            we will forget about it until the next board comes. */ 
3405         newGameMode = IcsIdle;
3406         break;
3407       case RELATION_STARTING_POSITION:
3408         newGameMode = gameMode;
3409         break;
3410     }
3411     
3412     /* Modify behavior for initial board display on move listing
3413        of wild games.
3414        */
3415     switch (ics_getting_history) {
3416       case H_FALSE:
3417       case H_REQUESTED:
3418         break;
3419       case H_GOT_REQ_HEADER:
3420       case H_GOT_UNREQ_HEADER:
3421         /* This is the initial position of the current game */
3422         gamenum = ics_gamenum;
3423         moveNum = 0;            /* old ICS bug workaround */
3424         if (to_play == 'B') {
3425           startedFromSetupPosition = TRUE;
3426           blackPlaysFirst = TRUE;
3427           moveNum = 1;
3428           if (forwardMostMove == 0) forwardMostMove = 1;
3429           if (backwardMostMove == 0) backwardMostMove = 1;
3430           if (currentMove == 0) currentMove = 1;
3431         }
3432         newGameMode = gameMode;
3433         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3434         break;
3435       case H_GOT_UNWANTED_HEADER:
3436         /* This is an initial board that we don't want */
3437         return;
3438       case H_GETTING_MOVES:
3439         /* Should not happen */
3440         DisplayError(_("Error gathering move list: extra board"), 0);
3441         ics_getting_history = H_FALSE;
3442         return;
3443     }
3444     
3445     /* Take action if this is the first board of a new game, or of a
3446        different game than is currently being displayed.  */
3447     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3448         relation == RELATION_ISOLATED_BOARD) {
3449         
3450         /* Forget the old game and get the history (if any) of the new one */
3451         if (gameMode != BeginningOfGame) {
3452           Reset(FALSE, TRUE);
3453         }
3454         newGame = TRUE;
3455         if (appData.autoRaiseBoard) BoardToTop();
3456         prevMove = -3;
3457         if (gamenum == -1) {
3458             newGameMode = IcsIdle;
3459         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3460                    appData.getMoveList) {
3461             /* Need to get game history */
3462             ics_getting_history = H_REQUESTED;
3463             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3464             SendToICS(str);
3465         }
3466         
3467         /* Initially flip the board to have black on the bottom if playing
3468            black or if the ICS flip flag is set, but let the user change
3469            it with the Flip View button. */
3470         flipView = appData.autoFlipView ? 
3471           (newGameMode == IcsPlayingBlack) || ics_flip :
3472           appData.flipView;
3473         
3474         /* Done with values from previous mode; copy in new ones */
3475         gameMode = newGameMode;
3476         ModeHighlight();
3477         ics_gamenum = gamenum;
3478         if (gamenum == gs_gamenum) {
3479             int klen = strlen(gs_kind);
3480             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3481             sprintf(str, "ICS %s", gs_kind);
3482             gameInfo.event = StrSave(str);
3483         } else {
3484             gameInfo.event = StrSave("ICS game");
3485         }
3486         gameInfo.site = StrSave(appData.icsHost);
3487         gameInfo.date = PGNDate();
3488         gameInfo.round = StrSave("-");
3489         gameInfo.white = StrSave(white);
3490         gameInfo.black = StrSave(black);
3491         timeControl = basetime * 60 * 1000;
3492         timeControl_2 = 0;
3493         timeIncrement = increment * 1000;
3494         movesPerSession = 0;
3495         gameInfo.timeControl = TimeControlTagValue();
3496         VariantSwitch(board, StringToVariant(gameInfo.event) );
3497   if (appData.debugMode) {
3498     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3499     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3500     setbuf(debugFP, NULL);
3501   }
3502
3503         gameInfo.outOfBook = NULL;
3504         
3505         /* Do we have the ratings? */
3506         if (strcmp(player1Name, white) == 0 &&
3507             strcmp(player2Name, black) == 0) {
3508             if (appData.debugMode)
3509               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3510                       player1Rating, player2Rating);
3511             gameInfo.whiteRating = player1Rating;
3512             gameInfo.blackRating = player2Rating;
3513         } else if (strcmp(player2Name, white) == 0 &&
3514                    strcmp(player1Name, black) == 0) {
3515             if (appData.debugMode)
3516               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3517                       player2Rating, player1Rating);
3518             gameInfo.whiteRating = player2Rating;
3519             gameInfo.blackRating = player1Rating;
3520         }
3521         player1Name[0] = player2Name[0] = NULLCHAR;
3522
3523         /* Silence shouts if requested */
3524         if (appData.quietPlay &&
3525             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3526             SendToICS(ics_prefix);
3527             SendToICS("set shout 0\n");
3528         }
3529     }
3530     
3531     /* Deal with midgame name changes */
3532     if (!newGame) {
3533         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3534             if (gameInfo.white) free(gameInfo.white);
3535             gameInfo.white = StrSave(white);
3536         }
3537         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3538             if (gameInfo.black) free(gameInfo.black);
3539             gameInfo.black = StrSave(black);
3540         }
3541     }
3542     
3543     /* Throw away game result if anything actually changes in examine mode */
3544     if (gameMode == IcsExamining && !newGame) {
3545         gameInfo.result = GameUnfinished;
3546         if (gameInfo.resultDetails != NULL) {
3547             free(gameInfo.resultDetails);
3548             gameInfo.resultDetails = NULL;
3549         }
3550     }
3551     
3552     /* In pausing && IcsExamining mode, we ignore boards coming
3553        in if they are in a different variation than we are. */
3554     if (pauseExamInvalid) return;
3555     if (pausing && gameMode == IcsExamining) {
3556         if (moveNum <= pauseExamForwardMostMove) {
3557             pauseExamInvalid = TRUE;
3558             forwardMostMove = pauseExamForwardMostMove;
3559             return;
3560         }
3561     }
3562     
3563   if (appData.debugMode) {
3564     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3565   }
3566     /* Parse the board */
3567     for (k = 0; k < ranks; k++) {
3568       for (j = 0; j < files; j++)
3569         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3570       if(gameInfo.holdingsWidth > 1) {
3571            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3572            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3573       }
3574     }
3575     CopyBoard(boards[moveNum], board);
3576     if (moveNum == 0) {
3577         startedFromSetupPosition =
3578           !CompareBoards(board, initialPosition);
3579         if(startedFromSetupPosition)
3580             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3581     }
3582
3583     /* [HGM] Set castling rights. Take the outermost Rooks,
3584        to make it also work for FRC opening positions. Note that board12
3585        is really defective for later FRC positions, as it has no way to
3586        indicate which Rook can castle if they are on the same side of King.
3587        For the initial position we grant rights to the outermost Rooks,
3588        and remember thos rights, and we then copy them on positions
3589        later in an FRC game. This means WB might not recognize castlings with
3590        Rooks that have moved back to their original position as illegal,
3591        but in ICS mode that is not its job anyway.
3592     */
3593     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3594     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3595
3596         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3597             if(board[0][i] == WhiteRook) j = i;
3598         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3599         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3600             if(board[0][i] == WhiteRook) j = i;
3601         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3602         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3603             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3604         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3605         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3606             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3607         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3608
3609         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3610         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3611             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3612         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3613             if(board[BOARD_HEIGHT-1][k] == bKing)
3614                 initialRights[5] = castlingRights[moveNum][5] = k;
3615     } else { int r;
3616         r = castlingRights[moveNum][0] = initialRights[0];
3617         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3618         r = castlingRights[moveNum][1] = initialRights[1];
3619         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3620         r = castlingRights[moveNum][3] = initialRights[3];
3621         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3622         r = castlingRights[moveNum][4] = initialRights[4];
3623         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3624         /* wildcastle kludge: always assume King has rights */
3625         r = castlingRights[moveNum][2] = initialRights[2];
3626         r = castlingRights[moveNum][5] = initialRights[5];
3627     }
3628     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3629     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3630
3631     
3632     if (ics_getting_history == H_GOT_REQ_HEADER ||
3633         ics_getting_history == H_GOT_UNREQ_HEADER) {
3634         /* This was an initial position from a move list, not
3635            the current position */
3636         return;
3637     }
3638     
3639     /* Update currentMove and known move number limits */
3640     newMove = newGame || moveNum > forwardMostMove;
3641
3642     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3643     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3644         takeback = forwardMostMove - moveNum;
3645         for (i = 0; i < takeback; i++) {
3646              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3647              SendToProgram("undo\n", &first);
3648         }
3649     }
3650
3651     if (newGame) {
3652         forwardMostMove = backwardMostMove = currentMove = moveNum;
3653         if (gameMode == IcsExamining && moveNum == 0) {
3654           /* Workaround for ICS limitation: we are not told the wild
3655              type when starting to examine a game.  But if we ask for
3656              the move list, the move list header will tell us */
3657             ics_getting_history = H_REQUESTED;
3658             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3659             SendToICS(str);
3660         }
3661     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3662                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3663         forwardMostMove = moveNum;
3664         if (!pausing || currentMove > forwardMostMove)
3665           currentMove = forwardMostMove;
3666     } else {
3667         /* New part of history that is not contiguous with old part */ 
3668         if (pausing && gameMode == IcsExamining) {
3669             pauseExamInvalid = TRUE;
3670             forwardMostMove = pauseExamForwardMostMove;
3671             return;
3672         }
3673         forwardMostMove = backwardMostMove = currentMove = moveNum;
3674         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3675             ics_getting_history = H_REQUESTED;
3676             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3677             SendToICS(str);
3678         }
3679     }
3680     
3681     /* Update the clocks */
3682     if (strchr(elapsed_time, '.')) {
3683       /* Time is in ms */
3684       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3685       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3686     } else {
3687       /* Time is in seconds */
3688       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3689       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3690     }
3691       
3692
3693 #if ZIPPY
3694     if (appData.zippyPlay && newGame &&
3695         gameMode != IcsObserving && gameMode != IcsIdle &&
3696         gameMode != IcsExamining)
3697       ZippyFirstBoard(moveNum, basetime, increment);
3698 #endif
3699     
3700     /* Put the move on the move list, first converting
3701        to canonical algebraic form. */
3702     if (moveNum > 0) {
3703   if (appData.debugMode) {
3704     if (appData.debugMode) { int f = forwardMostMove;
3705         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3706                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3707     }
3708     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3709     fprintf(debugFP, "moveNum = %d\n", moveNum);
3710     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3711     setbuf(debugFP, NULL);
3712   }
3713         if (moveNum <= backwardMostMove) {
3714             /* We don't know what the board looked like before
3715                this move.  Punt. */
3716             strcpy(parseList[moveNum - 1], move_str);
3717             strcat(parseList[moveNum - 1], " ");
3718             strcat(parseList[moveNum - 1], elapsed_time);
3719             moveList[moveNum - 1][0] = NULLCHAR;
3720         } else if (strcmp(move_str, "none") == 0) {
3721             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3722             /* Again, we don't know what the board looked like;
3723                this is really the start of the game. */
3724             parseList[moveNum - 1][0] = NULLCHAR;
3725             moveList[moveNum - 1][0] = NULLCHAR;
3726             backwardMostMove = moveNum;
3727             startedFromSetupPosition = TRUE;
3728             fromX = fromY = toX = toY = -1;
3729         } else {
3730           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3731           //                 So we parse the long-algebraic move string in stead of the SAN move
3732           int valid; char buf[MSG_SIZ], *prom;
3733
3734           // str looks something like "Q/a1-a2"; kill the slash
3735           if(str[1] == '/') 
3736                 sprintf(buf, "%c%s", str[0], str+2);
3737           else  strcpy(buf, str); // might be castling
3738           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3739                 strcat(buf, prom); // long move lacks promo specification!
3740           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3741                 if(appData.debugMode) 
3742                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3743                 strcpy(move_str, buf);
3744           }
3745           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3746                                 &fromX, &fromY, &toX, &toY, &promoChar)
3747                || ParseOneMove(buf, moveNum - 1, &moveType,
3748                                 &fromX, &fromY, &toX, &toY, &promoChar);
3749           // end of long SAN patch
3750           if (valid) {
3751             (void) CoordsToAlgebraic(boards[moveNum - 1],
3752                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3753                                      fromY, fromX, toY, toX, promoChar,
3754                                      parseList[moveNum-1]);
3755             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3756                              castlingRights[moveNum]) ) {
3757               case MT_NONE:
3758               case MT_STALEMATE:
3759               default:
3760                 break;
3761               case MT_CHECK:
3762                 if(gameInfo.variant != VariantShogi)
3763                     strcat(parseList[moveNum - 1], "+");
3764                 break;
3765               case MT_CHECKMATE:
3766               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3767                 strcat(parseList[moveNum - 1], "#");
3768                 break;
3769             }
3770             strcat(parseList[moveNum - 1], " ");
3771             strcat(parseList[moveNum - 1], elapsed_time);
3772             /* currentMoveString is set as a side-effect of ParseOneMove */
3773             strcpy(moveList[moveNum - 1], currentMoveString);
3774             strcat(moveList[moveNum - 1], "\n");
3775           } else {
3776             /* Move from ICS was illegal!?  Punt. */
3777   if (appData.debugMode) {
3778     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3779     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3780   }
3781             strcpy(parseList[moveNum - 1], move_str);
3782             strcat(parseList[moveNum - 1], " ");
3783             strcat(parseList[moveNum - 1], elapsed_time);
3784             moveList[moveNum - 1][0] = NULLCHAR;
3785             fromX = fromY = toX = toY = -1;
3786           }
3787         }
3788   if (appData.debugMode) {
3789     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3790     setbuf(debugFP, NULL);
3791   }
3792
3793 #if ZIPPY
3794         /* Send move to chess program (BEFORE animating it). */
3795         if (appData.zippyPlay && !newGame && newMove && 
3796            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3797
3798             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3799                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3800                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3801                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3802                             move_str);
3803                     DisplayError(str, 0);
3804                 } else {
3805                     if (first.sendTime) {
3806                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3807                     }
3808                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3809                     if (firstMove && !bookHit) {
3810                         firstMove = FALSE;
3811                         if (first.useColors) {
3812                           SendToProgram(gameMode == IcsPlayingWhite ?
3813                                         "white\ngo\n" :
3814                                         "black\ngo\n", &first);
3815                         } else {
3816                           SendToProgram("go\n", &first);
3817                         }
3818                         first.maybeThinking = TRUE;
3819                     }
3820                 }
3821             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3822               if (moveList[moveNum - 1][0] == NULLCHAR) {
3823                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3824                 DisplayError(str, 0);
3825               } else {
3826                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3827                 SendMoveToProgram(moveNum - 1, &first);
3828               }
3829             }
3830         }
3831 #endif
3832     }
3833
3834     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3835         /* If move comes from a remote source, animate it.  If it
3836            isn't remote, it will have already been animated. */
3837         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3838             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3839         }
3840         if (!pausing && appData.highlightLastMove) {
3841             SetHighlights(fromX, fromY, toX, toY);
3842         }
3843     }
3844     
3845     /* Start the clocks */
3846     whiteFlag = blackFlag = FALSE;
3847     appData.clockMode = !(basetime == 0 && increment == 0);
3848     if (ticking == 0) {
3849       ics_clock_paused = TRUE;
3850       StopClocks();
3851     } else if (ticking == 1) {
3852       ics_clock_paused = FALSE;
3853     }
3854     if (gameMode == IcsIdle ||
3855         relation == RELATION_OBSERVING_STATIC ||
3856         relation == RELATION_EXAMINING ||
3857         ics_clock_paused)
3858       DisplayBothClocks();
3859     else
3860       StartClocks();
3861     
3862     /* Display opponents and material strengths */
3863     if (gameInfo.variant != VariantBughouse &&
3864         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3865         if (tinyLayout || smallLayout) {
3866             if(gameInfo.variant == VariantNormal)
3867                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3868                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3869                     basetime, increment);
3870             else
3871                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3872                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3873                     basetime, increment, (int) gameInfo.variant);
3874         } else {
3875             if(gameInfo.variant == VariantNormal)
3876                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3877                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3878                     basetime, increment);
3879             else
3880                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3881                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3882                     basetime, increment, VariantName(gameInfo.variant));
3883         }
3884         DisplayTitle(str);
3885   if (appData.debugMode) {
3886     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3887   }
3888     }
3889
3890    
3891     /* Display the board */
3892     if (!pausing && !appData.noGUI) {
3893       
3894       if (appData.premove)
3895           if (!gotPremove || 
3896              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3897              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3898               ClearPremoveHighlights();
3899
3900       DrawPosition(FALSE, boards[currentMove]);
3901       DisplayMove(moveNum - 1);
3902       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3903             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3904               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3905         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3906       }
3907     }
3908
3909     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3910 #if ZIPPY
3911     if(bookHit) { // [HGM] book: simulate book reply
3912         static char bookMove[MSG_SIZ]; // a bit generous?
3913
3914         programStats.nodes = programStats.depth = programStats.time = 
3915         programStats.score = programStats.got_only_move = 0;
3916         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3917
3918         strcpy(bookMove, "move ");
3919         strcat(bookMove, bookHit);
3920         HandleMachineMove(bookMove, &first);
3921     }
3922 #endif
3923 }
3924
3925 void
3926 GetMoveListEvent()
3927 {
3928     char buf[MSG_SIZ];
3929     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3930         ics_getting_history = H_REQUESTED;
3931         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3932         SendToICS(buf);
3933     }
3934 }
3935
3936 void
3937 AnalysisPeriodicEvent(force)
3938      int force;
3939 {
3940     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3941          && !force) || !appData.periodicUpdates)
3942       return;
3943
3944     /* Send . command to Crafty to collect stats */
3945     SendToProgram(".\n", &first);
3946
3947     /* Don't send another until we get a response (this makes
3948        us stop sending to old Crafty's which don't understand
3949        the "." command (sending illegal cmds resets node count & time,
3950        which looks bad)) */
3951     programStats.ok_to_send = 0;
3952 }
3953
3954 void ics_update_width(new_width)
3955         int new_width;
3956 {
3957         ics_printf("set width %d\n", new_width);
3958 }
3959
3960 void
3961 SendMoveToProgram(moveNum, cps)
3962      int moveNum;
3963      ChessProgramState *cps;
3964 {
3965     char buf[MSG_SIZ];
3966
3967     if (cps->useUsermove) {
3968       SendToProgram("usermove ", cps);
3969     }
3970     if (cps->useSAN) {
3971       char *space;
3972       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3973         int len = space - parseList[moveNum];
3974         memcpy(buf, parseList[moveNum], len);
3975         buf[len++] = '\n';
3976         buf[len] = NULLCHAR;
3977       } else {
3978         sprintf(buf, "%s\n", parseList[moveNum]);
3979       }
3980       SendToProgram(buf, cps);
3981     } else {
3982       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3983         AlphaRank(moveList[moveNum], 4);
3984         SendToProgram(moveList[moveNum], cps);
3985         AlphaRank(moveList[moveNum], 4); // and back
3986       } else
3987       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3988        * the engine. It would be nice to have a better way to identify castle 
3989        * moves here. */
3990       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3991                                                                          && cps->useOOCastle) {
3992         int fromX = moveList[moveNum][0] - AAA; 
3993         int fromY = moveList[moveNum][1] - ONE;
3994         int toX = moveList[moveNum][2] - AAA; 
3995         int toY = moveList[moveNum][3] - ONE;
3996         if((boards[moveNum][fromY][fromX] == WhiteKing 
3997             && boards[moveNum][toY][toX] == WhiteRook)
3998            || (boards[moveNum][fromY][fromX] == BlackKing 
3999                && boards[moveNum][toY][toX] == BlackRook)) {
4000           if(toX > fromX) SendToProgram("O-O\n", cps);
4001           else SendToProgram("O-O-O\n", cps);
4002         }
4003         else SendToProgram(moveList[moveNum], cps);
4004       }
4005       else SendToProgram(moveList[moveNum], cps);
4006       /* End of additions by Tord */
4007     }
4008
4009     /* [HGM] setting up the opening has brought engine in force mode! */
4010     /*       Send 'go' if we are in a mode where machine should play. */
4011     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4012         (gameMode == TwoMachinesPlay   ||
4013 #ifdef ZIPPY
4014          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4015 #endif
4016          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4017         SendToProgram("go\n", cps);
4018   if (appData.debugMode) {
4019     fprintf(debugFP, "(extra)\n");
4020   }
4021     }
4022     setboardSpoiledMachineBlack = 0;
4023 }
4024
4025 void
4026 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4027      ChessMove moveType;
4028      int fromX, fromY, toX, toY;
4029 {
4030     char user_move[MSG_SIZ];
4031
4032     switch (moveType) {
4033       default:
4034         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4035                 (int)moveType, fromX, fromY, toX, toY);
4036         DisplayError(user_move + strlen("say "), 0);
4037         break;
4038       case WhiteKingSideCastle:
4039       case BlackKingSideCastle:
4040       case WhiteQueenSideCastleWild:
4041       case BlackQueenSideCastleWild:
4042       /* PUSH Fabien */
4043       case WhiteHSideCastleFR:
4044       case BlackHSideCastleFR:
4045       /* POP Fabien */
4046         sprintf(user_move, "o-o\n");
4047         break;
4048       case WhiteQueenSideCastle:
4049       case BlackQueenSideCastle:
4050       case WhiteKingSideCastleWild:
4051       case BlackKingSideCastleWild:
4052       /* PUSH Fabien */
4053       case WhiteASideCastleFR:
4054       case BlackASideCastleFR:
4055       /* POP Fabien */
4056         sprintf(user_move, "o-o-o\n");
4057         break;
4058       case WhitePromotionQueen:
4059       case BlackPromotionQueen:
4060       case WhitePromotionRook:
4061       case BlackPromotionRook:
4062       case WhitePromotionBishop:
4063       case BlackPromotionBishop:
4064       case WhitePromotionKnight:
4065       case BlackPromotionKnight:
4066       case WhitePromotionKing:
4067       case BlackPromotionKing:
4068       case WhitePromotionChancellor:
4069       case BlackPromotionChancellor:
4070       case WhitePromotionArchbishop:
4071       case BlackPromotionArchbishop:
4072         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4073             sprintf(user_move, "%c%c%c%c=%c\n",
4074                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4075                 PieceToChar(WhiteFerz));
4076         else if(gameInfo.variant == VariantGreat)
4077             sprintf(user_move, "%c%c%c%c=%c\n",
4078                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4079                 PieceToChar(WhiteMan));
4080         else
4081             sprintf(user_move, "%c%c%c%c=%c\n",
4082                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4083                 PieceToChar(PromoPiece(moveType)));
4084         break;
4085       case WhiteDrop:
4086       case BlackDrop:
4087         sprintf(user_move, "%c@%c%c\n",
4088                 ToUpper(PieceToChar((ChessSquare) fromX)),
4089                 AAA + toX, ONE + toY);
4090         break;
4091       case NormalMove:
4092       case WhiteCapturesEnPassant:
4093       case BlackCapturesEnPassant:
4094       case IllegalMove:  /* could be a variant we don't quite understand */
4095         sprintf(user_move, "%c%c%c%c\n",
4096                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4097         break;
4098     }
4099     SendToICS(user_move);
4100     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4101         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4102 }
4103
4104 void
4105 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4106      int rf, ff, rt, ft;
4107      char promoChar;
4108      char move[7];
4109 {
4110     if (rf == DROP_RANK) {
4111         sprintf(move, "%c@%c%c\n",
4112                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4113     } else {
4114         if (promoChar == 'x' || promoChar == NULLCHAR) {
4115             sprintf(move, "%c%c%c%c\n",
4116                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4117         } else {
4118             sprintf(move, "%c%c%c%c%c\n",
4119                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4120         }
4121     }
4122 }
4123
4124 void
4125 ProcessICSInitScript(f)
4126      FILE *f;
4127 {
4128     char buf[MSG_SIZ];
4129
4130     while (fgets(buf, MSG_SIZ, f)) {
4131         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4132     }
4133
4134     fclose(f);
4135 }
4136
4137
4138 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4139 void
4140 AlphaRank(char *move, int n)
4141 {
4142 //    char *p = move, c; int x, y;
4143
4144     if (appData.debugMode) {
4145         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4146     }
4147
4148     if(move[1]=='*' && 
4149        move[2]>='0' && move[2]<='9' &&
4150        move[3]>='a' && move[3]<='x'    ) {
4151         move[1] = '@';
4152         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4153         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4154     } else
4155     if(move[0]>='0' && move[0]<='9' &&
4156        move[1]>='a' && move[1]<='x' &&
4157        move[2]>='0' && move[2]<='9' &&
4158        move[3]>='a' && move[3]<='x'    ) {
4159         /* input move, Shogi -> normal */
4160         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4161         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4162         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4163         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4164     } else
4165     if(move[1]=='@' &&
4166        move[3]>='0' && move[3]<='9' &&
4167        move[2]>='a' && move[2]<='x'    ) {
4168         move[1] = '*';
4169         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4170         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4171     } else
4172     if(
4173        move[0]>='a' && move[0]<='x' &&
4174        move[3]>='0' && move[3]<='9' &&
4175        move[2]>='a' && move[2]<='x'    ) {
4176          /* output move, normal -> Shogi */
4177         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4178         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4179         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4180         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4181         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4182     }
4183     if (appData.debugMode) {
4184         fprintf(debugFP, "   out = '%s'\n", move);
4185     }
4186 }
4187
4188 /* Parser for moves from gnuchess, ICS, or user typein box */
4189 Boolean
4190 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4191      char *move;
4192      int moveNum;
4193      ChessMove *moveType;
4194      int *fromX, *fromY, *toX, *toY;
4195      char *promoChar;
4196 {       
4197     if (appData.debugMode) {
4198         fprintf(debugFP, "move to parse: %s\n", move);
4199     }
4200     *moveType = yylexstr(moveNum, move);
4201
4202     switch (*moveType) {
4203       case WhitePromotionChancellor:
4204       case BlackPromotionChancellor:
4205       case WhitePromotionArchbishop:
4206       case BlackPromotionArchbishop:
4207       case WhitePromotionQueen:
4208       case BlackPromotionQueen:
4209       case WhitePromotionRook:
4210       case BlackPromotionRook:
4211       case WhitePromotionBishop:
4212       case BlackPromotionBishop:
4213       case WhitePromotionKnight:
4214       case BlackPromotionKnight:
4215       case WhitePromotionKing:
4216       case BlackPromotionKing:
4217       case NormalMove:
4218       case WhiteCapturesEnPassant:
4219       case BlackCapturesEnPassant:
4220       case WhiteKingSideCastle:
4221       case WhiteQueenSideCastle:
4222       case BlackKingSideCastle:
4223       case BlackQueenSideCastle:
4224       case WhiteKingSideCastleWild:
4225       case WhiteQueenSideCastleWild:
4226       case BlackKingSideCastleWild:
4227       case BlackQueenSideCastleWild:
4228       /* Code added by Tord: */
4229       case WhiteHSideCastleFR:
4230       case WhiteASideCastleFR:
4231       case BlackHSideCastleFR:
4232       case BlackASideCastleFR:
4233       /* End of code added by Tord */
4234       case IllegalMove:         /* bug or odd chess variant */
4235         *fromX = currentMoveString[0] - AAA;
4236         *fromY = currentMoveString[1] - ONE;
4237         *toX = currentMoveString[2] - AAA;
4238         *toY = currentMoveString[3] - ONE;
4239         *promoChar = currentMoveString[4];
4240         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4241             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4242     if (appData.debugMode) {
4243         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4244     }
4245             *fromX = *fromY = *toX = *toY = 0;
4246             return FALSE;
4247         }
4248         if (appData.testLegality) {
4249           return (*moveType != IllegalMove);
4250         } else {
4251           return !(fromX == fromY && toX == toY);
4252         }
4253
4254       case WhiteDrop:
4255       case BlackDrop:
4256         *fromX = *moveType == WhiteDrop ?
4257           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4258           (int) CharToPiece(ToLower(currentMoveString[0]));
4259         *fromY = DROP_RANK;
4260         *toX = currentMoveString[2] - AAA;
4261         *toY = currentMoveString[3] - ONE;
4262         *promoChar = NULLCHAR;
4263         return TRUE;
4264
4265       case AmbiguousMove:
4266       case ImpossibleMove:
4267       case (ChessMove) 0:       /* end of file */
4268       case ElapsedTime:
4269       case Comment:
4270       case PGNTag:
4271       case NAG:
4272       case WhiteWins:
4273       case BlackWins:
4274       case GameIsDrawn:
4275       default:
4276     if (appData.debugMode) {
4277         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4278     }
4279         /* bug? */
4280         *fromX = *fromY = *toX = *toY = 0;
4281         *promoChar = NULLCHAR;
4282         return FALSE;
4283     }
4284 }
4285
4286 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4287 // All positions will have equal probability, but the current method will not provide a unique
4288 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4289 #define DARK 1
4290 #define LITE 2
4291 #define ANY 3
4292
4293 int squaresLeft[4];
4294 int piecesLeft[(int)BlackPawn];
4295 int seed, nrOfShuffles;
4296
4297 void GetPositionNumber()
4298 {       // sets global variable seed
4299         int i;
4300
4301         seed = appData.defaultFrcPosition;
4302         if(seed < 0) { // randomize based on time for negative FRC position numbers
4303                 for(i=0; i<50; i++) seed += random();
4304                 seed = random() ^ random() >> 8 ^ random() << 8;
4305                 if(seed<0) seed = -seed;
4306         }
4307 }
4308
4309 int put(Board board, int pieceType, int rank, int n, int shade)
4310 // put the piece on the (n-1)-th empty squares of the given shade
4311 {
4312         int i;
4313
4314         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4315                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4316                         board[rank][i] = (ChessSquare) pieceType;
4317                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4318                         squaresLeft[ANY]--;
4319                         piecesLeft[pieceType]--; 
4320                         return i;
4321                 }
4322         }
4323         return -1;
4324 }
4325
4326
4327 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4328 // calculate where the next piece goes, (any empty square), and put it there
4329 {
4330         int i;
4331
4332         i = seed % squaresLeft[shade];
4333         nrOfShuffles *= squaresLeft[shade];
4334         seed /= squaresLeft[shade];
4335         put(board, pieceType, rank, i, shade);
4336 }
4337
4338 void AddTwoPieces(Board board, int pieceType, int rank)
4339 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4340 {
4341         int i, n=squaresLeft[ANY], j=n-1, k;
4342
4343         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4344         i = seed % k;  // pick one
4345         nrOfShuffles *= k;
4346         seed /= k;
4347         while(i >= j) i -= j--;
4348         j = n - 1 - j; i += j;
4349         put(board, pieceType, rank, j, ANY);
4350         put(board, pieceType, rank, i, ANY);
4351 }
4352
4353 void SetUpShuffle(Board board, int number)
4354 {
4355         int i, p, first=1;
4356
4357         GetPositionNumber(); nrOfShuffles = 1;
4358
4359         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4360         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4361         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4362
4363         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4364
4365         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4366             p = (int) board[0][i];
4367             if(p < (int) BlackPawn) piecesLeft[p] ++;
4368             board[0][i] = EmptySquare;
4369         }
4370
4371         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4372             // shuffles restricted to allow normal castling put KRR first
4373             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4374                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4375             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4376                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4377             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4378                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4379             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4380                 put(board, WhiteRook, 0, 0, ANY);
4381             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4382         }
4383
4384         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4385             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4386             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4387                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4388                 while(piecesLeft[p] >= 2) {
4389                     AddOnePiece(board, p, 0, LITE);
4390                     AddOnePiece(board, p, 0, DARK);
4391                 }
4392                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4393             }
4394
4395         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4396             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4397             // but we leave King and Rooks for last, to possibly obey FRC restriction
4398             if(p == (int)WhiteRook) continue;
4399             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4400             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4401         }
4402
4403         // now everything is placed, except perhaps King (Unicorn) and Rooks
4404
4405         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4406             // Last King gets castling rights
4407             while(piecesLeft[(int)WhiteUnicorn]) {
4408                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4409                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4410             }
4411
4412             while(piecesLeft[(int)WhiteKing]) {
4413                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4414                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4415             }
4416
4417
4418         } else {
4419             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4420             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4421         }
4422
4423         // Only Rooks can be left; simply place them all
4424         while(piecesLeft[(int)WhiteRook]) {
4425                 i = put(board, WhiteRook, 0, 0, ANY);
4426                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4427                         if(first) {
4428                                 first=0;
4429                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4430                         }
4431                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4432                 }
4433         }
4434         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4435             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4436         }
4437
4438         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4439 }
4440
4441 int SetCharTable( char *table, const char * map )
4442 /* [HGM] moved here from winboard.c because of its general usefulness */
4443 /*       Basically a safe strcpy that uses the last character as King */
4444 {
4445     int result = FALSE; int NrPieces;
4446
4447     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4448                     && NrPieces >= 12 && !(NrPieces&1)) {
4449         int i; /* [HGM] Accept even length from 12 to 34 */
4450
4451         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4452         for( i=0; i<NrPieces/2-1; i++ ) {
4453             table[i] = map[i];
4454             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4455         }
4456         table[(int) WhiteKing]  = map[NrPieces/2-1];
4457         table[(int) BlackKing]  = map[NrPieces-1];
4458
4459         result = TRUE;
4460     }
4461
4462     return result;
4463 }
4464
4465 void Prelude(Board board)
4466 {       // [HGM] superchess: random selection of exo-pieces
4467         int i, j, k; ChessSquare p; 
4468         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4469
4470         GetPositionNumber(); // use FRC position number
4471
4472         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4473             SetCharTable(pieceToChar, appData.pieceToCharTable);
4474             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4475                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4476         }
4477
4478         j = seed%4;                 seed /= 4; 
4479         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4480         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4481         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4482         j = seed%3 + (seed%3 >= j); seed /= 3; 
4483         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4484         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4485         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4486         j = seed%3;                 seed /= 3; 
4487         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4488         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4489         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4490         j = seed%2 + (seed%2 >= j); seed /= 2; 
4491         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4492         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4493         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4494         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4495         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4496         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4497         put(board, exoPieces[0],    0, 0, ANY);
4498         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4499 }
4500
4501 void
4502 InitPosition(redraw)
4503      int redraw;
4504 {
4505     ChessSquare (* pieces)[BOARD_SIZE];
4506     int i, j, pawnRow, overrule,
4507     oldx = gameInfo.boardWidth,
4508     oldy = gameInfo.boardHeight,
4509     oldh = gameInfo.holdingsWidth,
4510     oldv = gameInfo.variant;
4511
4512     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4513
4514     /* [AS] Initialize pv info list [HGM] and game status */
4515     {
4516         for( i=0; i<MAX_MOVES; i++ ) {
4517             pvInfoList[i].depth = 0;
4518             epStatus[i]=EP_NONE;
4519             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4520         }
4521
4522         initialRulePlies = 0; /* 50-move counter start */
4523
4524         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4525         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4526     }
4527
4528     
4529     /* [HGM] logic here is completely changed. In stead of full positions */
4530     /* the initialized data only consist of the two backranks. The switch */
4531     /* selects which one we will use, which is than copied to the Board   */
4532     /* initialPosition, which for the rest is initialized by Pawns and    */
4533     /* empty squares. This initial position is then copied to boards[0],  */
4534     /* possibly after shuffling, so that it remains available.            */
4535
4536     gameInfo.holdingsWidth = 0; /* default board sizes */
4537     gameInfo.boardWidth    = 8;
4538     gameInfo.boardHeight   = 8;
4539     gameInfo.holdingsSize  = 0;
4540     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4541     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4542     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4543
4544     switch (gameInfo.variant) {
4545     case VariantFischeRandom:
4546       shuffleOpenings = TRUE;
4547     default:
4548       pieces = FIDEArray;
4549       break;
4550     case VariantShatranj:
4551       pieces = ShatranjArray;
4552       nrCastlingRights = 0;
4553       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4554       break;
4555     case VariantTwoKings:
4556       pieces = twoKingsArray;
4557       break;
4558     case VariantCapaRandom:
4559       shuffleOpenings = TRUE;
4560     case VariantCapablanca:
4561       pieces = CapablancaArray;
4562       gameInfo.boardWidth = 10;
4563       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4564       break;
4565     case VariantGothic:
4566       pieces = GothicArray;
4567       gameInfo.boardWidth = 10;
4568       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4569       break;
4570     case VariantJanus:
4571       pieces = JanusArray;
4572       gameInfo.boardWidth = 10;
4573       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4574       nrCastlingRights = 6;
4575         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4576         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4577         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4578         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4579         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4580         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4581       break;
4582     case VariantFalcon:
4583       pieces = FalconArray;
4584       gameInfo.boardWidth = 10;
4585       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4586       break;
4587     case VariantXiangqi:
4588       pieces = XiangqiArray;
4589       gameInfo.boardWidth  = 9;
4590       gameInfo.boardHeight = 10;
4591       nrCastlingRights = 0;
4592       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4593       break;
4594     case VariantShogi:
4595       pieces = ShogiArray;
4596       gameInfo.boardWidth  = 9;
4597       gameInfo.boardHeight = 9;
4598       gameInfo.holdingsSize = 7;
4599       nrCastlingRights = 0;
4600       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4601       break;
4602     case VariantCourier:
4603       pieces = CourierArray;
4604       gameInfo.boardWidth  = 12;
4605       nrCastlingRights = 0;
4606       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4607       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4608       break;
4609     case VariantKnightmate:
4610       pieces = KnightmateArray;
4611       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4612       break;
4613     case VariantFairy:
4614       pieces = fairyArray;
4615       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4616       break;
4617     case VariantGreat:
4618       pieces = GreatArray;
4619       gameInfo.boardWidth = 10;
4620       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4621       gameInfo.holdingsSize = 8;
4622       break;
4623     case VariantSuper:
4624       pieces = FIDEArray;
4625       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4626       gameInfo.holdingsSize = 8;
4627       startedFromSetupPosition = TRUE;
4628       break;
4629     case VariantCrazyhouse:
4630     case VariantBughouse:
4631       pieces = FIDEArray;
4632       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4633       gameInfo.holdingsSize = 5;
4634       break;
4635     case VariantWildCastle:
4636       pieces = FIDEArray;
4637       /* !!?shuffle with kings guaranteed to be on d or e file */
4638       shuffleOpenings = 1;
4639       break;
4640     case VariantNoCastle:
4641       pieces = FIDEArray;
4642       nrCastlingRights = 0;
4643       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4644       /* !!?unconstrained back-rank shuffle */
4645       shuffleOpenings = 1;
4646       break;
4647     }
4648
4649     overrule = 0;
4650     if(appData.NrFiles >= 0) {
4651         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4652         gameInfo.boardWidth = appData.NrFiles;
4653     }
4654     if(appData.NrRanks >= 0) {
4655         gameInfo.boardHeight = appData.NrRanks;
4656     }
4657     if(appData.holdingsSize >= 0) {
4658         i = appData.holdingsSize;
4659         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4660         gameInfo.holdingsSize = i;
4661     }
4662     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4663     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4664         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4665
4666     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4667     if(pawnRow < 1) pawnRow = 1;
4668
4669     /* User pieceToChar list overrules defaults */
4670     if(appData.pieceToCharTable != NULL)
4671         SetCharTable(pieceToChar, appData.pieceToCharTable);
4672
4673     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4674
4675         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4676             s = (ChessSquare) 0; /* account holding counts in guard band */
4677         for( i=0; i<BOARD_HEIGHT; i++ )
4678             initialPosition[i][j] = s;
4679
4680         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4681         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4682         initialPosition[pawnRow][j] = WhitePawn;
4683         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4684         if(gameInfo.variant == VariantXiangqi) {
4685             if(j&1) {
4686                 initialPosition[pawnRow][j] = 
4687                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4688                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4689                    initialPosition[2][j] = WhiteCannon;
4690                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4691                 }
4692             }
4693         }
4694         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4695     }
4696     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4697
4698             j=BOARD_LEFT+1;
4699             initialPosition[1][j] = WhiteBishop;
4700             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4701             j=BOARD_RGHT-2;
4702             initialPosition[1][j] = WhiteRook;
4703             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4704     }
4705
4706     if( nrCastlingRights == -1) {
4707         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4708         /*       This sets default castling rights from none to normal corners   */
4709         /* Variants with other castling rights must set them themselves above    */
4710         nrCastlingRights = 6;
4711        
4712         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4713         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4714         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4715         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4716         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4717         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4718      }
4719
4720      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4721      if(gameInfo.variant == VariantGreat) { // promotion commoners
4722         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4723         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4724         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4725         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4726      }
4727   if (appData.debugMode) {
4728     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4729   }
4730     if(shuffleOpenings) {
4731         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4732         startedFromSetupPosition = TRUE;
4733     }
4734     if(startedFromPositionFile) {
4735       /* [HGM] loadPos: use PositionFile for every new game */
4736       CopyBoard(initialPosition, filePosition);
4737       for(i=0; i<nrCastlingRights; i++)
4738           castlingRights[0][i] = initialRights[i] = fileRights[i];
4739       startedFromSetupPosition = TRUE;
4740     }
4741
4742     CopyBoard(boards[0], initialPosition);
4743
4744     if(oldx != gameInfo.boardWidth ||
4745        oldy != gameInfo.boardHeight ||
4746        oldh != gameInfo.holdingsWidth
4747 #ifdef GOTHIC
4748        || oldv == VariantGothic ||        // For licensing popups
4749        gameInfo.variant == VariantGothic
4750 #endif
4751 #ifdef FALCON
4752        || oldv == VariantFalcon ||
4753        gameInfo.variant == VariantFalcon
4754 #endif
4755                                          )
4756             InitDrawingSizes(-2 ,0);
4757
4758     if (redraw)
4759       DrawPosition(TRUE, boards[currentMove]);
4760 }
4761
4762 void
4763 SendBoard(cps, moveNum)
4764      ChessProgramState *cps;
4765      int moveNum;
4766 {
4767     char message[MSG_SIZ];
4768     
4769     if (cps->useSetboard) {
4770       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4771       sprintf(message, "setboard %s\n", fen);
4772       SendToProgram(message, cps);
4773       free(fen);
4774
4775     } else {
4776       ChessSquare *bp;
4777       int i, j;
4778       /* Kludge to set black to move, avoiding the troublesome and now
4779        * deprecated "black" command.
4780        */
4781       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4782
4783       SendToProgram("edit\n", cps);
4784       SendToProgram("#\n", cps);
4785       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4786         bp = &boards[moveNum][i][BOARD_LEFT];
4787         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4788           if ((int) *bp < (int) BlackPawn) {
4789             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4790                     AAA + j, ONE + i);
4791             if(message[0] == '+' || message[0] == '~') {
4792                 sprintf(message, "%c%c%c+\n",
4793                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4794                         AAA + j, ONE + i);
4795             }
4796             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4797                 message[1] = BOARD_RGHT   - 1 - j + '1';
4798                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4799             }
4800             SendToProgram(message, cps);
4801           }
4802         }
4803       }
4804     
4805       SendToProgram("c\n", cps);
4806       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4807         bp = &boards[moveNum][i][BOARD_LEFT];
4808         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4809           if (((int) *bp != (int) EmptySquare)
4810               && ((int) *bp >= (int) BlackPawn)) {
4811             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4812                     AAA + j, ONE + i);
4813             if(message[0] == '+' || message[0] == '~') {
4814                 sprintf(message, "%c%c%c+\n",
4815                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4816                         AAA + j, ONE + i);
4817             }
4818             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4819                 message[1] = BOARD_RGHT   - 1 - j + '1';
4820                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4821             }
4822             SendToProgram(message, cps);
4823           }
4824         }
4825       }
4826     
4827       SendToProgram(".\n", cps);
4828     }
4829     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4830 }
4831
4832 int
4833 IsPromotion(fromX, fromY, toX, toY)
4834      int fromX, fromY, toX, toY;
4835 {
4836     /* [HGM] add Shogi promotions */
4837     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4838     ChessSquare piece;
4839
4840     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4841       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4842    /* [HGM] Note to self: line above also weeds out drops */
4843     piece = boards[currentMove][fromY][fromX];
4844     if(gameInfo.variant == VariantShogi) {
4845         promotionZoneSize = 3;
4846         highestPromotingPiece = (int)WhiteKing;
4847         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4848            and if in normal chess we then allow promotion to King, why not
4849            allow promotion of other piece in Shogi?                         */
4850     }
4851     if((int)piece >= BlackPawn) {
4852         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4853              return FALSE;
4854         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4855     } else {
4856         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4857            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4858     }
4859     return ( (int)piece <= highestPromotingPiece );
4860 }
4861
4862 int
4863 InPalace(row, column)
4864      int row, column;
4865 {   /* [HGM] for Xiangqi */
4866     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4867          column < (BOARD_WIDTH + 4)/2 &&
4868          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4869     return FALSE;
4870 }
4871
4872 int
4873 PieceForSquare (x, y)
4874      int x;
4875      int y;
4876 {
4877   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4878      return -1;
4879   else
4880      return boards[currentMove][y][x];
4881 }
4882
4883 int
4884 OKToStartUserMove(x, y)
4885      int x, y;
4886 {
4887     ChessSquare from_piece;
4888     int white_piece;
4889
4890     if (matchMode) return FALSE;
4891     if (gameMode == EditPosition) return TRUE;
4892
4893     if (x >= 0 && y >= 0)
4894       from_piece = boards[currentMove][y][x];
4895     else
4896       from_piece = EmptySquare;
4897
4898     if (from_piece == EmptySquare) return FALSE;
4899
4900     white_piece = (int)from_piece >= (int)WhitePawn &&
4901       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4902
4903     switch (gameMode) {
4904       case PlayFromGameFile:
4905       case AnalyzeFile:
4906       case TwoMachinesPlay:
4907       case EndOfGame:
4908         return FALSE;
4909
4910       case IcsObserving:
4911       case IcsIdle:
4912         return FALSE;
4913
4914       case MachinePlaysWhite:
4915       case IcsPlayingBlack:
4916         if (appData.zippyPlay) return FALSE;
4917         if (white_piece) {
4918             DisplayMoveError(_("You are playing Black"));
4919             return FALSE;
4920         }
4921         break;
4922
4923       case MachinePlaysBlack:
4924       case IcsPlayingWhite:
4925         if (appData.zippyPlay) return FALSE;
4926         if (!white_piece) {
4927             DisplayMoveError(_("You are playing White"));
4928             return FALSE;
4929         }
4930         break;
4931
4932       case EditGame:
4933         if (!white_piece && WhiteOnMove(currentMove)) {
4934             DisplayMoveError(_("It is White's turn"));
4935             return FALSE;
4936         }           
4937         if (white_piece && !WhiteOnMove(currentMove)) {
4938             DisplayMoveError(_("It is Black's turn"));
4939             return FALSE;
4940         }           
4941         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4942             /* Editing correspondence game history */
4943             /* Could disallow this or prompt for confirmation */
4944             cmailOldMove = -1;
4945         }
4946         if (currentMove < forwardMostMove) {
4947             /* Discarding moves */
4948             /* Could prompt for confirmation here,
4949                but I don't think that's such a good idea */
4950             forwardMostMove = currentMove;
4951         }
4952         break;
4953
4954       case BeginningOfGame:
4955         if (appData.icsActive) return FALSE;
4956         if (!appData.noChessProgram) {
4957             if (!white_piece) {
4958                 DisplayMoveError(_("You are playing White"));
4959                 return FALSE;
4960             }
4961         }
4962         break;
4963         
4964       case Training:
4965         if (!white_piece && WhiteOnMove(currentMove)) {
4966             DisplayMoveError(_("It is White's turn"));
4967             return FALSE;
4968         }           
4969         if (white_piece && !WhiteOnMove(currentMove)) {
4970             DisplayMoveError(_("It is Black's turn"));
4971             return FALSE;
4972         }           
4973         break;
4974
4975       default:
4976       case IcsExamining:
4977         break;
4978     }
4979     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4980         && gameMode != AnalyzeFile && gameMode != Training) {
4981         DisplayMoveError(_("Displayed position is not current"));
4982         return FALSE;
4983     }
4984     return TRUE;
4985 }
4986
4987 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4988 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4989 int lastLoadGameUseList = FALSE;
4990 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4991 ChessMove lastLoadGameStart = (ChessMove) 0;
4992
4993 ChessMove
4994 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
4995      int fromX, fromY, toX, toY;
4996      int promoChar;
4997      Boolean captureOwn;
4998 {
4999     ChessMove moveType;
5000     ChessSquare pdown, pup;
5001
5002     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5003
5004     /* [HGM] suppress all moves into holdings area and guard band */
5005     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5006             return ImpossibleMove;
5007
5008     /* [HGM] <sameColor> moved to here from winboard.c */
5009     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5010     pdown = boards[currentMove][fromY][fromX];
5011     pup = boards[currentMove][toY][toX];
5012     if (    gameMode != EditPosition && !captureOwn &&
5013             (WhitePawn <= pdown && pdown < BlackPawn &&
5014              WhitePawn <= pup && pup < BlackPawn  ||
5015              BlackPawn <= pdown && pdown < EmptySquare &&
5016              BlackPawn <= pup && pup < EmptySquare 
5017             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5018                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5019                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5020                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5021                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5022         )           )
5023          return Comment;
5024
5025     /* Check if the user is playing in turn.  This is complicated because we
5026        let the user "pick up" a piece before it is his turn.  So the piece he
5027        tried to pick up may have been captured by the time he puts it down!
5028        Therefore we use the color the user is supposed to be playing in this
5029        test, not the color of the piece that is currently on the starting
5030        square---except in EditGame mode, where the user is playing both
5031        sides; fortunately there the capture race can't happen.  (It can
5032        now happen in IcsExamining mode, but that's just too bad.  The user
5033        will get a somewhat confusing message in that case.)
5034        */
5035
5036     switch (gameMode) {
5037       case PlayFromGameFile:
5038       case AnalyzeFile:
5039       case TwoMachinesPlay:
5040       case EndOfGame:
5041       case IcsObserving:
5042       case IcsIdle:
5043         /* We switched into a game mode where moves are not accepted,
5044            perhaps while the mouse button was down. */
5045         return ImpossibleMove;
5046
5047       case MachinePlaysWhite:
5048         /* User is moving for Black */
5049         if (WhiteOnMove(currentMove)) {
5050             DisplayMoveError(_("It is White's turn"));
5051             return ImpossibleMove;
5052         }
5053         break;
5054
5055       case MachinePlaysBlack:
5056         /* User is moving for White */
5057         if (!WhiteOnMove(currentMove)) {
5058             DisplayMoveError(_("It is Black's turn"));
5059             return ImpossibleMove;
5060         }
5061         break;
5062
5063       case EditGame:
5064       case IcsExamining:
5065       case BeginningOfGame:
5066       case AnalyzeMode:
5067       case Training:
5068         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5069             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5070             /* User is moving for Black */
5071             if (WhiteOnMove(currentMove)) {
5072                 DisplayMoveError(_("It is White's turn"));
5073                 return ImpossibleMove;
5074             }
5075         } else {
5076             /* User is moving for White */
5077             if (!WhiteOnMove(currentMove)) {
5078                 DisplayMoveError(_("It is Black's turn"));
5079                 return ImpossibleMove;
5080             }
5081         }
5082         break;
5083
5084       case IcsPlayingBlack:
5085         /* User is moving for Black */
5086         if (WhiteOnMove(currentMove)) {
5087             if (!appData.premove) {
5088                 DisplayMoveError(_("It is White's turn"));
5089             } else if (toX >= 0 && toY >= 0) {
5090                 premoveToX = toX;
5091                 premoveToY = toY;
5092                 premoveFromX = fromX;
5093                 premoveFromY = fromY;
5094                 premovePromoChar = promoChar;
5095                 gotPremove = 1;
5096                 if (appData.debugMode) 
5097                     fprintf(debugFP, "Got premove: fromX %d,"
5098                             "fromY %d, toX %d, toY %d\n",
5099                             fromX, fromY, toX, toY);
5100             }
5101             return ImpossibleMove;
5102         }
5103         break;
5104
5105       case IcsPlayingWhite:
5106         /* User is moving for White */
5107         if (!WhiteOnMove(currentMove)) {
5108             if (!appData.premove) {
5109                 DisplayMoveError(_("It is Black's turn"));
5110             } else if (toX >= 0 && toY >= 0) {
5111                 premoveToX = toX;
5112                 premoveToY = toY;
5113                 premoveFromX = fromX;
5114                 premoveFromY = fromY;
5115                 premovePromoChar = promoChar;
5116                 gotPremove = 1;
5117                 if (appData.debugMode) 
5118                     fprintf(debugFP, "Got premove: fromX %d,"
5119                             "fromY %d, toX %d, toY %d\n",
5120                             fromX, fromY, toX, toY);
5121             }
5122             return ImpossibleMove;
5123         }
5124         break;
5125
5126       default:
5127         break;
5128
5129       case EditPosition:
5130         /* EditPosition, empty square, or different color piece;
5131            click-click move is possible */
5132         if (toX == -2 || toY == -2) {
5133             boards[0][fromY][fromX] = EmptySquare;
5134             return AmbiguousMove;
5135         } else if (toX >= 0 && toY >= 0) {
5136             boards[0][toY][toX] = boards[0][fromY][fromX];
5137             boards[0][fromY][fromX] = EmptySquare;
5138             return AmbiguousMove;
5139         }
5140         return ImpossibleMove;
5141     }
5142
5143     /* [HGM] If move started in holdings, it means a drop */
5144     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5145          if( pup != EmptySquare ) return ImpossibleMove;
5146          if(appData.testLegality) {
5147              /* it would be more logical if LegalityTest() also figured out
5148               * which drops are legal. For now we forbid pawns on back rank.
5149               * Shogi is on its own here...
5150               */
5151              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5152                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5153                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5154          }
5155          return WhiteDrop; /* Not needed to specify white or black yet */
5156     }
5157
5158     userOfferedDraw = FALSE;
5159         
5160     /* [HGM] always test for legality, to get promotion info */
5161     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5162                           epStatus[currentMove], castlingRights[currentMove],
5163                                          fromY, fromX, toY, toX, promoChar);
5164     /* [HGM] but possibly ignore an IllegalMove result */
5165     if (appData.testLegality) {
5166         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5167             DisplayMoveError(_("Illegal move"));
5168             return ImpossibleMove;
5169         }
5170     }
5171 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5172     return moveType;
5173     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5174        function is made into one that returns an OK move type if FinishMove
5175        should be called. This to give the calling driver routine the
5176        opportunity to finish the userMove input with a promotion popup,
5177        without bothering the user with this for invalid or illegal moves */
5178
5179 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5180 }
5181
5182 /* Common tail of UserMoveEvent and DropMenuEvent */
5183 int
5184 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5185      ChessMove moveType;
5186      int fromX, fromY, toX, toY;
5187      /*char*/int promoChar;
5188 {
5189     char *bookHit = 0;
5190 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5191     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5192         // [HGM] superchess: suppress promotions to non-available piece
5193         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5194         if(WhiteOnMove(currentMove)) {
5195             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5196         } else {
5197             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5198         }
5199     }
5200
5201     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5202        move type in caller when we know the move is a legal promotion */
5203     if(moveType == NormalMove && promoChar)
5204         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5205 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5206     /* [HGM] convert drag-and-drop piece drops to standard form */
5207     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5208          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5209            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5210                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5211 //         fromX = boards[currentMove][fromY][fromX];
5212            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5213            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5214            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5215            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5216          fromY = DROP_RANK;
5217     }
5218
5219     /* [HGM] <popupFix> The following if has been moved here from
5220        UserMoveEvent(). Because it seemed to belon here (why not allow
5221        piece drops in training games?), and because it can only be
5222        performed after it is known to what we promote. */
5223     if (gameMode == Training) {
5224       /* compare the move played on the board to the next move in the
5225        * game. If they match, display the move and the opponent's response. 
5226        * If they don't match, display an error message.
5227        */
5228       int saveAnimate;
5229       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5230       CopyBoard(testBoard, boards[currentMove]);
5231       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5232
5233       if (CompareBoards(testBoard, boards[currentMove+1])) {
5234         ForwardInner(currentMove+1);
5235
5236         /* Autoplay the opponent's response.
5237          * if appData.animate was TRUE when Training mode was entered,
5238          * the response will be animated.
5239          */
5240         saveAnimate = appData.animate;
5241         appData.animate = animateTraining;
5242         ForwardInner(currentMove+1);
5243         appData.animate = saveAnimate;
5244
5245         /* check for the end of the game */
5246         if (currentMove >= forwardMostMove) {
5247           gameMode = PlayFromGameFile;
5248           ModeHighlight();
5249           SetTrainingModeOff();
5250           DisplayInformation(_("End of game"));
5251         }
5252       } else {
5253         DisplayError(_("Incorrect move"), 0);
5254       }
5255       return 1;
5256     }
5257
5258   /* Ok, now we know that the move is good, so we can kill
5259      the previous line in Analysis Mode */
5260   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5261     forwardMostMove = currentMove;
5262   }
5263
5264   /* If we need the chess program but it's dead, restart it */
5265   ResurrectChessProgram();
5266
5267   /* A user move restarts a paused game*/
5268   if (pausing)
5269     PauseEvent();
5270
5271   thinkOutput[0] = NULLCHAR;
5272
5273   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5274
5275   if (gameMode == BeginningOfGame) {
5276     if (appData.noChessProgram) {
5277       gameMode = EditGame;
5278       SetGameInfo();
5279     } else {
5280       char buf[MSG_SIZ];
5281       gameMode = MachinePlaysBlack;
5282       StartClocks();
5283       SetGameInfo();
5284       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5285       DisplayTitle(buf);
5286       if (first.sendName) {
5287         sprintf(buf, "name %s\n", gameInfo.white);
5288         SendToProgram(buf, &first);
5289       }
5290       StartClocks();
5291     }
5292     ModeHighlight();
5293   }
5294 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5295   /* Relay move to ICS or chess engine */
5296   if (appData.icsActive) {
5297     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5298         gameMode == IcsExamining) {
5299       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5300       ics_user_moved = 1;
5301     }
5302   } else {
5303     if (first.sendTime && (gameMode == BeginningOfGame ||
5304                            gameMode == MachinePlaysWhite ||
5305                            gameMode == MachinePlaysBlack)) {
5306       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5307     }
5308     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5309          // [HGM] book: if program might be playing, let it use book
5310         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5311         first.maybeThinking = TRUE;
5312     } else SendMoveToProgram(forwardMostMove-1, &first);
5313     if (currentMove == cmailOldMove + 1) {
5314       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5315     }
5316   }
5317
5318   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5319
5320   switch (gameMode) {
5321   case EditGame:
5322     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5323                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5324     case MT_NONE:
5325     case MT_CHECK:
5326       break;
5327     case MT_CHECKMATE:
5328     case MT_STAINMATE:
5329       if (WhiteOnMove(currentMove)) {
5330         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5331       } else {
5332         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5333       }
5334       break;
5335     case MT_STALEMATE:
5336       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5337       break;
5338     }
5339     break;
5340     
5341   case MachinePlaysBlack:
5342   case MachinePlaysWhite:
5343     /* disable certain menu options while machine is thinking */
5344     SetMachineThinkingEnables();
5345     break;
5346
5347   default:
5348     break;
5349   }
5350
5351   if(bookHit) { // [HGM] book: simulate book reply
5352         static char bookMove[MSG_SIZ]; // a bit generous?
5353
5354         programStats.nodes = programStats.depth = programStats.time = 
5355         programStats.score = programStats.got_only_move = 0;
5356         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5357
5358         strcpy(bookMove, "move ");
5359         strcat(bookMove, bookHit);
5360         HandleMachineMove(bookMove, &first);
5361   }
5362   return 1;
5363 }
5364
5365 void
5366 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5367      int fromX, fromY, toX, toY;
5368      int promoChar;
5369 {
5370     /* [HGM] This routine was added to allow calling of its two logical
5371        parts from other modules in the old way. Before, UserMoveEvent()
5372        automatically called FinishMove() if the move was OK, and returned
5373        otherwise. I separated the two, in order to make it possible to
5374        slip a promotion popup in between. But that it always needs two
5375        calls, to the first part, (now called UserMoveTest() ), and to
5376        FinishMove if the first part succeeded. Calls that do not need
5377        to do anything in between, can call this routine the old way. 
5378     */
5379     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5380 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5381     if(moveType == AmbiguousMove)
5382         DrawPosition(FALSE, boards[currentMove]);
5383     else if(moveType != ImpossibleMove && moveType != Comment)
5384         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5385 }
5386
5387 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5388 {
5389 //    char * hint = lastHint;
5390     FrontEndProgramStats stats;
5391
5392     stats.which = cps == &first ? 0 : 1;
5393     stats.depth = cpstats->depth;
5394     stats.nodes = cpstats->nodes;
5395     stats.score = cpstats->score;
5396     stats.time = cpstats->time;
5397     stats.pv = cpstats->movelist;
5398     stats.hint = lastHint;
5399     stats.an_move_index = 0;
5400     stats.an_move_count = 0;
5401
5402     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5403         stats.hint = cpstats->move_name;
5404         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5405         stats.an_move_count = cpstats->nr_moves;
5406     }
5407
5408     SetProgramStats( &stats );
5409 }
5410
5411 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5412 {   // [HGM] book: this routine intercepts moves to simulate book replies
5413     char *bookHit = NULL;
5414
5415     //first determine if the incoming move brings opponent into his book
5416     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5417         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5418     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5419     if(bookHit != NULL && !cps->bookSuspend) {
5420         // make sure opponent is not going to reply after receiving move to book position
5421         SendToProgram("force\n", cps);
5422         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5423     }
5424     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5425     // now arrange restart after book miss
5426     if(bookHit) {
5427         // after a book hit we never send 'go', and the code after the call to this routine
5428         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5429         char buf[MSG_SIZ];
5430         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5431         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5432         SendToProgram(buf, cps);
5433         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5434     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5435         SendToProgram("go\n", cps);
5436         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5437     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5438         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5439             SendToProgram("go\n", cps); 
5440         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5441     }
5442     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5443 }
5444
5445 char *savedMessage;
5446 ChessProgramState *savedState;
5447 void DeferredBookMove(void)
5448 {
5449         if(savedState->lastPing != savedState->lastPong)
5450                     ScheduleDelayedEvent(DeferredBookMove, 10);
5451         else
5452         HandleMachineMove(savedMessage, savedState);
5453 }
5454
5455 void
5456 HandleMachineMove(message, cps)
5457      char *message;
5458      ChessProgramState *cps;
5459 {
5460     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5461     char realname[MSG_SIZ];
5462     int fromX, fromY, toX, toY;
5463     ChessMove moveType;
5464     char promoChar;
5465     char *p;
5466     int machineWhite;
5467     char *bookHit;
5468
5469 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5470     /*
5471      * Kludge to ignore BEL characters
5472      */
5473     while (*message == '\007') message++;
5474
5475     /*
5476      * [HGM] engine debug message: ignore lines starting with '#' character
5477      */
5478     if(cps->debug && *message == '#') return;
5479
5480     /*
5481      * Look for book output
5482      */
5483     if (cps == &first && bookRequested) {
5484         if (message[0] == '\t' || message[0] == ' ') {
5485             /* Part of the book output is here; append it */
5486             strcat(bookOutput, message);
5487             strcat(bookOutput, "  \n");
5488             return;
5489         } else if (bookOutput[0] != NULLCHAR) {
5490             /* All of book output has arrived; display it */
5491             char *p = bookOutput;
5492             while (*p != NULLCHAR) {
5493                 if (*p == '\t') *p = ' ';
5494                 p++;
5495             }
5496             DisplayInformation(bookOutput);
5497             bookRequested = FALSE;
5498             /* Fall through to parse the current output */
5499         }
5500     }
5501
5502     /*
5503      * Look for machine move.
5504      */
5505     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5506         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5507     {
5508         /* This method is only useful on engines that support ping */
5509         if (cps->lastPing != cps->lastPong) {
5510           if (gameMode == BeginningOfGame) {
5511             /* Extra move from before last new; ignore */
5512             if (appData.debugMode) {
5513                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5514             }
5515           } else {
5516             if (appData.debugMode) {
5517                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5518                         cps->which, gameMode);
5519             }
5520
5521             SendToProgram("undo\n", cps);
5522           }
5523           return;
5524         }
5525
5526         switch (gameMode) {
5527           case BeginningOfGame:
5528             /* Extra move from before last reset; ignore */
5529             if (appData.debugMode) {
5530                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5531             }
5532             return;
5533
5534           case EndOfGame:
5535           case IcsIdle:
5536           default:
5537             /* Extra move after we tried to stop.  The mode test is
5538                not a reliable way of detecting this problem, but it's
5539                the best we can do on engines that don't support ping.
5540             */
5541             if (appData.debugMode) {
5542                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5543                         cps->which, gameMode);
5544             }
5545             SendToProgram("undo\n", cps);
5546             return;
5547
5548           case MachinePlaysWhite:
5549           case IcsPlayingWhite:
5550             machineWhite = TRUE;
5551             break;
5552
5553           case MachinePlaysBlack:
5554           case IcsPlayingBlack:
5555             machineWhite = FALSE;
5556             break;
5557
5558           case TwoMachinesPlay:
5559             machineWhite = (cps->twoMachinesColor[0] == 'w');
5560             break;
5561         }
5562         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5563             if (appData.debugMode) {
5564                 fprintf(debugFP,
5565                         "Ignoring move out of turn by %s, gameMode %d"
5566                         ", forwardMost %d\n",
5567                         cps->which, gameMode, forwardMostMove);
5568             }
5569             return;
5570         }
5571
5572     if (appData.debugMode) { int f = forwardMostMove;
5573         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5574                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5575     }
5576         if(cps->alphaRank) AlphaRank(machineMove, 4);
5577         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5578                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5579             /* Machine move could not be parsed; ignore it. */
5580             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5581                     machineMove, cps->which);
5582             DisplayError(buf1, 0);
5583             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5584                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5585             if (gameMode == TwoMachinesPlay) {
5586               GameEnds(machineWhite ? BlackWins : WhiteWins,
5587                        buf1, GE_XBOARD);
5588             }
5589             return;
5590         }
5591
5592         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5593         /* So we have to redo legality test with true e.p. status here,  */
5594         /* to make sure an illegal e.p. capture does not slip through,   */
5595         /* to cause a forfeit on a justified illegal-move complaint      */
5596         /* of the opponent.                                              */
5597         if( gameMode==TwoMachinesPlay && appData.testLegality
5598             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5599                                                               ) {
5600            ChessMove moveType;
5601            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5602                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5603                              fromY, fromX, toY, toX, promoChar);
5604             if (appData.debugMode) {
5605                 int i;
5606                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5607                     castlingRights[forwardMostMove][i], castlingRank[i]);
5608                 fprintf(debugFP, "castling rights\n");
5609             }
5610             if(moveType == IllegalMove) {
5611                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5612                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5613                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5614                            buf1, GE_XBOARD);
5615                 return;
5616            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5617            /* [HGM] Kludge to handle engines that send FRC-style castling
5618               when they shouldn't (like TSCP-Gothic) */
5619            switch(moveType) {
5620              case WhiteASideCastleFR:
5621              case BlackASideCastleFR:
5622                toX+=2;
5623                currentMoveString[2]++;
5624                break;
5625              case WhiteHSideCastleFR:
5626              case BlackHSideCastleFR:
5627                toX--;
5628                currentMoveString[2]--;
5629                break;
5630              default: ; // nothing to do, but suppresses warning of pedantic compilers
5631            }
5632         }
5633         hintRequested = FALSE;
5634         lastHint[0] = NULLCHAR;
5635         bookRequested = FALSE;
5636         /* Program may be pondering now */
5637         cps->maybeThinking = TRUE;
5638         if (cps->sendTime == 2) cps->sendTime = 1;
5639         if (cps->offeredDraw) cps->offeredDraw--;
5640
5641 #if ZIPPY
5642         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5643             first.initDone) {
5644           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5645           ics_user_moved = 1;
5646           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5647                 char buf[3*MSG_SIZ];
5648
5649                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5650                         programStats.score / 100.,
5651                         programStats.depth,
5652                         programStats.time / 100.,
5653                         (unsigned int)programStats.nodes,
5654                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5655                         programStats.movelist);
5656                 SendToICS(buf);
5657 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5658           }
5659         }
5660 #endif
5661         /* currentMoveString is set as a side-effect of ParseOneMove */
5662         strcpy(machineMove, currentMoveString);
5663         strcat(machineMove, "\n");
5664         strcpy(moveList[forwardMostMove], machineMove);
5665
5666         /* [AS] Save move info and clear stats for next move */
5667         pvInfoList[ forwardMostMove ].score = programStats.score;
5668         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5669         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5670         ClearProgramStats();
5671         thinkOutput[0] = NULLCHAR;
5672         hiddenThinkOutputState = 0;
5673
5674         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5675
5676         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5677         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5678             int count = 0;
5679
5680             while( count < adjudicateLossPlies ) {
5681                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5682
5683                 if( count & 1 ) {
5684                     score = -score; /* Flip score for winning side */
5685                 }
5686
5687                 if( score > adjudicateLossThreshold ) {
5688                     break;
5689                 }
5690
5691                 count++;
5692             }
5693
5694             if( count >= adjudicateLossPlies ) {
5695                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5696
5697                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5698                     "Xboard adjudication", 
5699                     GE_XBOARD );
5700
5701                 return;
5702             }
5703         }
5704
5705         if( gameMode == TwoMachinesPlay ) {
5706           // [HGM] some adjudications useful with buggy engines
5707             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5708           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5709
5710
5711             if( appData.testLegality )
5712             {   /* [HGM] Some more adjudications for obstinate engines */
5713                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5714                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5715                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5716                 static int moveCount = 6;
5717                 ChessMove result;
5718                 char *reason = NULL;
5719
5720                 /* Count what is on board. */
5721                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5722                 {   ChessSquare p = boards[forwardMostMove][i][j];
5723                     int m=i;
5724
5725                     switch((int) p)
5726                     {   /* count B,N,R and other of each side */
5727                         case WhiteKing:
5728                         case BlackKing:
5729                              NrK++; break; // [HGM] atomic: count Kings
5730                         case WhiteKnight:
5731                              NrWN++; break;
5732                         case WhiteBishop:
5733                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5734                              bishopsColor |= 1 << ((i^j)&1);
5735                              NrWB++; break;
5736                         case BlackKnight:
5737                              NrBN++; break;
5738                         case BlackBishop:
5739                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5740                              bishopsColor |= 1 << ((i^j)&1);
5741                              NrBB++; break;
5742                         case WhiteRook:
5743                              NrWR++; break;
5744                         case BlackRook:
5745                              NrBR++; break;
5746                         case WhiteQueen:
5747                              NrWQ++; break;
5748                         case BlackQueen:
5749                              NrBQ++; break;
5750                         case EmptySquare: 
5751                              break;
5752                         case BlackPawn:
5753                              m = 7-i;
5754                         case WhitePawn:
5755                              PawnAdvance += m; NrPawns++;
5756                     }
5757                     NrPieces += (p != EmptySquare);
5758                     NrW += ((int)p < (int)BlackPawn);
5759                     if(gameInfo.variant == VariantXiangqi && 
5760                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5761                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5762                         NrW -= ((int)p < (int)BlackPawn);
5763                     }
5764                 }
5765
5766                 /* Some material-based adjudications that have to be made before stalemate test */
5767                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5768                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5769                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5770                      if(appData.checkMates) {
5771                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5772                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5773                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5774                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5775                          return;
5776                      }
5777                 }
5778
5779                 /* Bare King in Shatranj (loses) or Losers (wins) */
5780                 if( NrW == 1 || NrPieces - NrW == 1) {
5781                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5782                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5783                      if(appData.checkMates) {
5784                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5785                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5786                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5787                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5788                          return;
5789                      }
5790                   } else
5791                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5792                   {    /* bare King */
5793                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5794                         if(appData.checkMates) {
5795                             /* but only adjudicate if adjudication enabled */
5796                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5797                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5798                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5799                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5800                             return;
5801                         }
5802                   }
5803                 } else bare = 1;
5804
5805
5806             // don't wait for engine to announce game end if we can judge ourselves
5807             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5808                                        castlingRights[forwardMostMove]) ) {
5809               case MT_CHECK:
5810                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5811                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5812                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5813                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5814                             checkCnt++;
5815                         if(checkCnt >= 2) {
5816                             reason = "Xboard adjudication: 3rd check";
5817                             epStatus[forwardMostMove] = EP_CHECKMATE;
5818                             break;
5819                         }
5820                     }
5821                 }
5822               case MT_NONE:
5823               default:
5824                 break;
5825               case MT_STALEMATE:
5826               case MT_STAINMATE:
5827                 reason = "Xboard adjudication: Stalemate";
5828                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5829                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5830                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5831                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5832                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5833                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5834                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5835                                                                         EP_CHECKMATE : EP_WINS);
5836                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5837                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5838                 }
5839                 break;
5840               case MT_CHECKMATE:
5841                 reason = "Xboard adjudication: Checkmate";
5842                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5843                 break;
5844             }
5845
5846                 switch(i = epStatus[forwardMostMove]) {
5847                     case EP_STALEMATE:
5848                         result = GameIsDrawn; break;
5849                     case EP_CHECKMATE:
5850                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5851                     case EP_WINS:
5852                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5853                     default:
5854                         result = (ChessMove) 0;
5855                 }
5856                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5857                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5858                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5859                     GameEnds( result, reason, GE_XBOARD );
5860                     return;
5861                 }
5862
5863                 /* Next absolutely insufficient mating material. */
5864                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5865                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5866                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5867                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5868                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5869
5870                      /* always flag draws, for judging claims */
5871                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5872
5873                      if(appData.materialDraws) {
5874                          /* but only adjudicate them if adjudication enabled */
5875                          SendToProgram("force\n", cps->other); // suppress reply
5876                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5877                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5878                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5879                          return;
5880                      }
5881                 }
5882
5883                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5884                 if(NrPieces == 4 && 
5885                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5886                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5887                    || NrWN==2 || NrBN==2     /* KNNK */
5888                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5889                   ) ) {
5890                      if(--moveCount < 0 && appData.trivialDraws)
5891                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5892                           SendToProgram("force\n", cps->other); // suppress reply
5893                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5894                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5895                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5896                           return;
5897                      }
5898                 } else moveCount = 6;
5899             }
5900           }
5901           
5902           if (appData.debugMode) { int i;
5903             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5904                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5905                     appData.drawRepeats);
5906             for( i=forwardMostMove; i>=backwardMostMove; i-- )
5907               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5908             
5909           }
5910
5911                 /* Check for rep-draws */
5912                 count = 0;
5913                 for(k = forwardMostMove-2;
5914                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5915                         epStatus[k] < EP_UNKNOWN &&
5916                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5917                     k-=2)
5918                 {   int rights=0;
5919                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5920                         /* compare castling rights */
5921                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5922                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5923                                 rights++; /* King lost rights, while rook still had them */
5924                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5925                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5926                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5927                                    rights++; /* but at least one rook lost them */
5928                         }
5929                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5930                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5931                                 rights++; 
5932                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5933                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5934                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5935                                    rights++;
5936                         }
5937                         if( rights == 0 && ++count > appData.drawRepeats-2
5938                             && appData.drawRepeats > 1) {
5939                              /* adjudicate after user-specified nr of repeats */
5940                              SendToProgram("force\n", cps->other); // suppress reply
5941                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5942                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5943                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5944                                 // [HGM] xiangqi: check for forbidden perpetuals
5945                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5946                                 for(m=forwardMostMove; m>k; m-=2) {
5947                                     if(MateTest(boards[m], PosFlags(m), 
5948                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5949                                         ourPerpetual = 0; // the current mover did not always check
5950                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5951                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5952                                         hisPerpetual = 0; // the opponent did not always check
5953                                 }
5954                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5955                                                                         ourPerpetual, hisPerpetual);
5956                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5957                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5958                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5959                                     return;
5960                                 }
5961                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5962                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5963                                 // Now check for perpetual chases
5964                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5965                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5966                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5967                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5968                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5969                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5970                                         return;
5971                                     }
5972                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5973                                         break; // Abort repetition-checking loop.
5974                                 }
5975                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5976                              }
5977                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5978                              return;
5979                         }
5980                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5981                              epStatus[forwardMostMove] = EP_REP_DRAW;
5982                     }
5983                 }
5984
5985                 /* Now we test for 50-move draws. Determine ply count */
5986                 count = forwardMostMove;
5987                 /* look for last irreversble move */
5988                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5989                     count--;
5990                 /* if we hit starting position, add initial plies */
5991                 if( count == backwardMostMove )
5992                     count -= initialRulePlies;
5993                 count = forwardMostMove - count; 
5994                 if( count >= 100)
5995                          epStatus[forwardMostMove] = EP_RULE_DRAW;
5996                          /* this is used to judge if draw claims are legal */
5997                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
5998                          SendToProgram("force\n", cps->other); // suppress reply
5999                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6000                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6001                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6002                          return;
6003                 }
6004
6005                 /* if draw offer is pending, treat it as a draw claim
6006                  * when draw condition present, to allow engines a way to
6007                  * claim draws before making their move to avoid a race
6008                  * condition occurring after their move
6009                  */
6010                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6011                          char *p = NULL;
6012                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6013                              p = "Draw claim: 50-move rule";
6014                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6015                              p = "Draw claim: 3-fold repetition";
6016                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6017                              p = "Draw claim: insufficient mating material";
6018                          if( p != NULL ) {
6019                              SendToProgram("force\n", cps->other); // suppress reply
6020                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6021                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6022                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6023                              return;
6024                          }
6025                 }
6026
6027
6028                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6029                     SendToProgram("force\n", cps->other); // suppress reply
6030                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6031                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6032
6033                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6034
6035                     return;
6036                 }
6037         }
6038
6039         bookHit = NULL;
6040         if (gameMode == TwoMachinesPlay) {
6041             /* [HGM] relaying draw offers moved to after reception of move */
6042             /* and interpreting offer as claim if it brings draw condition */
6043             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6044                 SendToProgram("draw\n", cps->other);
6045             }
6046             if (cps->other->sendTime) {
6047                 SendTimeRemaining(cps->other,
6048                                   cps->other->twoMachinesColor[0] == 'w');
6049             }
6050             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6051             if (firstMove && !bookHit) {
6052                 firstMove = FALSE;
6053                 if (cps->other->useColors) {
6054                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6055                 }
6056                 SendToProgram("go\n", cps->other);
6057             }
6058             cps->other->maybeThinking = TRUE;
6059         }
6060
6061         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6062         
6063         if (!pausing && appData.ringBellAfterMoves) {
6064             RingBell();
6065         }
6066
6067         /* 
6068          * Reenable menu items that were disabled while
6069          * machine was thinking
6070          */
6071         if (gameMode != TwoMachinesPlay)
6072             SetUserThinkingEnables();
6073
6074         // [HGM] book: after book hit opponent has received move and is now in force mode
6075         // force the book reply into it, and then fake that it outputted this move by jumping
6076         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6077         if(bookHit) {
6078                 static char bookMove[MSG_SIZ]; // a bit generous?
6079
6080                 strcpy(bookMove, "move ");
6081                 strcat(bookMove, bookHit);
6082                 message = bookMove;
6083                 cps = cps->other;
6084                 programStats.nodes = programStats.depth = programStats.time = 
6085                 programStats.score = programStats.got_only_move = 0;
6086                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6087
6088                 if(cps->lastPing != cps->lastPong) {
6089                     savedMessage = message; // args for deferred call
6090                     savedState = cps;
6091                     ScheduleDelayedEvent(DeferredBookMove, 10);
6092                     return;
6093                 }
6094                 goto FakeBookMove;
6095         }
6096
6097         return;
6098     }
6099
6100     /* Set special modes for chess engines.  Later something general
6101      *  could be added here; for now there is just one kludge feature,
6102      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6103      *  when "xboard" is given as an interactive command.
6104      */
6105     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6106         cps->useSigint = FALSE;
6107         cps->useSigterm = FALSE;
6108     }
6109     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6110       ParseFeatures(message+8, cps);
6111       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6112     }
6113
6114     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6115      * want this, I was asked to put it in, and obliged.
6116      */
6117     if (!strncmp(message, "setboard ", 9)) {
6118         Board initial_position; int i;
6119
6120         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6121
6122         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6123             DisplayError(_("Bad FEN received from engine"), 0);
6124             return ;
6125         } else {
6126            Reset(FALSE, FALSE);
6127            CopyBoard(boards[0], initial_position);
6128            initialRulePlies = FENrulePlies;
6129            epStatus[0] = FENepStatus;
6130            for( i=0; i<nrCastlingRights; i++ )
6131                 castlingRights[0][i] = FENcastlingRights[i];
6132            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6133            else gameMode = MachinePlaysBlack;                 
6134            DrawPosition(FALSE, boards[currentMove]);
6135         }
6136         return;
6137     }
6138
6139     /*
6140      * Look for communication commands
6141      */
6142     if (!strncmp(message, "telluser ", 9)) {
6143         DisplayNote(message + 9);
6144         return;
6145     }
6146     if (!strncmp(message, "tellusererror ", 14)) {
6147         DisplayError(message + 14, 0);
6148         return;
6149     }
6150     if (!strncmp(message, "tellopponent ", 13)) {
6151       if (appData.icsActive) {
6152         if (loggedOn) {
6153           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6154           SendToICS(buf1);
6155         }
6156       } else {
6157         DisplayNote(message + 13);
6158       }
6159       return;
6160     }
6161     if (!strncmp(message, "tellothers ", 11)) {
6162       if (appData.icsActive) {
6163         if (loggedOn) {
6164           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6165           SendToICS(buf1);
6166         }
6167       }
6168       return;
6169     }
6170     if (!strncmp(message, "tellall ", 8)) {
6171       if (appData.icsActive) {
6172         if (loggedOn) {
6173           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6174           SendToICS(buf1);
6175         }
6176       } else {
6177         DisplayNote(message + 8);
6178       }
6179       return;
6180     }
6181     if (strncmp(message, "warning", 7) == 0) {
6182         /* Undocumented feature, use tellusererror in new code */
6183         DisplayError(message, 0);
6184         return;
6185     }
6186     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6187         strcpy(realname, cps->tidy);
6188         strcat(realname, " query");
6189         AskQuestion(realname, buf2, buf1, cps->pr);
6190         return;
6191     }
6192     /* Commands from the engine directly to ICS.  We don't allow these to be 
6193      *  sent until we are logged on. Crafty kibitzes have been known to 
6194      *  interfere with the login process.
6195      */
6196     if (loggedOn) {
6197         if (!strncmp(message, "tellics ", 8)) {
6198             SendToICS(message + 8);
6199             SendToICS("\n");
6200             return;
6201         }
6202         if (!strncmp(message, "tellicsnoalias ", 15)) {
6203             SendToICS(ics_prefix);
6204             SendToICS(message + 15);
6205             SendToICS("\n");
6206             return;
6207         }
6208         /* The following are for backward compatibility only */
6209         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6210             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6211             SendToICS(ics_prefix);
6212             SendToICS(message);
6213             SendToICS("\n");
6214             return;
6215         }
6216     }
6217     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6218         return;
6219     }
6220     /*
6221      * If the move is illegal, cancel it and redraw the board.
6222      * Also deal with other error cases.  Matching is rather loose
6223      * here to accommodate engines written before the spec.
6224      */
6225     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6226         strncmp(message, "Error", 5) == 0) {
6227         if (StrStr(message, "name") || 
6228             StrStr(message, "rating") || StrStr(message, "?") ||
6229             StrStr(message, "result") || StrStr(message, "board") ||
6230             StrStr(message, "bk") || StrStr(message, "computer") ||
6231             StrStr(message, "variant") || StrStr(message, "hint") ||
6232             StrStr(message, "random") || StrStr(message, "depth") ||
6233             StrStr(message, "accepted")) {
6234             return;
6235         }
6236         if (StrStr(message, "protover")) {
6237           /* Program is responding to input, so it's apparently done
6238              initializing, and this error message indicates it is
6239              protocol version 1.  So we don't need to wait any longer
6240              for it to initialize and send feature commands. */
6241           FeatureDone(cps, 1);
6242           cps->protocolVersion = 1;
6243           return;
6244         }
6245         cps->maybeThinking = FALSE;
6246
6247         if (StrStr(message, "draw")) {
6248             /* Program doesn't have "draw" command */
6249             cps->sendDrawOffers = 0;
6250             return;
6251         }
6252         if (cps->sendTime != 1 &&
6253             (StrStr(message, "time") || StrStr(message, "otim"))) {
6254           /* Program apparently doesn't have "time" or "otim" command */
6255           cps->sendTime = 0;
6256           return;
6257         }
6258         if (StrStr(message, "analyze")) {
6259             cps->analysisSupport = FALSE;
6260             cps->analyzing = FALSE;
6261             Reset(FALSE, TRUE);
6262             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6263             DisplayError(buf2, 0);
6264             return;
6265         }
6266         if (StrStr(message, "(no matching move)st")) {
6267           /* Special kludge for GNU Chess 4 only */
6268           cps->stKludge = TRUE;
6269           SendTimeControl(cps, movesPerSession, timeControl,
6270                           timeIncrement, appData.searchDepth,
6271                           searchTime);
6272           return;
6273         }
6274         if (StrStr(message, "(no matching move)sd")) {
6275           /* Special kludge for GNU Chess 4 only */
6276           cps->sdKludge = TRUE;
6277           SendTimeControl(cps, movesPerSession, timeControl,
6278                           timeIncrement, appData.searchDepth,
6279                           searchTime);
6280           return;
6281         }
6282         if (!StrStr(message, "llegal")) {
6283             return;
6284         }
6285         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6286             gameMode == IcsIdle) return;
6287         if (forwardMostMove <= backwardMostMove) return;
6288         if (pausing) PauseEvent();
6289       if(appData.forceIllegal) {
6290             // [HGM] illegal: machine refused move; force position after move into it
6291           SendToProgram("force\n", cps);
6292           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6293                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6294                 // when black is to move, while there might be nothing on a2 or black
6295                 // might already have the move. So send the board as if white has the move.
6296                 // But first we must change the stm of the engine, as it refused the last move
6297                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6298                 if(WhiteOnMove(forwardMostMove)) {
6299                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6300                     SendBoard(cps, forwardMostMove); // kludgeless board
6301                 } else {
6302                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6303                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6304                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6305                 }
6306           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6307             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6308                  gameMode == TwoMachinesPlay)
6309               SendToProgram("go\n", cps);
6310             return;
6311       } else
6312         if (gameMode == PlayFromGameFile) {
6313             /* Stop reading this game file */
6314             gameMode = EditGame;
6315             ModeHighlight();
6316         }
6317         currentMove = --forwardMostMove;
6318         DisplayMove(currentMove-1); /* before DisplayMoveError */
6319         SwitchClocks();
6320         DisplayBothClocks();
6321         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6322                 parseList[currentMove], cps->which);
6323         DisplayMoveError(buf1);
6324         DrawPosition(FALSE, boards[currentMove]);
6325
6326         /* [HGM] illegal-move claim should forfeit game when Xboard */
6327         /* only passes fully legal moves                            */
6328         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6329             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6330                                 "False illegal-move claim", GE_XBOARD );
6331         }
6332         return;
6333     }
6334     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6335         /* Program has a broken "time" command that
6336            outputs a string not ending in newline.
6337            Don't use it. */
6338         cps->sendTime = 0;
6339     }
6340     
6341     /*
6342      * If chess program startup fails, exit with an error message.
6343      * Attempts to recover here are futile.
6344      */
6345     if ((StrStr(message, "unknown host") != NULL)
6346         || (StrStr(message, "No remote directory") != NULL)
6347         || (StrStr(message, "not found") != NULL)
6348         || (StrStr(message, "No such file") != NULL)
6349         || (StrStr(message, "can't alloc") != NULL)
6350         || (StrStr(message, "Permission denied") != NULL)) {
6351
6352         cps->maybeThinking = FALSE;
6353         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6354                 cps->which, cps->program, cps->host, message);
6355         RemoveInputSource(cps->isr);
6356         DisplayFatalError(buf1, 0, 1);
6357         return;
6358     }
6359     
6360     /* 
6361      * Look for hint output
6362      */
6363     if (sscanf(message, "Hint: %s", buf1) == 1) {
6364         if (cps == &first && hintRequested) {
6365             hintRequested = FALSE;
6366             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6367                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6368                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6369                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6370                                     fromY, fromX, toY, toX, promoChar, buf1);
6371                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6372                 DisplayInformation(buf2);
6373             } else {
6374                 /* Hint move could not be parsed!? */
6375               snprintf(buf2, sizeof(buf2),
6376                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6377                         buf1, cps->which);
6378                 DisplayError(buf2, 0);
6379             }
6380         } else {
6381             strcpy(lastHint, buf1);
6382         }
6383         return;
6384     }
6385
6386     /*
6387      * Ignore other messages if game is not in progress
6388      */
6389     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6390         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6391
6392     /*
6393      * look for win, lose, draw, or draw offer
6394      */
6395     if (strncmp(message, "1-0", 3) == 0) {
6396         char *p, *q, *r = "";
6397         p = strchr(message, '{');
6398         if (p) {
6399             q = strchr(p, '}');
6400             if (q) {
6401                 *q = NULLCHAR;
6402                 r = p + 1;
6403             }
6404         }
6405         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6406         return;
6407     } else if (strncmp(message, "0-1", 3) == 0) {
6408         char *p, *q, *r = "";
6409         p = strchr(message, '{');
6410         if (p) {
6411             q = strchr(p, '}');
6412             if (q) {
6413                 *q = NULLCHAR;
6414                 r = p + 1;
6415             }
6416         }
6417         /* Kludge for Arasan 4.1 bug */
6418         if (strcmp(r, "Black resigns") == 0) {
6419             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6420             return;
6421         }
6422         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6423         return;
6424     } else if (strncmp(message, "1/2", 3) == 0) {
6425         char *p, *q, *r = "";
6426         p = strchr(message, '{');
6427         if (p) {
6428             q = strchr(p, '}');
6429             if (q) {
6430                 *q = NULLCHAR;
6431                 r = p + 1;
6432             }
6433         }
6434             
6435         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6436         return;
6437
6438     } else if (strncmp(message, "White resign", 12) == 0) {
6439         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6440         return;
6441     } else if (strncmp(message, "Black resign", 12) == 0) {
6442         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6443         return;
6444     } else if (strncmp(message, "White matches", 13) == 0 ||
6445                strncmp(message, "Black matches", 13) == 0   ) {
6446         /* [HGM] ignore GNUShogi noises */
6447         return;
6448     } else if (strncmp(message, "White", 5) == 0 &&
6449                message[5] != '(' &&
6450                StrStr(message, "Black") == NULL) {
6451         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6452         return;
6453     } else if (strncmp(message, "Black", 5) == 0 &&
6454                message[5] != '(') {
6455         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6456         return;
6457     } else if (strcmp(message, "resign") == 0 ||
6458                strcmp(message, "computer resigns") == 0) {
6459         switch (gameMode) {
6460           case MachinePlaysBlack:
6461           case IcsPlayingBlack:
6462             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6463             break;
6464           case MachinePlaysWhite:
6465           case IcsPlayingWhite:
6466             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6467             break;
6468           case TwoMachinesPlay:
6469             if (cps->twoMachinesColor[0] == 'w')
6470               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6471             else
6472               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6473             break;
6474           default:
6475             /* can't happen */
6476             break;
6477         }
6478         return;
6479     } else if (strncmp(message, "opponent mates", 14) == 0) {
6480         switch (gameMode) {
6481           case MachinePlaysBlack:
6482           case IcsPlayingBlack:
6483             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6484             break;
6485           case MachinePlaysWhite:
6486           case IcsPlayingWhite:
6487             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6488             break;
6489           case TwoMachinesPlay:
6490             if (cps->twoMachinesColor[0] == 'w')
6491               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6492             else
6493               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6494             break;
6495           default:
6496             /* can't happen */
6497             break;
6498         }
6499         return;
6500     } else if (strncmp(message, "computer mates", 14) == 0) {
6501         switch (gameMode) {
6502           case MachinePlaysBlack:
6503           case IcsPlayingBlack:
6504             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6505             break;
6506           case MachinePlaysWhite:
6507           case IcsPlayingWhite:
6508             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6509             break;
6510           case TwoMachinesPlay:
6511             if (cps->twoMachinesColor[0] == 'w')
6512               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6513             else
6514               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6515             break;
6516           default:
6517             /* can't happen */
6518             break;
6519         }
6520         return;
6521     } else if (strncmp(message, "checkmate", 9) == 0) {
6522         if (WhiteOnMove(forwardMostMove)) {
6523             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6524         } else {
6525             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6526         }
6527         return;
6528     } else if (strstr(message, "Draw") != NULL ||
6529                strstr(message, "game is a draw") != NULL) {
6530         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6531         return;
6532     } else if (strstr(message, "offer") != NULL &&
6533                strstr(message, "draw") != NULL) {
6534 #if ZIPPY
6535         if (appData.zippyPlay && first.initDone) {
6536             /* Relay offer to ICS */
6537             SendToICS(ics_prefix);
6538             SendToICS("draw\n");
6539         }
6540 #endif
6541         cps->offeredDraw = 2; /* valid until this engine moves twice */
6542         if (gameMode == TwoMachinesPlay) {
6543             if (cps->other->offeredDraw) {
6544                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6545             /* [HGM] in two-machine mode we delay relaying draw offer      */
6546             /* until after we also have move, to see if it is really claim */
6547             }
6548         } else if (gameMode == MachinePlaysWhite ||
6549                    gameMode == MachinePlaysBlack) {
6550           if (userOfferedDraw) {
6551             DisplayInformation(_("Machine accepts your draw offer"));
6552             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6553           } else {
6554             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6555           }
6556         }
6557     }
6558
6559     
6560     /*
6561      * Look for thinking output
6562      */
6563     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6564           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6565                                 ) {
6566         int plylev, mvleft, mvtot, curscore, time;
6567         char mvname[MOVE_LEN];
6568         u64 nodes; // [DM]
6569         char plyext;
6570         int ignore = FALSE;
6571         int prefixHint = FALSE;
6572         mvname[0] = NULLCHAR;
6573
6574         switch (gameMode) {
6575           case MachinePlaysBlack:
6576           case IcsPlayingBlack:
6577             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6578             break;
6579           case MachinePlaysWhite:
6580           case IcsPlayingWhite:
6581             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6582             break;
6583           case AnalyzeMode:
6584           case AnalyzeFile:
6585             break;
6586           case IcsObserving: /* [DM] icsEngineAnalyze */
6587             if (!appData.icsEngineAnalyze) ignore = TRUE;
6588             break;
6589           case TwoMachinesPlay:
6590             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6591                 ignore = TRUE;
6592             }
6593             break;
6594           default:
6595             ignore = TRUE;
6596             break;
6597         }
6598
6599         if (!ignore) {
6600             buf1[0] = NULLCHAR;
6601             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6602                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6603
6604                 if (plyext != ' ' && plyext != '\t') {
6605                     time *= 100;
6606                 }
6607
6608                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6609                 if( cps->scoreIsAbsolute && 
6610                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6611                 {
6612                     curscore = -curscore;
6613                 }
6614
6615
6616                 programStats.depth = plylev;
6617                 programStats.nodes = nodes;
6618                 programStats.time = time;
6619                 programStats.score = curscore;
6620                 programStats.got_only_move = 0;
6621
6622                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6623                         int ticklen;
6624
6625                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6626                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6627                         if(WhiteOnMove(forwardMostMove)) 
6628                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6629                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6630                 }
6631
6632                 /* Buffer overflow protection */
6633                 if (buf1[0] != NULLCHAR) {
6634                     if (strlen(buf1) >= sizeof(programStats.movelist)
6635                         && appData.debugMode) {
6636                         fprintf(debugFP,
6637                                 "PV is too long; using the first %d bytes.\n",
6638                                 sizeof(programStats.movelist) - 1);
6639                     }
6640
6641                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6642                 } else {
6643                     sprintf(programStats.movelist, " no PV\n");
6644                 }
6645
6646                 if (programStats.seen_stat) {
6647                     programStats.ok_to_send = 1;
6648                 }
6649
6650                 if (strchr(programStats.movelist, '(') != NULL) {
6651                     programStats.line_is_book = 1;
6652                     programStats.nr_moves = 0;
6653                     programStats.moves_left = 0;
6654                 } else {
6655                     programStats.line_is_book = 0;
6656                 }
6657
6658                 SendProgramStatsToFrontend( cps, &programStats );
6659
6660                 /* 
6661                     [AS] Protect the thinkOutput buffer from overflow... this
6662                     is only useful if buf1 hasn't overflowed first!
6663                 */
6664                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6665                         plylev, 
6666                         (gameMode == TwoMachinesPlay ?
6667                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6668                         ((double) curscore) / 100.0,
6669                         prefixHint ? lastHint : "",
6670                         prefixHint ? " " : "" );
6671
6672                 if( buf1[0] != NULLCHAR ) {
6673                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6674
6675                     if( strlen(buf1) > max_len ) {
6676                         if( appData.debugMode) {
6677                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6678                         }
6679                         buf1[max_len+1] = '\0';
6680                     }
6681
6682                     strcat( thinkOutput, buf1 );
6683                 }
6684
6685                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6686                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6687                     DisplayMove(currentMove - 1);
6688                     DisplayAnalysis();
6689                 }
6690                 return;
6691
6692             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6693                 /* crafty (9.25+) says "(only move) <move>"
6694                  * if there is only 1 legal move
6695                  */
6696                 sscanf(p, "(only move) %s", buf1);
6697                 sprintf(thinkOutput, "%s (only move)", buf1);
6698                 sprintf(programStats.movelist, "%s (only move)", buf1);
6699                 programStats.depth = 1;
6700                 programStats.nr_moves = 1;
6701                 programStats.moves_left = 1;
6702                 programStats.nodes = 1;
6703                 programStats.time = 1;
6704                 programStats.got_only_move = 1;
6705
6706                 /* Not really, but we also use this member to
6707                    mean "line isn't going to change" (Crafty
6708                    isn't searching, so stats won't change) */
6709                 programStats.line_is_book = 1;
6710
6711                 SendProgramStatsToFrontend( cps, &programStats );
6712                 
6713                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6714                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6715                     DisplayMove(currentMove - 1);
6716                     DisplayAnalysis();
6717                 }
6718                 return;
6719             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6720                               &time, &nodes, &plylev, &mvleft,
6721                               &mvtot, mvname) >= 5) {
6722                 /* The stat01: line is from Crafty (9.29+) in response
6723                    to the "." command */
6724                 programStats.seen_stat = 1;
6725                 cps->maybeThinking = TRUE;
6726
6727                 if (programStats.got_only_move || !appData.periodicUpdates)
6728                   return;
6729
6730                 programStats.depth = plylev;
6731                 programStats.time = time;
6732                 programStats.nodes = nodes;
6733                 programStats.moves_left = mvleft;
6734                 programStats.nr_moves = mvtot;
6735                 strcpy(programStats.move_name, mvname);
6736                 programStats.ok_to_send = 1;
6737                 programStats.movelist[0] = '\0';
6738
6739                 SendProgramStatsToFrontend( cps, &programStats );
6740
6741                 DisplayAnalysis();
6742                 return;
6743
6744             } else if (strncmp(message,"++",2) == 0) {
6745                 /* Crafty 9.29+ outputs this */
6746                 programStats.got_fail = 2;
6747                 return;
6748
6749             } else if (strncmp(message,"--",2) == 0) {
6750                 /* Crafty 9.29+ outputs this */
6751                 programStats.got_fail = 1;
6752                 return;
6753
6754             } else if (thinkOutput[0] != NULLCHAR &&
6755                        strncmp(message, "    ", 4) == 0) {
6756                 unsigned message_len;
6757
6758                 p = message;
6759                 while (*p && *p == ' ') p++;
6760
6761                 message_len = strlen( p );
6762
6763                 /* [AS] Avoid buffer overflow */
6764                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6765                     strcat(thinkOutput, " ");
6766                     strcat(thinkOutput, p);
6767                 }
6768
6769                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6770                     strcat(programStats.movelist, " ");
6771                     strcat(programStats.movelist, p);
6772                 }
6773
6774                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6775                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6776                     DisplayMove(currentMove - 1);
6777                     DisplayAnalysis();
6778                 }
6779                 return;
6780             }
6781         }
6782         else {
6783             buf1[0] = NULLCHAR;
6784
6785             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6786                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6787             {
6788                 ChessProgramStats cpstats;
6789
6790                 if (plyext != ' ' && plyext != '\t') {
6791                     time *= 100;
6792                 }
6793
6794                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6795                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6796                     curscore = -curscore;
6797                 }
6798
6799                 cpstats.depth = plylev;
6800                 cpstats.nodes = nodes;
6801                 cpstats.time = time;
6802                 cpstats.score = curscore;
6803                 cpstats.got_only_move = 0;
6804                 cpstats.movelist[0] = '\0';
6805
6806                 if (buf1[0] != NULLCHAR) {
6807                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6808                 }
6809
6810                 cpstats.ok_to_send = 0;
6811                 cpstats.line_is_book = 0;
6812                 cpstats.nr_moves = 0;
6813                 cpstats.moves_left = 0;
6814
6815                 SendProgramStatsToFrontend( cps, &cpstats );
6816             }
6817         }
6818     }
6819 }
6820
6821
6822 /* Parse a game score from the character string "game", and
6823    record it as the history of the current game.  The game
6824    score is NOT assumed to start from the standard position. 
6825    The display is not updated in any way.
6826    */
6827 void
6828 ParseGameHistory(game)
6829      char *game;
6830 {
6831     ChessMove moveType;
6832     int fromX, fromY, toX, toY, boardIndex;
6833     char promoChar;
6834     char *p, *q;
6835     char buf[MSG_SIZ];
6836
6837     if (appData.debugMode)
6838       fprintf(debugFP, "Parsing game history: %s\n", game);
6839
6840     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6841     gameInfo.site = StrSave(appData.icsHost);
6842     gameInfo.date = PGNDate();
6843     gameInfo.round = StrSave("-");
6844
6845     /* Parse out names of players */
6846     while (*game == ' ') game++;
6847     p = buf;
6848     while (*game != ' ') *p++ = *game++;
6849     *p = NULLCHAR;
6850     gameInfo.white = StrSave(buf);
6851     while (*game == ' ') game++;
6852     p = buf;
6853     while (*game != ' ' && *game != '\n') *p++ = *game++;
6854     *p = NULLCHAR;
6855     gameInfo.black = StrSave(buf);
6856
6857     /* Parse moves */
6858     boardIndex = blackPlaysFirst ? 1 : 0;
6859     yynewstr(game);
6860     for (;;) {
6861         yyboardindex = boardIndex;
6862         moveType = (ChessMove) yylex();
6863         switch (moveType) {
6864           case IllegalMove:             /* maybe suicide chess, etc. */
6865   if (appData.debugMode) {
6866     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6867     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6868     setbuf(debugFP, NULL);
6869   }
6870           case WhitePromotionChancellor:
6871           case BlackPromotionChancellor:
6872           case WhitePromotionArchbishop:
6873           case BlackPromotionArchbishop:
6874           case WhitePromotionQueen:
6875           case BlackPromotionQueen:
6876           case WhitePromotionRook:
6877           case BlackPromotionRook:
6878           case WhitePromotionBishop:
6879           case BlackPromotionBishop:
6880           case WhitePromotionKnight:
6881           case BlackPromotionKnight:
6882           case WhitePromotionKing:
6883           case BlackPromotionKing:
6884           case NormalMove:
6885           case WhiteCapturesEnPassant:
6886           case BlackCapturesEnPassant:
6887           case WhiteKingSideCastle:
6888           case WhiteQueenSideCastle:
6889           case BlackKingSideCastle:
6890           case BlackQueenSideCastle:
6891           case WhiteKingSideCastleWild:
6892           case WhiteQueenSideCastleWild:
6893           case BlackKingSideCastleWild:
6894           case BlackQueenSideCastleWild:
6895           /* PUSH Fabien */
6896           case WhiteHSideCastleFR:
6897           case WhiteASideCastleFR:
6898           case BlackHSideCastleFR:
6899           case BlackASideCastleFR:
6900           /* POP Fabien */
6901             fromX = currentMoveString[0] - AAA;
6902             fromY = currentMoveString[1] - ONE;
6903             toX = currentMoveString[2] - AAA;
6904             toY = currentMoveString[3] - ONE;
6905             promoChar = currentMoveString[4];
6906             break;
6907           case WhiteDrop:
6908           case BlackDrop:
6909             fromX = moveType == WhiteDrop ?
6910               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6911             (int) CharToPiece(ToLower(currentMoveString[0]));
6912             fromY = DROP_RANK;
6913             toX = currentMoveString[2] - AAA;
6914             toY = currentMoveString[3] - ONE;
6915             promoChar = NULLCHAR;
6916             break;
6917           case AmbiguousMove:
6918             /* bug? */
6919             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6920   if (appData.debugMode) {
6921     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6922     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6923     setbuf(debugFP, NULL);
6924   }
6925             DisplayError(buf, 0);
6926             return;
6927           case ImpossibleMove:
6928             /* bug? */
6929             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6930   if (appData.debugMode) {
6931     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6932     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6933     setbuf(debugFP, NULL);
6934   }
6935             DisplayError(buf, 0);
6936             return;
6937           case (ChessMove) 0:   /* end of file */
6938             if (boardIndex < backwardMostMove) {
6939                 /* Oops, gap.  How did that happen? */
6940                 DisplayError(_("Gap in move list"), 0);
6941                 return;
6942             }
6943             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6944             if (boardIndex > forwardMostMove) {
6945                 forwardMostMove = boardIndex;
6946             }
6947             return;
6948           case ElapsedTime:
6949             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6950                 strcat(parseList[boardIndex-1], " ");
6951                 strcat(parseList[boardIndex-1], yy_text);
6952             }
6953             continue;
6954           case Comment:
6955           case PGNTag:
6956           case NAG:
6957           default:
6958             /* ignore */
6959             continue;
6960           case WhiteWins:
6961           case BlackWins:
6962           case GameIsDrawn:
6963           case GameUnfinished:
6964             if (gameMode == IcsExamining) {
6965                 if (boardIndex < backwardMostMove) {
6966                     /* Oops, gap.  How did that happen? */
6967                     return;
6968                 }
6969                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6970                 return;
6971             }
6972             gameInfo.result = moveType;
6973             p = strchr(yy_text, '{');
6974             if (p == NULL) p = strchr(yy_text, '(');
6975             if (p == NULL) {
6976                 p = yy_text;
6977                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6978             } else {
6979                 q = strchr(p, *p == '{' ? '}' : ')');
6980                 if (q != NULL) *q = NULLCHAR;
6981                 p++;
6982             }
6983             gameInfo.resultDetails = StrSave(p);
6984             continue;
6985         }
6986         if (boardIndex >= forwardMostMove &&
6987             !(gameMode == IcsObserving && ics_gamenum == -1)) {
6988             backwardMostMove = blackPlaysFirst ? 1 : 0;
6989             return;
6990         }
6991         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6992                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6993                                  parseList[boardIndex]);
6994         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6995         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6996         /* currentMoveString is set as a side-effect of yylex */
6997         strcpy(moveList[boardIndex], currentMoveString);
6998         strcat(moveList[boardIndex], "\n");
6999         boardIndex++;
7000         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7001                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7002         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7003                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7004           case MT_NONE:
7005           case MT_STALEMATE:
7006           default:
7007             break;
7008           case MT_CHECK:
7009             if(gameInfo.variant != VariantShogi)
7010                 strcat(parseList[boardIndex - 1], "+");
7011             break;
7012           case MT_CHECKMATE:
7013           case MT_STAINMATE:
7014             strcat(parseList[boardIndex - 1], "#");
7015             break;
7016         }
7017     }
7018 }
7019
7020
7021 /* Apply a move to the given board  */
7022 void
7023 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7024      int fromX, fromY, toX, toY;
7025      int promoChar;
7026      Board board;
7027      char *castling;
7028      char *ep;
7029 {
7030   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7031
7032     /* [HGM] compute & store e.p. status and castling rights for new position */
7033     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7034     { int i;
7035
7036       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7037       oldEP = *ep;
7038       *ep = EP_NONE;
7039
7040       if( board[toY][toX] != EmptySquare ) 
7041            *ep = EP_CAPTURE;  
7042
7043       if( board[fromY][fromX] == WhitePawn ) {
7044            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7045                *ep = EP_PAWN_MOVE;
7046            if( toY-fromY==2) {
7047                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7048                         gameInfo.variant != VariantBerolina || toX < fromX)
7049                       *ep = toX | berolina;
7050                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7051                         gameInfo.variant != VariantBerolina || toX > fromX) 
7052                       *ep = toX;
7053            }
7054       } else 
7055       if( board[fromY][fromX] == BlackPawn ) {
7056            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7057                *ep = EP_PAWN_MOVE; 
7058            if( toY-fromY== -2) {
7059                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7060                         gameInfo.variant != VariantBerolina || toX < fromX)
7061                       *ep = toX | berolina;
7062                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7063                         gameInfo.variant != VariantBerolina || toX > fromX) 
7064                       *ep = toX;
7065            }
7066        }
7067
7068        for(i=0; i<nrCastlingRights; i++) {
7069            if(castling[i] == fromX && castlingRank[i] == fromY ||
7070               castling[i] == toX   && castlingRank[i] == toY   
7071              ) castling[i] = -1; // revoke for moved or captured piece
7072        }
7073
7074     }
7075
7076   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7077   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7078        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7079          
7080   if (fromX == toX && fromY == toY) return;
7081
7082   if (fromY == DROP_RANK) {
7083         /* must be first */
7084         piece = board[toY][toX] = (ChessSquare) fromX;
7085   } else {
7086      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7087      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7088      if(gameInfo.variant == VariantKnightmate)
7089          king += (int) WhiteUnicorn - (int) WhiteKing;
7090
7091     /* Code added by Tord: */
7092     /* FRC castling assumed when king captures friendly rook. */
7093     if (board[fromY][fromX] == WhiteKing &&
7094              board[toY][toX] == WhiteRook) {
7095       board[fromY][fromX] = EmptySquare;
7096       board[toY][toX] = EmptySquare;
7097       if(toX > fromX) {
7098         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7099       } else {
7100         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7101       }
7102     } else if (board[fromY][fromX] == BlackKing &&
7103                board[toY][toX] == BlackRook) {
7104       board[fromY][fromX] = EmptySquare;
7105       board[toY][toX] = EmptySquare;
7106       if(toX > fromX) {
7107         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7108       } else {
7109         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7110       }
7111     /* End of code added by Tord */
7112
7113     } else if (board[fromY][fromX] == king
7114         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7115         && toY == fromY && toX > fromX+1) {
7116         board[fromY][fromX] = EmptySquare;
7117         board[toY][toX] = king;
7118         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7119         board[fromY][BOARD_RGHT-1] = EmptySquare;
7120     } else if (board[fromY][fromX] == king
7121         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7122                && toY == fromY && toX < fromX-1) {
7123         board[fromY][fromX] = EmptySquare;
7124         board[toY][toX] = king;
7125         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7126         board[fromY][BOARD_LEFT] = EmptySquare;
7127     } else if (board[fromY][fromX] == WhitePawn
7128                && toY == BOARD_HEIGHT-1
7129                && gameInfo.variant != VariantXiangqi
7130                ) {
7131         /* white pawn promotion */
7132         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7133         if (board[toY][toX] == EmptySquare) {
7134             board[toY][toX] = WhiteQueen;
7135         }
7136         if(gameInfo.variant==VariantBughouse ||
7137            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7138             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7139         board[fromY][fromX] = EmptySquare;
7140     } else if ((fromY == BOARD_HEIGHT-4)
7141                && (toX != fromX)
7142                && gameInfo.variant != VariantXiangqi
7143                && gameInfo.variant != VariantBerolina
7144                && (board[fromY][fromX] == WhitePawn)
7145                && (board[toY][toX] == EmptySquare)) {
7146         board[fromY][fromX] = EmptySquare;
7147         board[toY][toX] = WhitePawn;
7148         captured = board[toY - 1][toX];
7149         board[toY - 1][toX] = EmptySquare;
7150     } else if ((fromY == BOARD_HEIGHT-4)
7151                && (toX == fromX)
7152                && gameInfo.variant == VariantBerolina
7153                && (board[fromY][fromX] == WhitePawn)
7154                && (board[toY][toX] == EmptySquare)) {
7155         board[fromY][fromX] = EmptySquare;
7156         board[toY][toX] = WhitePawn;
7157         if(oldEP & EP_BEROLIN_A) {
7158                 captured = board[fromY][fromX-1];
7159                 board[fromY][fromX-1] = EmptySquare;
7160         }else{  captured = board[fromY][fromX+1];
7161                 board[fromY][fromX+1] = EmptySquare;
7162         }
7163     } else if (board[fromY][fromX] == king
7164         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7165                && toY == fromY && toX > fromX+1) {
7166         board[fromY][fromX] = EmptySquare;
7167         board[toY][toX] = king;
7168         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7169         board[fromY][BOARD_RGHT-1] = EmptySquare;
7170     } else if (board[fromY][fromX] == king
7171         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7172                && toY == fromY && toX < fromX-1) {
7173         board[fromY][fromX] = EmptySquare;
7174         board[toY][toX] = king;
7175         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7176         board[fromY][BOARD_LEFT] = EmptySquare;
7177     } else if (fromY == 7 && fromX == 3
7178                && board[fromY][fromX] == BlackKing
7179                && toY == 7 && toX == 5) {
7180         board[fromY][fromX] = EmptySquare;
7181         board[toY][toX] = BlackKing;
7182         board[fromY][7] = EmptySquare;
7183         board[toY][4] = BlackRook;
7184     } else if (fromY == 7 && fromX == 3
7185                && board[fromY][fromX] == BlackKing
7186                && toY == 7 && toX == 1) {
7187         board[fromY][fromX] = EmptySquare;
7188         board[toY][toX] = BlackKing;
7189         board[fromY][0] = EmptySquare;
7190         board[toY][2] = BlackRook;
7191     } else if (board[fromY][fromX] == BlackPawn
7192                && toY == 0
7193                && gameInfo.variant != VariantXiangqi
7194                ) {
7195         /* black pawn promotion */
7196         board[0][toX] = CharToPiece(ToLower(promoChar));
7197         if (board[0][toX] == EmptySquare) {
7198             board[0][toX] = BlackQueen;
7199         }
7200         if(gameInfo.variant==VariantBughouse ||
7201            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7202             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7203         board[fromY][fromX] = EmptySquare;
7204     } else if ((fromY == 3)
7205                && (toX != fromX)
7206                && gameInfo.variant != VariantXiangqi
7207                && gameInfo.variant != VariantBerolina
7208                && (board[fromY][fromX] == BlackPawn)
7209                && (board[toY][toX] == EmptySquare)) {
7210         board[fromY][fromX] = EmptySquare;
7211         board[toY][toX] = BlackPawn;
7212         captured = board[toY + 1][toX];
7213         board[toY + 1][toX] = EmptySquare;
7214     } else if ((fromY == 3)
7215                && (toX == fromX)
7216                && gameInfo.variant == VariantBerolina
7217                && (board[fromY][fromX] == BlackPawn)
7218                && (board[toY][toX] == EmptySquare)) {
7219         board[fromY][fromX] = EmptySquare;
7220         board[toY][toX] = BlackPawn;
7221         if(oldEP & EP_BEROLIN_A) {
7222                 captured = board[fromY][fromX-1];
7223                 board[fromY][fromX-1] = EmptySquare;
7224         }else{  captured = board[fromY][fromX+1];
7225                 board[fromY][fromX+1] = EmptySquare;
7226         }
7227     } else {
7228         board[toY][toX] = board[fromY][fromX];
7229         board[fromY][fromX] = EmptySquare;
7230     }
7231
7232     /* [HGM] now we promote for Shogi, if needed */
7233     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7234         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7235   }
7236
7237     if (gameInfo.holdingsWidth != 0) {
7238
7239       /* !!A lot more code needs to be written to support holdings  */
7240       /* [HGM] OK, so I have written it. Holdings are stored in the */
7241       /* penultimate board files, so they are automaticlly stored   */
7242       /* in the game history.                                       */
7243       if (fromY == DROP_RANK) {
7244         /* Delete from holdings, by decreasing count */
7245         /* and erasing image if necessary            */
7246         p = (int) fromX;
7247         if(p < (int) BlackPawn) { /* white drop */
7248              p -= (int)WhitePawn;
7249              if(p >= gameInfo.holdingsSize) p = 0;
7250              if(--board[p][BOARD_WIDTH-2] == 0)
7251                   board[p][BOARD_WIDTH-1] = EmptySquare;
7252         } else {                  /* black drop */
7253              p -= (int)BlackPawn;
7254              if(p >= gameInfo.holdingsSize) p = 0;
7255              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7256                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7257         }
7258       }
7259       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7260           && gameInfo.variant != VariantBughouse        ) {
7261         /* [HGM] holdings: Add to holdings, if holdings exist */
7262         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7263                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7264                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7265         }
7266         p = (int) captured;
7267         if (p >= (int) BlackPawn) {
7268           p -= (int)BlackPawn;
7269           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7270                   /* in Shogi restore piece to its original  first */
7271                   captured = (ChessSquare) (DEMOTED captured);
7272                   p = DEMOTED p;
7273           }
7274           p = PieceToNumber((ChessSquare)p);
7275           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7276           board[p][BOARD_WIDTH-2]++;
7277           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7278         } else {
7279           p -= (int)WhitePawn;
7280           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7281                   captured = (ChessSquare) (DEMOTED captured);
7282                   p = DEMOTED p;
7283           }
7284           p = PieceToNumber((ChessSquare)p);
7285           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7286           board[BOARD_HEIGHT-1-p][1]++;
7287           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7288         }
7289       }
7290
7291     } else if (gameInfo.variant == VariantAtomic) {
7292       if (captured != EmptySquare) {
7293         int y, x;
7294         for (y = toY-1; y <= toY+1; y++) {
7295           for (x = toX-1; x <= toX+1; x++) {
7296             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7297                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7298               board[y][x] = EmptySquare;
7299             }
7300           }
7301         }
7302         board[toY][toX] = EmptySquare;
7303       }
7304     }
7305     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7306         /* [HGM] Shogi promotions */
7307         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7308     }
7309
7310     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7311                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7312         // [HGM] superchess: take promotion piece out of holdings
7313         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7314         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7315             if(!--board[k][BOARD_WIDTH-2])
7316                 board[k][BOARD_WIDTH-1] = EmptySquare;
7317         } else {
7318             if(!--board[BOARD_HEIGHT-1-k][1])
7319                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7320         }
7321     }
7322
7323 }
7324
7325 /* Updates forwardMostMove */
7326 void
7327 MakeMove(fromX, fromY, toX, toY, promoChar)
7328      int fromX, fromY, toX, toY;
7329      int promoChar;
7330 {
7331 //    forwardMostMove++; // [HGM] bare: moved downstream
7332
7333     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7334         int timeLeft; static int lastLoadFlag=0; int king, piece;
7335         piece = boards[forwardMostMove][fromY][fromX];
7336         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7337         if(gameInfo.variant == VariantKnightmate)
7338             king += (int) WhiteUnicorn - (int) WhiteKing;
7339         if(forwardMostMove == 0) {
7340             if(blackPlaysFirst) 
7341                 fprintf(serverMoves, "%s;", second.tidy);
7342             fprintf(serverMoves, "%s;", first.tidy);
7343             if(!blackPlaysFirst) 
7344                 fprintf(serverMoves, "%s;", second.tidy);
7345         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7346         lastLoadFlag = loadFlag;
7347         // print base move
7348         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7349         // print castling suffix
7350         if( toY == fromY && piece == king ) {
7351             if(toX-fromX > 1)
7352                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7353             if(fromX-toX >1)
7354                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7355         }
7356         // e.p. suffix
7357         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7358              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7359              boards[forwardMostMove][toY][toX] == EmptySquare
7360              && fromX != toX )
7361                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7362         // promotion suffix
7363         if(promoChar != NULLCHAR)
7364                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7365         if(!loadFlag) {
7366             fprintf(serverMoves, "/%d/%d",
7367                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7368             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7369             else                      timeLeft = blackTimeRemaining/1000;
7370             fprintf(serverMoves, "/%d", timeLeft);
7371         }
7372         fflush(serverMoves);
7373     }
7374
7375     if (forwardMostMove+1 >= MAX_MOVES) {
7376       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7377                         0, 1);
7378       return;
7379     }
7380     if (commentList[forwardMostMove+1] != NULL) {
7381         free(commentList[forwardMostMove+1]);
7382         commentList[forwardMostMove+1] = NULL;
7383     }
7384     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7385     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7386     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7387                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7388     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7389     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7390     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7391     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7392     gameInfo.result = GameUnfinished;
7393     if (gameInfo.resultDetails != NULL) {
7394         free(gameInfo.resultDetails);
7395         gameInfo.resultDetails = NULL;
7396     }
7397     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7398                               moveList[forwardMostMove - 1]);
7399     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7400                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7401                              fromY, fromX, toY, toX, promoChar,
7402                              parseList[forwardMostMove - 1]);
7403     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7404                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7405                             castlingRights[forwardMostMove]) ) {
7406       case MT_NONE:
7407       case MT_STALEMATE:
7408       default:
7409         break;
7410       case MT_CHECK:
7411         if(gameInfo.variant != VariantShogi)
7412             strcat(parseList[forwardMostMove - 1], "+");
7413         break;
7414       case MT_CHECKMATE:
7415       case MT_STAINMATE:
7416         strcat(parseList[forwardMostMove - 1], "#");
7417         break;
7418     }
7419     if (appData.debugMode) {
7420         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7421     }
7422
7423 }
7424
7425 /* Updates currentMove if not pausing */
7426 void
7427 ShowMove(fromX, fromY, toX, toY)
7428 {
7429     int instant = (gameMode == PlayFromGameFile) ?
7430         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7431     if(appData.noGUI) return;
7432     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7433         if (!instant) {
7434             if (forwardMostMove == currentMove + 1) {
7435                 AnimateMove(boards[forwardMostMove - 1],
7436                             fromX, fromY, toX, toY);
7437             }
7438             if (appData.highlightLastMove) {
7439                 SetHighlights(fromX, fromY, toX, toY);
7440             }
7441         }
7442         currentMove = forwardMostMove;
7443     }
7444
7445     if (instant) return;
7446
7447     DisplayMove(currentMove - 1);
7448     DrawPosition(FALSE, boards[currentMove]);
7449     DisplayBothClocks();
7450     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7451 }
7452
7453 void SendEgtPath(ChessProgramState *cps)
7454 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7455         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7456
7457         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7458
7459         while(*p) {
7460             char c, *q = name+1, *r, *s;
7461
7462             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7463             while(*p && *p != ',') *q++ = *p++;
7464             *q++ = ':'; *q = 0;
7465             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7466                 strcmp(name, ",nalimov:") == 0 ) {
7467                 // take nalimov path from the menu-changeable option first, if it is defined
7468                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7469                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7470             } else
7471             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7472                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7473                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7474                 s = r = StrStr(s, ":") + 1; // beginning of path info
7475                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7476                 c = *r; *r = 0;             // temporarily null-terminate path info
7477                     *--q = 0;               // strip of trailig ':' from name
7478                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7479                 *r = c;
7480                 SendToProgram(buf,cps);     // send egtbpath command for this format
7481             }
7482             if(*p == ',') p++; // read away comma to position for next format name
7483         }
7484 }
7485
7486 void
7487 InitChessProgram(cps, setup)
7488      ChessProgramState *cps;
7489      int setup; /* [HGM] needed to setup FRC opening position */
7490 {
7491     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7492     if (appData.noChessProgram) return;
7493     hintRequested = FALSE;
7494     bookRequested = FALSE;
7495
7496     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7497     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7498     if(cps->memSize) { /* [HGM] memory */
7499         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7500         SendToProgram(buf, cps);
7501     }
7502     SendEgtPath(cps); /* [HGM] EGT */
7503     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7504         sprintf(buf, "cores %d\n", appData.smpCores);
7505         SendToProgram(buf, cps);
7506     }
7507
7508     SendToProgram(cps->initString, cps);
7509     if (gameInfo.variant != VariantNormal &&
7510         gameInfo.variant != VariantLoadable
7511         /* [HGM] also send variant if board size non-standard */
7512         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7513                                             ) {
7514       char *v = VariantName(gameInfo.variant);
7515       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7516         /* [HGM] in protocol 1 we have to assume all variants valid */
7517         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7518         DisplayFatalError(buf, 0, 1);
7519         return;
7520       }
7521
7522       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7523       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7524       if( gameInfo.variant == VariantXiangqi )
7525            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7526       if( gameInfo.variant == VariantShogi )
7527            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7528       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7529            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7530       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7531                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7532            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7533       if( gameInfo.variant == VariantCourier )
7534            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7535       if( gameInfo.variant == VariantSuper )
7536            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7537       if( gameInfo.variant == VariantGreat )
7538            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7539
7540       if(overruled) {
7541            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7542                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7543            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7544            if(StrStr(cps->variants, b) == NULL) { 
7545                // specific sized variant not known, check if general sizing allowed
7546                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7547                    if(StrStr(cps->variants, "boardsize") == NULL) {
7548                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7549                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7550                        DisplayFatalError(buf, 0, 1);
7551                        return;
7552                    }
7553                    /* [HGM] here we really should compare with the maximum supported board size */
7554                }
7555            }
7556       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7557       sprintf(buf, "variant %s\n", b);
7558       SendToProgram(buf, cps);
7559     }
7560     currentlyInitializedVariant = gameInfo.variant;
7561
7562     /* [HGM] send opening position in FRC to first engine */
7563     if(setup) {
7564           SendToProgram("force\n", cps);
7565           SendBoard(cps, 0);
7566           /* engine is now in force mode! Set flag to wake it up after first move. */
7567           setboardSpoiledMachineBlack = 1;
7568     }
7569
7570     if (cps->sendICS) {
7571       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7572       SendToProgram(buf, cps);
7573     }
7574     cps->maybeThinking = FALSE;
7575     cps->offeredDraw = 0;
7576     if (!appData.icsActive) {
7577         SendTimeControl(cps, movesPerSession, timeControl,
7578                         timeIncrement, appData.searchDepth,
7579                         searchTime);
7580     }
7581     if (appData.showThinking 
7582         // [HGM] thinking: four options require thinking output to be sent
7583         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7584                                 ) {
7585         SendToProgram("post\n", cps);
7586     }
7587     SendToProgram("hard\n", cps);
7588     if (!appData.ponderNextMove) {
7589         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7590            it without being sure what state we are in first.  "hard"
7591            is not a toggle, so that one is OK.
7592          */
7593         SendToProgram("easy\n", cps);
7594     }
7595     if (cps->usePing) {
7596       sprintf(buf, "ping %d\n", ++cps->lastPing);
7597       SendToProgram(buf, cps);
7598     }
7599     cps->initDone = TRUE;
7600 }   
7601
7602
7603 void
7604 StartChessProgram(cps)
7605      ChessProgramState *cps;
7606 {
7607     char buf[MSG_SIZ];
7608     int err;
7609
7610     if (appData.noChessProgram) return;
7611     cps->initDone = FALSE;
7612
7613     if (strcmp(cps->host, "localhost") == 0) {
7614         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7615     } else if (*appData.remoteShell == NULLCHAR) {
7616         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7617     } else {
7618         if (*appData.remoteUser == NULLCHAR) {
7619           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7620                     cps->program);
7621         } else {
7622           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7623                     cps->host, appData.remoteUser, cps->program);
7624         }
7625         err = StartChildProcess(buf, "", &cps->pr);
7626     }
7627     
7628     if (err != 0) {
7629         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7630         DisplayFatalError(buf, err, 1);
7631         cps->pr = NoProc;
7632         cps->isr = NULL;
7633         return;
7634     }
7635     
7636     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7637     if (cps->protocolVersion > 1) {
7638       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7639       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7640       cps->comboCnt = 0;  //                and values of combo boxes
7641       SendToProgram(buf, cps);
7642     } else {
7643       SendToProgram("xboard\n", cps);
7644     }
7645 }
7646
7647
7648 void
7649 TwoMachinesEventIfReady P((void))
7650 {
7651   if (first.lastPing != first.lastPong) {
7652     DisplayMessage("", _("Waiting for first chess program"));
7653     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7654     return;
7655   }
7656   if (second.lastPing != second.lastPong) {
7657     DisplayMessage("", _("Waiting for second chess program"));
7658     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7659     return;
7660   }
7661   ThawUI();
7662   TwoMachinesEvent();
7663 }
7664
7665 void
7666 NextMatchGame P((void))
7667 {
7668     int index; /* [HGM] autoinc: step lod index during match */
7669     Reset(FALSE, TRUE);
7670     if (*appData.loadGameFile != NULLCHAR) {
7671         index = appData.loadGameIndex;
7672         if(index < 0) { // [HGM] autoinc
7673             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7674             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7675         } 
7676         LoadGameFromFile(appData.loadGameFile,
7677                          index,
7678                          appData.loadGameFile, FALSE);
7679     } else if (*appData.loadPositionFile != NULLCHAR) {
7680         index = appData.loadPositionIndex;
7681         if(index < 0) { // [HGM] autoinc
7682             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7683             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7684         } 
7685         LoadPositionFromFile(appData.loadPositionFile,
7686                              index,
7687                              appData.loadPositionFile);
7688     }
7689     TwoMachinesEventIfReady();
7690 }
7691
7692 void UserAdjudicationEvent( int result )
7693 {
7694     ChessMove gameResult = GameIsDrawn;
7695
7696     if( result > 0 ) {
7697         gameResult = WhiteWins;
7698     }
7699     else if( result < 0 ) {
7700         gameResult = BlackWins;
7701     }
7702
7703     if( gameMode == TwoMachinesPlay ) {
7704         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7705     }
7706 }
7707
7708
7709 // [HGM] save: calculate checksum of game to make games easily identifiable
7710 int StringCheckSum(char *s)
7711 {
7712         int i = 0;
7713         if(s==NULL) return 0;
7714         while(*s) i = i*259 + *s++;
7715         return i;
7716 }
7717
7718 int GameCheckSum()
7719 {
7720         int i, sum=0;
7721         for(i=backwardMostMove; i<forwardMostMove; i++) {
7722                 sum += pvInfoList[i].depth;
7723                 sum += StringCheckSum(parseList[i]);
7724                 sum += StringCheckSum(commentList[i]);
7725                 sum *= 261;
7726         }
7727         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7728         return sum + StringCheckSum(commentList[i]);
7729 } // end of save patch
7730
7731 void
7732 GameEnds(result, resultDetails, whosays)
7733      ChessMove result;
7734      char *resultDetails;
7735      int whosays;
7736 {
7737     GameMode nextGameMode;
7738     int isIcsGame;
7739     char buf[MSG_SIZ];
7740
7741     if(endingGame) return; /* [HGM] crash: forbid recursion */
7742     endingGame = 1;
7743
7744     if (appData.debugMode) {
7745       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7746               result, resultDetails ? resultDetails : "(null)", whosays);
7747     }
7748
7749     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7750         /* If we are playing on ICS, the server decides when the
7751            game is over, but the engine can offer to draw, claim 
7752            a draw, or resign. 
7753          */
7754 #if ZIPPY
7755         if (appData.zippyPlay && first.initDone) {
7756             if (result == GameIsDrawn) {
7757                 /* In case draw still needs to be claimed */
7758                 SendToICS(ics_prefix);
7759                 SendToICS("draw\n");
7760             } else if (StrCaseStr(resultDetails, "resign")) {
7761                 SendToICS(ics_prefix);
7762                 SendToICS("resign\n");
7763             }
7764         }
7765 #endif
7766         endingGame = 0; /* [HGM] crash */
7767         return;
7768     }
7769
7770     /* If we're loading the game from a file, stop */
7771     if (whosays == GE_FILE) {
7772       (void) StopLoadGameTimer();
7773       gameFileFP = NULL;
7774     }
7775
7776     /* Cancel draw offers */
7777     first.offeredDraw = second.offeredDraw = 0;
7778
7779     /* If this is an ICS game, only ICS can really say it's done;
7780        if not, anyone can. */
7781     isIcsGame = (gameMode == IcsPlayingWhite || 
7782                  gameMode == IcsPlayingBlack || 
7783                  gameMode == IcsObserving    || 
7784                  gameMode == IcsExamining);
7785
7786     if (!isIcsGame || whosays == GE_ICS) {
7787         /* OK -- not an ICS game, or ICS said it was done */
7788         StopClocks();
7789         if (!isIcsGame && !appData.noChessProgram) 
7790           SetUserThinkingEnables();
7791     
7792         /* [HGM] if a machine claims the game end we verify this claim */
7793         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7794             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7795                 char claimer;
7796                 ChessMove trueResult = (ChessMove) -1;
7797
7798                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7799                                             first.twoMachinesColor[0] :
7800                                             second.twoMachinesColor[0] ;
7801
7802                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7803                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7804                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7805                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7806                 } else
7807                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7808                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7809                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7810                 } else
7811                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7812                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7813                 }
7814
7815                 // now verify win claims, but not in drop games, as we don't understand those yet
7816                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7817                                                  || gameInfo.variant == VariantGreat) &&
7818                     (result == WhiteWins && claimer == 'w' ||
7819                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7820                       if (appData.debugMode) {
7821                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7822                                 result, epStatus[forwardMostMove], forwardMostMove);
7823                       }
7824                       if(result != trueResult) {
7825                               sprintf(buf, "False win claim: '%s'", resultDetails);
7826                               result = claimer == 'w' ? BlackWins : WhiteWins;
7827                               resultDetails = buf;
7828                       }
7829                 } else
7830                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7831                     && (forwardMostMove <= backwardMostMove ||
7832                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7833                         (claimer=='b')==(forwardMostMove&1))
7834                                                                                   ) {
7835                       /* [HGM] verify: draws that were not flagged are false claims */
7836                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7837                       result = claimer == 'w' ? BlackWins : WhiteWins;
7838                       resultDetails = buf;
7839                 }
7840                 /* (Claiming a loss is accepted no questions asked!) */
7841             }
7842             /* [HGM] bare: don't allow bare King to win */
7843             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7844                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7845                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7846                && result != GameIsDrawn)
7847             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7848                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7849                         int p = (int)boards[forwardMostMove][i][j] - color;
7850                         if(p >= 0 && p <= (int)WhiteKing) k++;
7851                 }
7852                 if (appData.debugMode) {
7853                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7854                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7855                 }
7856                 if(k <= 1) {
7857                         result = GameIsDrawn;
7858                         sprintf(buf, "%s but bare king", resultDetails);
7859                         resultDetails = buf;
7860                 }
7861             }
7862         }
7863
7864
7865         if(serverMoves != NULL && !loadFlag) { char c = '=';
7866             if(result==WhiteWins) c = '+';
7867             if(result==BlackWins) c = '-';
7868             if(resultDetails != NULL)
7869                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7870         }
7871         if (resultDetails != NULL) {
7872             gameInfo.result = result;
7873             gameInfo.resultDetails = StrSave(resultDetails);
7874
7875             /* display last move only if game was not loaded from file */
7876             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7877                 DisplayMove(currentMove - 1);
7878     
7879             if (forwardMostMove != 0) {
7880                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7881                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7882                                                                 ) {
7883                     if (*appData.saveGameFile != NULLCHAR) {
7884                         SaveGameToFile(appData.saveGameFile, TRUE);
7885                     } else if (appData.autoSaveGames) {
7886                         AutoSaveGame();
7887                     }
7888                     if (*appData.savePositionFile != NULLCHAR) {
7889                         SavePositionToFile(appData.savePositionFile);
7890                     }
7891                 }
7892             }
7893
7894             /* Tell program how game ended in case it is learning */
7895             /* [HGM] Moved this to after saving the PGN, just in case */
7896             /* engine died and we got here through time loss. In that */
7897             /* case we will get a fatal error writing the pipe, which */
7898             /* would otherwise lose us the PGN.                       */
7899             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7900             /* output during GameEnds should never be fatal anymore   */
7901             if (gameMode == MachinePlaysWhite ||
7902                 gameMode == MachinePlaysBlack ||
7903                 gameMode == TwoMachinesPlay ||
7904                 gameMode == IcsPlayingWhite ||
7905                 gameMode == IcsPlayingBlack ||
7906                 gameMode == BeginningOfGame) {
7907                 char buf[MSG_SIZ];
7908                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7909                         resultDetails);
7910                 if (first.pr != NoProc) {
7911                     SendToProgram(buf, &first);
7912                 }
7913                 if (second.pr != NoProc &&
7914                     gameMode == TwoMachinesPlay) {
7915                     SendToProgram(buf, &second);
7916                 }
7917             }
7918         }
7919
7920         if (appData.icsActive) {
7921             if (appData.quietPlay &&
7922                 (gameMode == IcsPlayingWhite ||
7923                  gameMode == IcsPlayingBlack)) {
7924                 SendToICS(ics_prefix);
7925                 SendToICS("set shout 1\n");
7926             }
7927             nextGameMode = IcsIdle;
7928             ics_user_moved = FALSE;
7929             /* clean up premove.  It's ugly when the game has ended and the
7930              * premove highlights are still on the board.
7931              */
7932             if (gotPremove) {
7933               gotPremove = FALSE;
7934               ClearPremoveHighlights();
7935               DrawPosition(FALSE, boards[currentMove]);
7936             }
7937             if (whosays == GE_ICS) {
7938                 switch (result) {
7939                 case WhiteWins:
7940                     if (gameMode == IcsPlayingWhite)
7941                         PlayIcsWinSound();
7942                     else if(gameMode == IcsPlayingBlack)
7943                         PlayIcsLossSound();
7944                     break;
7945                 case BlackWins:
7946                     if (gameMode == IcsPlayingBlack)
7947                         PlayIcsWinSound();
7948                     else if(gameMode == IcsPlayingWhite)
7949                         PlayIcsLossSound();
7950                     break;
7951                 case GameIsDrawn:
7952                     PlayIcsDrawSound();
7953                     break;
7954                 default:
7955                     PlayIcsUnfinishedSound();
7956                 }
7957             }
7958         } else if (gameMode == EditGame ||
7959                    gameMode == PlayFromGameFile || 
7960                    gameMode == AnalyzeMode || 
7961                    gameMode == AnalyzeFile) {
7962             nextGameMode = gameMode;
7963         } else {
7964             nextGameMode = EndOfGame;
7965         }
7966         pausing = FALSE;
7967         ModeHighlight();
7968     } else {
7969         nextGameMode = gameMode;
7970     }
7971
7972     if (appData.noChessProgram) {
7973         gameMode = nextGameMode;
7974         ModeHighlight();
7975         endingGame = 0; /* [HGM] crash */
7976         return;
7977     }
7978
7979     if (first.reuse) {
7980         /* Put first chess program into idle state */
7981         if (first.pr != NoProc &&
7982             (gameMode == MachinePlaysWhite ||
7983              gameMode == MachinePlaysBlack ||
7984              gameMode == TwoMachinesPlay ||
7985              gameMode == IcsPlayingWhite ||
7986              gameMode == IcsPlayingBlack ||
7987              gameMode == BeginningOfGame)) {
7988             SendToProgram("force\n", &first);
7989             if (first.usePing) {
7990               char buf[MSG_SIZ];
7991               sprintf(buf, "ping %d\n", ++first.lastPing);
7992               SendToProgram(buf, &first);
7993             }
7994         }
7995     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7996         /* Kill off first chess program */
7997         if (first.isr != NULL)
7998           RemoveInputSource(first.isr);
7999         first.isr = NULL;
8000     
8001         if (first.pr != NoProc) {
8002             ExitAnalyzeMode();
8003             DoSleep( appData.delayBeforeQuit );
8004             SendToProgram("quit\n", &first);
8005             DoSleep( appData.delayAfterQuit );
8006             DestroyChildProcess(first.pr, first.useSigterm);
8007         }
8008         first.pr = NoProc;
8009     }
8010     if (second.reuse) {
8011         /* Put second chess program into idle state */
8012         if (second.pr != NoProc &&
8013             gameMode == TwoMachinesPlay) {
8014             SendToProgram("force\n", &second);
8015             if (second.usePing) {
8016               char buf[MSG_SIZ];
8017               sprintf(buf, "ping %d\n", ++second.lastPing);
8018               SendToProgram(buf, &second);
8019             }
8020         }
8021     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8022         /* Kill off second chess program */
8023         if (second.isr != NULL)
8024           RemoveInputSource(second.isr);
8025         second.isr = NULL;
8026     
8027         if (second.pr != NoProc) {
8028             DoSleep( appData.delayBeforeQuit );
8029             SendToProgram("quit\n", &second);
8030             DoSleep( appData.delayAfterQuit );
8031             DestroyChildProcess(second.pr, second.useSigterm);
8032         }
8033         second.pr = NoProc;
8034     }
8035
8036     if (matchMode && gameMode == TwoMachinesPlay) {
8037         switch (result) {
8038         case WhiteWins:
8039           if (first.twoMachinesColor[0] == 'w') {
8040             first.matchWins++;
8041           } else {
8042             second.matchWins++;
8043           }
8044           break;
8045         case BlackWins:
8046           if (first.twoMachinesColor[0] == 'b') {
8047             first.matchWins++;
8048           } else {
8049             second.matchWins++;
8050           }
8051           break;
8052         default:
8053           break;
8054         }
8055         if (matchGame < appData.matchGames) {
8056             char *tmp;
8057             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8058                 tmp = first.twoMachinesColor;
8059                 first.twoMachinesColor = second.twoMachinesColor;
8060                 second.twoMachinesColor = tmp;
8061             }
8062             gameMode = nextGameMode;
8063             matchGame++;
8064             if(appData.matchPause>10000 || appData.matchPause<10)
8065                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8066             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8067             endingGame = 0; /* [HGM] crash */
8068             return;
8069         } else {
8070             char buf[MSG_SIZ];
8071             gameMode = nextGameMode;
8072             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8073                     first.tidy, second.tidy,
8074                     first.matchWins, second.matchWins,
8075                     appData.matchGames - (first.matchWins + second.matchWins));
8076             DisplayFatalError(buf, 0, 0);
8077         }
8078     }
8079     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8080         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8081       ExitAnalyzeMode();
8082     gameMode = nextGameMode;
8083     ModeHighlight();
8084     endingGame = 0;  /* [HGM] crash */
8085 }
8086
8087 /* Assumes program was just initialized (initString sent).
8088    Leaves program in force mode. */
8089 void
8090 FeedMovesToProgram(cps, upto) 
8091      ChessProgramState *cps;
8092      int upto;
8093 {
8094     int i;
8095     
8096     if (appData.debugMode)
8097       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8098               startedFromSetupPosition ? "position and " : "",
8099               backwardMostMove, upto, cps->which);
8100     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8101         // [HGM] variantswitch: make engine aware of new variant
8102         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8103                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8104         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8105         SendToProgram(buf, cps);
8106         currentlyInitializedVariant = gameInfo.variant;
8107     }
8108     SendToProgram("force\n", cps);
8109     if (startedFromSetupPosition) {
8110         SendBoard(cps, backwardMostMove);
8111     if (appData.debugMode) {
8112         fprintf(debugFP, "feedMoves\n");
8113     }
8114     }
8115     for (i = backwardMostMove; i < upto; i++) {
8116         SendMoveToProgram(i, cps);
8117     }
8118 }
8119
8120
8121 void
8122 ResurrectChessProgram()
8123 {
8124      /* The chess program may have exited.
8125         If so, restart it and feed it all the moves made so far. */
8126
8127     if (appData.noChessProgram || first.pr != NoProc) return;
8128     
8129     StartChessProgram(&first);
8130     InitChessProgram(&first, FALSE);
8131     FeedMovesToProgram(&first, currentMove);
8132
8133     if (!first.sendTime) {
8134         /* can't tell gnuchess what its clock should read,
8135            so we bow to its notion. */
8136         ResetClocks();
8137         timeRemaining[0][currentMove] = whiteTimeRemaining;
8138         timeRemaining[1][currentMove] = blackTimeRemaining;
8139     }
8140
8141     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8142                 appData.icsEngineAnalyze) && first.analysisSupport) {
8143       SendToProgram("analyze\n", &first);
8144       first.analyzing = TRUE;
8145     }
8146 }
8147
8148 /*
8149  * Button procedures
8150  */
8151 void
8152 Reset(redraw, init)
8153      int redraw, init;
8154 {
8155     int i;
8156
8157     if (appData.debugMode) {
8158         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8159                 redraw, init, gameMode);
8160     }
8161     pausing = pauseExamInvalid = FALSE;
8162     startedFromSetupPosition = blackPlaysFirst = FALSE;
8163     firstMove = TRUE;
8164     whiteFlag = blackFlag = FALSE;
8165     userOfferedDraw = FALSE;
8166     hintRequested = bookRequested = FALSE;
8167     first.maybeThinking = FALSE;
8168     second.maybeThinking = FALSE;
8169     first.bookSuspend = FALSE; // [HGM] book
8170     second.bookSuspend = FALSE;
8171     thinkOutput[0] = NULLCHAR;
8172     lastHint[0] = NULLCHAR;
8173     ClearGameInfo(&gameInfo);
8174     gameInfo.variant = StringToVariant(appData.variant);
8175     ics_user_moved = ics_clock_paused = FALSE;
8176     ics_getting_history = H_FALSE;
8177     ics_gamenum = -1;
8178     white_holding[0] = black_holding[0] = NULLCHAR;
8179     ClearProgramStats();
8180     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8181     
8182     ResetFrontEnd();
8183     ClearHighlights();
8184     flipView = appData.flipView;
8185     ClearPremoveHighlights();
8186     gotPremove = FALSE;
8187     alarmSounded = FALSE;
8188
8189     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8190     if(appData.serverMovesName != NULL) {
8191         /* [HGM] prepare to make moves file for broadcasting */
8192         clock_t t = clock();
8193         if(serverMoves != NULL) fclose(serverMoves);
8194         serverMoves = fopen(appData.serverMovesName, "r");
8195         if(serverMoves != NULL) {
8196             fclose(serverMoves);
8197             /* delay 15 sec before overwriting, so all clients can see end */
8198             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8199         }
8200         serverMoves = fopen(appData.serverMovesName, "w");
8201     }
8202
8203     ExitAnalyzeMode();
8204     gameMode = BeginningOfGame;
8205     ModeHighlight();
8206     if(appData.icsActive) gameInfo.variant = VariantNormal;
8207     currentMove = forwardMostMove = backwardMostMove = 0;
8208     InitPosition(redraw);
8209     for (i = 0; i < MAX_MOVES; i++) {
8210         if (commentList[i] != NULL) {
8211             free(commentList[i]);
8212             commentList[i] = NULL;
8213         }
8214     }
8215     ResetClocks();
8216     timeRemaining[0][0] = whiteTimeRemaining;
8217     timeRemaining[1][0] = blackTimeRemaining;
8218     if (first.pr == NULL) {
8219         StartChessProgram(&first);
8220     }
8221     if (init) {
8222             InitChessProgram(&first, startedFromSetupPosition);
8223     }
8224     DisplayTitle("");
8225     DisplayMessage("", "");
8226     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8227     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8228 }
8229
8230 void
8231 AutoPlayGameLoop()
8232 {
8233     for (;;) {
8234         if (!AutoPlayOneMove())
8235           return;
8236         if (matchMode || appData.timeDelay == 0)
8237           continue;
8238         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8239           return;
8240         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8241         break;
8242     }
8243 }
8244
8245
8246 int
8247 AutoPlayOneMove()
8248 {
8249     int fromX, fromY, toX, toY;
8250
8251     if (appData.debugMode) {
8252       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8253     }
8254
8255     if (gameMode != PlayFromGameFile)
8256       return FALSE;
8257
8258     if (currentMove >= forwardMostMove) {
8259       gameMode = EditGame;
8260       ModeHighlight();
8261
8262       /* [AS] Clear current move marker at the end of a game */
8263       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8264
8265       return FALSE;
8266     }
8267     
8268     toX = moveList[currentMove][2] - AAA;
8269     toY = moveList[currentMove][3] - ONE;
8270
8271     if (moveList[currentMove][1] == '@') {
8272         if (appData.highlightLastMove) {
8273             SetHighlights(-1, -1, toX, toY);
8274         }
8275     } else {
8276         fromX = moveList[currentMove][0] - AAA;
8277         fromY = moveList[currentMove][1] - ONE;
8278
8279         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8280
8281         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8282
8283         if (appData.highlightLastMove) {
8284             SetHighlights(fromX, fromY, toX, toY);
8285         }
8286     }
8287     DisplayMove(currentMove);
8288     SendMoveToProgram(currentMove++, &first);
8289     DisplayBothClocks();
8290     DrawPosition(FALSE, boards[currentMove]);
8291     // [HGM] PV info: always display, routine tests if empty
8292     DisplayComment(currentMove - 1, commentList[currentMove]);
8293     return TRUE;
8294 }
8295
8296
8297 int
8298 LoadGameOneMove(readAhead)
8299      ChessMove readAhead;
8300 {
8301     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8302     char promoChar = NULLCHAR;
8303     ChessMove moveType;
8304     char move[MSG_SIZ];
8305     char *p, *q;
8306     
8307     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8308         gameMode != AnalyzeMode && gameMode != Training) {
8309         gameFileFP = NULL;
8310         return FALSE;
8311     }
8312     
8313     yyboardindex = forwardMostMove;
8314     if (readAhead != (ChessMove)0) {
8315       moveType = readAhead;
8316     } else {
8317       if (gameFileFP == NULL)
8318           return FALSE;
8319       moveType = (ChessMove) yylex();
8320     }
8321     
8322     done = FALSE;
8323     switch (moveType) {
8324       case Comment:
8325         if (appData.debugMode) 
8326           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8327         p = yy_text;
8328         if (*p == '{' || *p == '[' || *p == '(') {
8329             p[strlen(p) - 1] = NULLCHAR;
8330             p++;
8331         }
8332
8333         /* append the comment but don't display it */
8334         while (*p == '\n') p++;
8335         AppendComment(currentMove, p);
8336         return TRUE;
8337
8338       case WhiteCapturesEnPassant:
8339       case BlackCapturesEnPassant:
8340       case WhitePromotionChancellor:
8341       case BlackPromotionChancellor:
8342       case WhitePromotionArchbishop:
8343       case BlackPromotionArchbishop:
8344       case WhitePromotionCentaur:
8345       case BlackPromotionCentaur:
8346       case WhitePromotionQueen:
8347       case BlackPromotionQueen:
8348       case WhitePromotionRook:
8349       case BlackPromotionRook:
8350       case WhitePromotionBishop:
8351       case BlackPromotionBishop:
8352       case WhitePromotionKnight:
8353       case BlackPromotionKnight:
8354       case WhitePromotionKing:
8355       case BlackPromotionKing:
8356       case NormalMove:
8357       case WhiteKingSideCastle:
8358       case WhiteQueenSideCastle:
8359       case BlackKingSideCastle:
8360       case BlackQueenSideCastle:
8361       case WhiteKingSideCastleWild:
8362       case WhiteQueenSideCastleWild:
8363       case BlackKingSideCastleWild:
8364       case BlackQueenSideCastleWild:
8365       /* PUSH Fabien */
8366       case WhiteHSideCastleFR:
8367       case WhiteASideCastleFR:
8368       case BlackHSideCastleFR:
8369       case BlackASideCastleFR:
8370       /* POP Fabien */
8371         if (appData.debugMode)
8372           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8373         fromX = currentMoveString[0] - AAA;
8374         fromY = currentMoveString[1] - ONE;
8375         toX = currentMoveString[2] - AAA;
8376         toY = currentMoveString[3] - ONE;
8377         promoChar = currentMoveString[4];
8378         break;
8379
8380       case WhiteDrop:
8381       case BlackDrop:
8382         if (appData.debugMode)
8383           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8384         fromX = moveType == WhiteDrop ?
8385           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8386         (int) CharToPiece(ToLower(currentMoveString[0]));
8387         fromY = DROP_RANK;
8388         toX = currentMoveString[2] - AAA;
8389         toY = currentMoveString[3] - ONE;
8390         break;
8391
8392       case WhiteWins:
8393       case BlackWins:
8394       case GameIsDrawn:
8395       case GameUnfinished:
8396         if (appData.debugMode)
8397           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8398         p = strchr(yy_text, '{');
8399         if (p == NULL) p = strchr(yy_text, '(');
8400         if (p == NULL) {
8401             p = yy_text;
8402             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8403         } else {
8404             q = strchr(p, *p == '{' ? '}' : ')');
8405             if (q != NULL) *q = NULLCHAR;
8406             p++;
8407         }
8408         GameEnds(moveType, p, GE_FILE);
8409         done = TRUE;
8410         if (cmailMsgLoaded) {
8411             ClearHighlights();
8412             flipView = WhiteOnMove(currentMove);
8413             if (moveType == GameUnfinished) flipView = !flipView;
8414             if (appData.debugMode)
8415               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8416         }
8417         break;
8418
8419       case (ChessMove) 0:       /* end of file */
8420         if (appData.debugMode)
8421           fprintf(debugFP, "Parser hit end of file\n");
8422         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8423                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8424           case MT_NONE:
8425           case MT_CHECK:
8426             break;
8427           case MT_CHECKMATE:
8428           case MT_STAINMATE:
8429             if (WhiteOnMove(currentMove)) {
8430                 GameEnds(BlackWins, "Black mates", GE_FILE);
8431             } else {
8432                 GameEnds(WhiteWins, "White mates", GE_FILE);
8433             }
8434             break;
8435           case MT_STALEMATE:
8436             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8437             break;
8438         }
8439         done = TRUE;
8440         break;
8441
8442       case MoveNumberOne:
8443         if (lastLoadGameStart == GNUChessGame) {
8444             /* GNUChessGames have numbers, but they aren't move numbers */
8445             if (appData.debugMode)
8446               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8447                       yy_text, (int) moveType);
8448             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8449         }
8450         /* else fall thru */
8451
8452       case XBoardGame:
8453       case GNUChessGame:
8454       case PGNTag:
8455         /* Reached start of next game in file */
8456         if (appData.debugMode)
8457           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8458         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8459                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8460           case MT_NONE:
8461           case MT_CHECK:
8462             break;
8463           case MT_CHECKMATE:
8464           case MT_STAINMATE:
8465             if (WhiteOnMove(currentMove)) {
8466                 GameEnds(BlackWins, "Black mates", GE_FILE);
8467             } else {
8468                 GameEnds(WhiteWins, "White mates", GE_FILE);
8469             }
8470             break;
8471           case MT_STALEMATE:
8472             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8473             break;
8474         }
8475         done = TRUE;
8476         break;
8477
8478       case PositionDiagram:     /* should not happen; ignore */
8479       case ElapsedTime:         /* ignore */
8480       case NAG:                 /* ignore */
8481         if (appData.debugMode)
8482           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8483                   yy_text, (int) moveType);
8484         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8485
8486       case IllegalMove:
8487         if (appData.testLegality) {
8488             if (appData.debugMode)
8489               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8490             sprintf(move, _("Illegal move: %d.%s%s"),
8491                     (forwardMostMove / 2) + 1,
8492                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8493             DisplayError(move, 0);
8494             done = TRUE;
8495         } else {
8496             if (appData.debugMode)
8497               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8498                       yy_text, currentMoveString);
8499             fromX = currentMoveString[0] - AAA;
8500             fromY = currentMoveString[1] - ONE;
8501             toX = currentMoveString[2] - AAA;
8502             toY = currentMoveString[3] - ONE;
8503             promoChar = currentMoveString[4];
8504         }
8505         break;
8506
8507       case AmbiguousMove:
8508         if (appData.debugMode)
8509           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8510         sprintf(move, _("Ambiguous move: %d.%s%s"),
8511                 (forwardMostMove / 2) + 1,
8512                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8513         DisplayError(move, 0);
8514         done = TRUE;
8515         break;
8516
8517       default:
8518       case ImpossibleMove:
8519         if (appData.debugMode)
8520           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8521         sprintf(move, _("Illegal move: %d.%s%s"),
8522                 (forwardMostMove / 2) + 1,
8523                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8524         DisplayError(move, 0);
8525         done = TRUE;
8526         break;
8527     }
8528
8529     if (done) {
8530         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8531             DrawPosition(FALSE, boards[currentMove]);
8532             DisplayBothClocks();
8533             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8534               DisplayComment(currentMove - 1, commentList[currentMove]);
8535         }
8536         (void) StopLoadGameTimer();
8537         gameFileFP = NULL;
8538         cmailOldMove = forwardMostMove;
8539         return FALSE;
8540     } else {
8541         /* currentMoveString is set as a side-effect of yylex */
8542         strcat(currentMoveString, "\n");
8543         strcpy(moveList[forwardMostMove], currentMoveString);
8544         
8545         thinkOutput[0] = NULLCHAR;
8546         MakeMove(fromX, fromY, toX, toY, promoChar);
8547         currentMove = forwardMostMove;
8548         return TRUE;
8549     }
8550 }
8551
8552 /* Load the nth game from the given file */
8553 int
8554 LoadGameFromFile(filename, n, title, useList)
8555      char *filename;
8556      int n;
8557      char *title;
8558      /*Boolean*/ int useList;
8559 {
8560     FILE *f;
8561     char buf[MSG_SIZ];
8562
8563     if (strcmp(filename, "-") == 0) {
8564         f = stdin;
8565         title = "stdin";
8566     } else {
8567         f = fopen(filename, "rb");
8568         if (f == NULL) {
8569           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8570             DisplayError(buf, errno);
8571             return FALSE;
8572         }
8573     }
8574     if (fseek(f, 0, 0) == -1) {
8575         /* f is not seekable; probably a pipe */
8576         useList = FALSE;
8577     }
8578     if (useList && n == 0) {
8579         int error = GameListBuild(f);
8580         if (error) {
8581             DisplayError(_("Cannot build game list"), error);
8582         } else if (!ListEmpty(&gameList) &&
8583                    ((ListGame *) gameList.tailPred)->number > 1) {
8584             GameListPopUp(f, title);
8585             return TRUE;
8586         }
8587         GameListDestroy();
8588         n = 1;
8589     }
8590     if (n == 0) n = 1;
8591     return LoadGame(f, n, title, FALSE);
8592 }
8593
8594
8595 void
8596 MakeRegisteredMove()
8597 {
8598     int fromX, fromY, toX, toY;
8599     char promoChar;
8600     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8601         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8602           case CMAIL_MOVE:
8603           case CMAIL_DRAW:
8604             if (appData.debugMode)
8605               fprintf(debugFP, "Restoring %s for game %d\n",
8606                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8607     
8608             thinkOutput[0] = NULLCHAR;
8609             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8610             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8611             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8612             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8613             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8614             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8615             MakeMove(fromX, fromY, toX, toY, promoChar);
8616             ShowMove(fromX, fromY, toX, toY);
8617               
8618             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8619                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8620               case MT_NONE:
8621               case MT_CHECK:
8622                 break;
8623                 
8624               case MT_CHECKMATE:
8625               case MT_STAINMATE:
8626                 if (WhiteOnMove(currentMove)) {
8627                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8628                 } else {
8629                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8630                 }
8631                 break;
8632                 
8633               case MT_STALEMATE:
8634                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8635                 break;
8636             }
8637
8638             break;
8639             
8640           case CMAIL_RESIGN:
8641             if (WhiteOnMove(currentMove)) {
8642                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8643             } else {
8644                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8645             }
8646             break;
8647             
8648           case CMAIL_ACCEPT:
8649             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8650             break;
8651               
8652           default:
8653             break;
8654         }
8655     }
8656
8657     return;
8658 }
8659
8660 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8661 int
8662 CmailLoadGame(f, gameNumber, title, useList)
8663      FILE *f;
8664      int gameNumber;
8665      char *title;
8666      int useList;
8667 {
8668     int retVal;
8669
8670     if (gameNumber > nCmailGames) {
8671         DisplayError(_("No more games in this message"), 0);
8672         return FALSE;
8673     }
8674     if (f == lastLoadGameFP) {
8675         int offset = gameNumber - lastLoadGameNumber;
8676         if (offset == 0) {
8677             cmailMsg[0] = NULLCHAR;
8678             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8679                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8680                 nCmailMovesRegistered--;
8681             }
8682             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8683             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8684                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8685             }
8686         } else {
8687             if (! RegisterMove()) return FALSE;
8688         }
8689     }
8690
8691     retVal = LoadGame(f, gameNumber, title, useList);
8692
8693     /* Make move registered during previous look at this game, if any */
8694     MakeRegisteredMove();
8695
8696     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8697         commentList[currentMove]
8698           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8699         DisplayComment(currentMove - 1, commentList[currentMove]);
8700     }
8701
8702     return retVal;
8703 }
8704
8705 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8706 int
8707 ReloadGame(offset)
8708      int offset;
8709 {
8710     int gameNumber = lastLoadGameNumber + offset;
8711     if (lastLoadGameFP == NULL) {
8712         DisplayError(_("No game has been loaded yet"), 0);
8713         return FALSE;
8714     }
8715     if (gameNumber <= 0) {
8716         DisplayError(_("Can't back up any further"), 0);
8717         return FALSE;
8718     }
8719     if (cmailMsgLoaded) {
8720         return CmailLoadGame(lastLoadGameFP, gameNumber,
8721                              lastLoadGameTitle, lastLoadGameUseList);
8722     } else {
8723         return LoadGame(lastLoadGameFP, gameNumber,
8724                         lastLoadGameTitle, lastLoadGameUseList);
8725     }
8726 }
8727
8728
8729
8730 /* Load the nth game from open file f */
8731 int
8732 LoadGame(f, gameNumber, title, useList)
8733      FILE *f;
8734      int gameNumber;
8735      char *title;
8736      int useList;
8737 {
8738     ChessMove cm;
8739     char buf[MSG_SIZ];
8740     int gn = gameNumber;
8741     ListGame *lg = NULL;
8742     int numPGNTags = 0;
8743     int err;
8744     GameMode oldGameMode;
8745     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8746
8747     if (appData.debugMode) 
8748         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8749
8750     if (gameMode == Training )
8751         SetTrainingModeOff();
8752
8753     oldGameMode = gameMode;
8754     if (gameMode != BeginningOfGame) {
8755       Reset(FALSE, TRUE);
8756     }
8757
8758     gameFileFP = f;
8759     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8760         fclose(lastLoadGameFP);
8761     }
8762
8763     if (useList) {
8764         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8765         
8766         if (lg) {
8767             fseek(f, lg->offset, 0);
8768             GameListHighlight(gameNumber);
8769             gn = 1;
8770         }
8771         else {
8772             DisplayError(_("Game number out of range"), 0);
8773             return FALSE;
8774         }
8775     } else {
8776         GameListDestroy();
8777         if (fseek(f, 0, 0) == -1) {
8778             if (f == lastLoadGameFP ?
8779                 gameNumber == lastLoadGameNumber + 1 :
8780                 gameNumber == 1) {
8781                 gn = 1;
8782             } else {
8783                 DisplayError(_("Can't seek on game file"), 0);
8784                 return FALSE;
8785             }
8786         }
8787     }
8788     lastLoadGameFP = f;
8789     lastLoadGameNumber = gameNumber;
8790     strcpy(lastLoadGameTitle, title);
8791     lastLoadGameUseList = useList;
8792
8793     yynewfile(f);
8794
8795     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8796       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8797                 lg->gameInfo.black);
8798             DisplayTitle(buf);
8799     } else if (*title != NULLCHAR) {
8800         if (gameNumber > 1) {
8801             sprintf(buf, "%s %d", title, gameNumber);
8802             DisplayTitle(buf);
8803         } else {
8804             DisplayTitle(title);
8805         }
8806     }
8807
8808     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8809         gameMode = PlayFromGameFile;
8810         ModeHighlight();
8811     }
8812
8813     currentMove = forwardMostMove = backwardMostMove = 0;
8814     CopyBoard(boards[0], initialPosition);
8815     StopClocks();
8816
8817     /*
8818      * Skip the first gn-1 games in the file.
8819      * Also skip over anything that precedes an identifiable 
8820      * start of game marker, to avoid being confused by 
8821      * garbage at the start of the file.  Currently 
8822      * recognized start of game markers are the move number "1",
8823      * the pattern "gnuchess .* game", the pattern
8824      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8825      * A game that starts with one of the latter two patterns
8826      * will also have a move number 1, possibly
8827      * following a position diagram.
8828      * 5-4-02: Let's try being more lenient and allowing a game to
8829      * start with an unnumbered move.  Does that break anything?
8830      */
8831     cm = lastLoadGameStart = (ChessMove) 0;
8832     while (gn > 0) {
8833         yyboardindex = forwardMostMove;
8834         cm = (ChessMove) yylex();
8835         switch (cm) {
8836           case (ChessMove) 0:
8837             if (cmailMsgLoaded) {
8838                 nCmailGames = CMAIL_MAX_GAMES - gn;
8839             } else {
8840                 Reset(TRUE, TRUE);
8841                 DisplayError(_("Game not found in file"), 0);
8842             }
8843             return FALSE;
8844
8845           case GNUChessGame:
8846           case XBoardGame:
8847             gn--;
8848             lastLoadGameStart = cm;
8849             break;
8850             
8851           case MoveNumberOne:
8852             switch (lastLoadGameStart) {
8853               case GNUChessGame:
8854               case XBoardGame:
8855               case PGNTag:
8856                 break;
8857               case MoveNumberOne:
8858               case (ChessMove) 0:
8859                 gn--;           /* count this game */
8860                 lastLoadGameStart = cm;
8861                 break;
8862               default:
8863                 /* impossible */
8864                 break;
8865             }
8866             break;
8867
8868           case PGNTag:
8869             switch (lastLoadGameStart) {
8870               case GNUChessGame:
8871               case PGNTag:
8872               case MoveNumberOne:
8873               case (ChessMove) 0:
8874                 gn--;           /* count this game */
8875                 lastLoadGameStart = cm;
8876                 break;
8877               case XBoardGame:
8878                 lastLoadGameStart = cm; /* game counted already */
8879                 break;
8880               default:
8881                 /* impossible */
8882                 break;
8883             }
8884             if (gn > 0) {
8885                 do {
8886                     yyboardindex = forwardMostMove;
8887                     cm = (ChessMove) yylex();
8888                 } while (cm == PGNTag || cm == Comment);
8889             }
8890             break;
8891
8892           case WhiteWins:
8893           case BlackWins:
8894           case GameIsDrawn:
8895             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8896                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8897                     != CMAIL_OLD_RESULT) {
8898                     nCmailResults ++ ;
8899                     cmailResult[  CMAIL_MAX_GAMES
8900                                 - gn - 1] = CMAIL_OLD_RESULT;
8901                 }
8902             }
8903             break;
8904
8905           case NormalMove:
8906             /* Only a NormalMove can be at the start of a game
8907              * without a position diagram. */
8908             if (lastLoadGameStart == (ChessMove) 0) {
8909               gn--;
8910               lastLoadGameStart = MoveNumberOne;
8911             }
8912             break;
8913
8914           default:
8915             break;
8916         }
8917     }
8918     
8919     if (appData.debugMode)
8920       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8921
8922     if (cm == XBoardGame) {
8923         /* Skip any header junk before position diagram and/or move 1 */
8924         for (;;) {
8925             yyboardindex = forwardMostMove;
8926             cm = (ChessMove) yylex();
8927
8928             if (cm == (ChessMove) 0 ||
8929                 cm == GNUChessGame || cm == XBoardGame) {
8930                 /* Empty game; pretend end-of-file and handle later */
8931                 cm = (ChessMove) 0;
8932                 break;
8933             }
8934
8935             if (cm == MoveNumberOne || cm == PositionDiagram ||
8936                 cm == PGNTag || cm == Comment)
8937               break;
8938         }
8939     } else if (cm == GNUChessGame) {
8940         if (gameInfo.event != NULL) {
8941             free(gameInfo.event);
8942         }
8943         gameInfo.event = StrSave(yy_text);
8944     }   
8945
8946     startedFromSetupPosition = FALSE;
8947     while (cm == PGNTag) {
8948         if (appData.debugMode) 
8949           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8950         err = ParsePGNTag(yy_text, &gameInfo);
8951         if (!err) numPGNTags++;
8952
8953         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8954         if(gameInfo.variant != oldVariant) {
8955             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8956             InitPosition(TRUE);
8957             oldVariant = gameInfo.variant;
8958             if (appData.debugMode) 
8959               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8960         }
8961
8962
8963         if (gameInfo.fen != NULL) {
8964           Board initial_position;
8965           startedFromSetupPosition = TRUE;
8966           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8967             Reset(TRUE, TRUE);
8968             DisplayError(_("Bad FEN position in file"), 0);
8969             return FALSE;
8970           }
8971           CopyBoard(boards[0], initial_position);
8972           if (blackPlaysFirst) {
8973             currentMove = forwardMostMove = backwardMostMove = 1;
8974             CopyBoard(boards[1], initial_position);
8975             strcpy(moveList[0], "");
8976             strcpy(parseList[0], "");
8977             timeRemaining[0][1] = whiteTimeRemaining;
8978             timeRemaining[1][1] = blackTimeRemaining;
8979             if (commentList[0] != NULL) {
8980               commentList[1] = commentList[0];
8981               commentList[0] = NULL;
8982             }
8983           } else {
8984             currentMove = forwardMostMove = backwardMostMove = 0;
8985           }
8986           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8987           {   int i;
8988               initialRulePlies = FENrulePlies;
8989               epStatus[forwardMostMove] = FENepStatus;
8990               for( i=0; i< nrCastlingRights; i++ )
8991                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8992           }
8993           yyboardindex = forwardMostMove;
8994           free(gameInfo.fen);
8995           gameInfo.fen = NULL;
8996         }
8997
8998         yyboardindex = forwardMostMove;
8999         cm = (ChessMove) yylex();
9000
9001         /* Handle comments interspersed among the tags */
9002         while (cm == Comment) {
9003             char *p;
9004             if (appData.debugMode) 
9005               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9006             p = yy_text;
9007             if (*p == '{' || *p == '[' || *p == '(') {
9008                 p[strlen(p) - 1] = NULLCHAR;
9009                 p++;
9010             }
9011             while (*p == '\n') p++;
9012             AppendComment(currentMove, p);
9013             yyboardindex = forwardMostMove;
9014             cm = (ChessMove) yylex();
9015         }
9016     }
9017
9018     /* don't rely on existence of Event tag since if game was
9019      * pasted from clipboard the Event tag may not exist
9020      */
9021     if (numPGNTags > 0){
9022         char *tags;
9023         if (gameInfo.variant == VariantNormal) {
9024           gameInfo.variant = StringToVariant(gameInfo.event);
9025         }
9026         if (!matchMode) {
9027           if( appData.autoDisplayTags ) {
9028             tags = PGNTags(&gameInfo);
9029             TagsPopUp(tags, CmailMsg());
9030             free(tags);
9031           }
9032         }
9033     } else {
9034         /* Make something up, but don't display it now */
9035         SetGameInfo();
9036         TagsPopDown();
9037     }
9038
9039     if (cm == PositionDiagram) {
9040         int i, j;
9041         char *p;
9042         Board initial_position;
9043
9044         if (appData.debugMode)
9045           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9046
9047         if (!startedFromSetupPosition) {
9048             p = yy_text;
9049             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9050               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9051                 switch (*p) {
9052                   case '[':
9053                   case '-':
9054                   case ' ':
9055                   case '\t':
9056                   case '\n':
9057                   case '\r':
9058                     break;
9059                   default:
9060                     initial_position[i][j++] = CharToPiece(*p);
9061                     break;
9062                 }
9063             while (*p == ' ' || *p == '\t' ||
9064                    *p == '\n' || *p == '\r') p++;
9065         
9066             if (strncmp(p, "black", strlen("black"))==0)
9067               blackPlaysFirst = TRUE;
9068             else
9069               blackPlaysFirst = FALSE;
9070             startedFromSetupPosition = TRUE;
9071         
9072             CopyBoard(boards[0], initial_position);
9073             if (blackPlaysFirst) {
9074                 currentMove = forwardMostMove = backwardMostMove = 1;
9075                 CopyBoard(boards[1], initial_position);
9076                 strcpy(moveList[0], "");
9077                 strcpy(parseList[0], "");
9078                 timeRemaining[0][1] = whiteTimeRemaining;
9079                 timeRemaining[1][1] = blackTimeRemaining;
9080                 if (commentList[0] != NULL) {
9081                     commentList[1] = commentList[0];
9082                     commentList[0] = NULL;
9083                 }
9084             } else {
9085                 currentMove = forwardMostMove = backwardMostMove = 0;
9086             }
9087         }
9088         yyboardindex = forwardMostMove;
9089         cm = (ChessMove) yylex();
9090     }
9091
9092     if (first.pr == NoProc) {
9093         StartChessProgram(&first);
9094     }
9095     InitChessProgram(&first, FALSE);
9096     SendToProgram("force\n", &first);
9097     if (startedFromSetupPosition) {
9098         SendBoard(&first, forwardMostMove);
9099     if (appData.debugMode) {
9100         fprintf(debugFP, "Load Game\n");
9101     }
9102         DisplayBothClocks();
9103     }      
9104
9105     /* [HGM] server: flag to write setup moves in broadcast file as one */
9106     loadFlag = appData.suppressLoadMoves;
9107
9108     while (cm == Comment) {
9109         char *p;
9110         if (appData.debugMode) 
9111           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9112         p = yy_text;
9113         if (*p == '{' || *p == '[' || *p == '(') {
9114             p[strlen(p) - 1] = NULLCHAR;
9115             p++;
9116         }
9117         while (*p == '\n') p++;
9118         AppendComment(currentMove, p);
9119         yyboardindex = forwardMostMove;
9120         cm = (ChessMove) yylex();
9121     }
9122
9123     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9124         cm == WhiteWins || cm == BlackWins ||
9125         cm == GameIsDrawn || cm == GameUnfinished) {
9126         DisplayMessage("", _("No moves in game"));
9127         if (cmailMsgLoaded) {
9128             if (appData.debugMode)
9129               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9130             ClearHighlights();
9131             flipView = FALSE;
9132         }
9133         DrawPosition(FALSE, boards[currentMove]);
9134         DisplayBothClocks();
9135         gameMode = EditGame;
9136         ModeHighlight();
9137         gameFileFP = NULL;
9138         cmailOldMove = 0;
9139         return TRUE;
9140     }
9141
9142     // [HGM] PV info: routine tests if comment empty
9143     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9144         DisplayComment(currentMove - 1, commentList[currentMove]);
9145     }
9146     if (!matchMode && appData.timeDelay != 0) 
9147       DrawPosition(FALSE, boards[currentMove]);
9148
9149     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9150       programStats.ok_to_send = 1;
9151     }
9152
9153     /* if the first token after the PGN tags is a move
9154      * and not move number 1, retrieve it from the parser 
9155      */
9156     if (cm != MoveNumberOne)
9157         LoadGameOneMove(cm);
9158
9159     /* load the remaining moves from the file */
9160     while (LoadGameOneMove((ChessMove)0)) {
9161       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9162       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9163     }
9164
9165     /* rewind to the start of the game */
9166     currentMove = backwardMostMove;
9167
9168     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9169
9170     if (oldGameMode == AnalyzeFile ||
9171         oldGameMode == AnalyzeMode) {
9172       AnalyzeFileEvent();
9173     }
9174
9175     if (matchMode || appData.timeDelay == 0) {
9176       ToEndEvent();
9177       gameMode = EditGame;
9178       ModeHighlight();
9179     } else if (appData.timeDelay > 0) {
9180       AutoPlayGameLoop();
9181     }
9182
9183     if (appData.debugMode) 
9184         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9185
9186     loadFlag = 0; /* [HGM] true game starts */
9187     return TRUE;
9188 }
9189
9190 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9191 int
9192 ReloadPosition(offset)
9193      int offset;
9194 {
9195     int positionNumber = lastLoadPositionNumber + offset;
9196     if (lastLoadPositionFP == NULL) {
9197         DisplayError(_("No position has been loaded yet"), 0);
9198         return FALSE;
9199     }
9200     if (positionNumber <= 0) {
9201         DisplayError(_("Can't back up any further"), 0);
9202         return FALSE;
9203     }
9204     return LoadPosition(lastLoadPositionFP, positionNumber,
9205                         lastLoadPositionTitle);
9206 }
9207
9208 /* Load the nth position from the given file */
9209 int
9210 LoadPositionFromFile(filename, n, title)
9211      char *filename;
9212      int n;
9213      char *title;
9214 {
9215     FILE *f;
9216     char buf[MSG_SIZ];
9217
9218     if (strcmp(filename, "-") == 0) {
9219         return LoadPosition(stdin, n, "stdin");
9220     } else {
9221         f = fopen(filename, "rb");
9222         if (f == NULL) {
9223             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9224             DisplayError(buf, errno);
9225             return FALSE;
9226         } else {
9227             return LoadPosition(f, n, title);
9228         }
9229     }
9230 }
9231
9232 /* Load the nth position from the given open file, and close it */
9233 int
9234 LoadPosition(f, positionNumber, title)
9235      FILE *f;
9236      int positionNumber;
9237      char *title;
9238 {
9239     char *p, line[MSG_SIZ];
9240     Board initial_position;
9241     int i, j, fenMode, pn;
9242     
9243     if (gameMode == Training )
9244         SetTrainingModeOff();
9245
9246     if (gameMode != BeginningOfGame) {
9247         Reset(FALSE, TRUE);
9248     }
9249     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9250         fclose(lastLoadPositionFP);
9251     }
9252     if (positionNumber == 0) positionNumber = 1;
9253     lastLoadPositionFP = f;
9254     lastLoadPositionNumber = positionNumber;
9255     strcpy(lastLoadPositionTitle, title);
9256     if (first.pr == NoProc) {
9257       StartChessProgram(&first);
9258       InitChessProgram(&first, FALSE);
9259     }    
9260     pn = positionNumber;
9261     if (positionNumber < 0) {
9262         /* Negative position number means to seek to that byte offset */
9263         if (fseek(f, -positionNumber, 0) == -1) {
9264             DisplayError(_("Can't seek on position file"), 0);
9265             return FALSE;
9266         };
9267         pn = 1;
9268     } else {
9269         if (fseek(f, 0, 0) == -1) {
9270             if (f == lastLoadPositionFP ?
9271                 positionNumber == lastLoadPositionNumber + 1 :
9272                 positionNumber == 1) {
9273                 pn = 1;
9274             } else {
9275                 DisplayError(_("Can't seek on position file"), 0);
9276                 return FALSE;
9277             }
9278         }
9279     }
9280     /* See if this file is FEN or old-style xboard */
9281     if (fgets(line, MSG_SIZ, f) == NULL) {
9282         DisplayError(_("Position not found in file"), 0);
9283         return FALSE;
9284     }
9285     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9286     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9287
9288     if (pn >= 2) {
9289         if (fenMode || line[0] == '#') pn--;
9290         while (pn > 0) {
9291             /* skip positions before number pn */
9292             if (fgets(line, MSG_SIZ, f) == NULL) {
9293                 Reset(TRUE, TRUE);
9294                 DisplayError(_("Position not found in file"), 0);
9295                 return FALSE;
9296             }
9297             if (fenMode || line[0] == '#') pn--;
9298         }
9299     }
9300
9301     if (fenMode) {
9302         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9303             DisplayError(_("Bad FEN position in file"), 0);
9304             return FALSE;
9305         }
9306     } else {
9307         (void) fgets(line, MSG_SIZ, f);
9308         (void) fgets(line, MSG_SIZ, f);
9309     
9310         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9311             (void) fgets(line, MSG_SIZ, f);
9312             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9313                 if (*p == ' ')
9314                   continue;
9315                 initial_position[i][j++] = CharToPiece(*p);
9316             }
9317         }
9318     
9319         blackPlaysFirst = FALSE;
9320         if (!feof(f)) {
9321             (void) fgets(line, MSG_SIZ, f);
9322             if (strncmp(line, "black", strlen("black"))==0)
9323               blackPlaysFirst = TRUE;
9324         }
9325     }
9326     startedFromSetupPosition = TRUE;
9327     
9328     SendToProgram("force\n", &first);
9329     CopyBoard(boards[0], initial_position);
9330     if (blackPlaysFirst) {
9331         currentMove = forwardMostMove = backwardMostMove = 1;
9332         strcpy(moveList[0], "");
9333         strcpy(parseList[0], "");
9334         CopyBoard(boards[1], initial_position);
9335         DisplayMessage("", _("Black to play"));
9336     } else {
9337         currentMove = forwardMostMove = backwardMostMove = 0;
9338         DisplayMessage("", _("White to play"));
9339     }
9340           /* [HGM] copy FEN attributes as well */
9341           {   int i;
9342               initialRulePlies = FENrulePlies;
9343               epStatus[forwardMostMove] = FENepStatus;
9344               for( i=0; i< nrCastlingRights; i++ )
9345                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9346           }
9347     SendBoard(&first, forwardMostMove);
9348     if (appData.debugMode) {
9349 int i, j;
9350   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9351   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9352         fprintf(debugFP, "Load Position\n");
9353     }
9354
9355     if (positionNumber > 1) {
9356         sprintf(line, "%s %d", title, positionNumber);
9357         DisplayTitle(line);
9358     } else {
9359         DisplayTitle(title);
9360     }
9361     gameMode = EditGame;
9362     ModeHighlight();
9363     ResetClocks();
9364     timeRemaining[0][1] = whiteTimeRemaining;
9365     timeRemaining[1][1] = blackTimeRemaining;
9366     DrawPosition(FALSE, boards[currentMove]);
9367    
9368     return TRUE;
9369 }
9370
9371
9372 void
9373 CopyPlayerNameIntoFileName(dest, src)
9374      char **dest, *src;
9375 {
9376     while (*src != NULLCHAR && *src != ',') {
9377         if (*src == ' ') {
9378             *(*dest)++ = '_';
9379             src++;
9380         } else {
9381             *(*dest)++ = *src++;
9382         }
9383     }
9384 }
9385
9386 char *DefaultFileName(ext)
9387      char *ext;
9388 {
9389     static char def[MSG_SIZ];
9390     char *p;
9391
9392     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9393         p = def;
9394         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9395         *p++ = '-';
9396         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9397         *p++ = '.';
9398         strcpy(p, ext);
9399     } else {
9400         def[0] = NULLCHAR;
9401     }
9402     return def;
9403 }
9404
9405 /* Save the current game to the given file */
9406 int
9407 SaveGameToFile(filename, append)
9408      char *filename;
9409      int append;
9410 {
9411     FILE *f;
9412     char buf[MSG_SIZ];
9413
9414     if (strcmp(filename, "-") == 0) {
9415         return SaveGame(stdout, 0, NULL);
9416     } else {
9417         f = fopen(filename, append ? "a" : "w");
9418         if (f == NULL) {
9419             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9420             DisplayError(buf, errno);
9421             return FALSE;
9422         } else {
9423             return SaveGame(f, 0, NULL);
9424         }
9425     }
9426 }
9427
9428 char *
9429 SavePart(str)
9430      char *str;
9431 {
9432     static char buf[MSG_SIZ];
9433     char *p;
9434     
9435     p = strchr(str, ' ');
9436     if (p == NULL) return str;
9437     strncpy(buf, str, p - str);
9438     buf[p - str] = NULLCHAR;
9439     return buf;
9440 }
9441
9442 #define PGN_MAX_LINE 75
9443
9444 #define PGN_SIDE_WHITE  0
9445 #define PGN_SIDE_BLACK  1
9446
9447 /* [AS] */
9448 static int FindFirstMoveOutOfBook( int side )
9449 {
9450     int result = -1;
9451
9452     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9453         int index = backwardMostMove;
9454         int has_book_hit = 0;
9455
9456         if( (index % 2) != side ) {
9457             index++;
9458         }
9459
9460         while( index < forwardMostMove ) {
9461             /* Check to see if engine is in book */
9462             int depth = pvInfoList[index].depth;
9463             int score = pvInfoList[index].score;
9464             int in_book = 0;
9465
9466             if( depth <= 2 ) {
9467                 in_book = 1;
9468             }
9469             else if( score == 0 && depth == 63 ) {
9470                 in_book = 1; /* Zappa */
9471             }
9472             else if( score == 2 && depth == 99 ) {
9473                 in_book = 1; /* Abrok */
9474             }
9475
9476             has_book_hit += in_book;
9477
9478             if( ! in_book ) {
9479                 result = index;
9480
9481                 break;
9482             }
9483
9484             index += 2;
9485         }
9486     }
9487
9488     return result;
9489 }
9490
9491 /* [AS] */
9492 void GetOutOfBookInfo( char * buf )
9493 {
9494     int oob[2];
9495     int i;
9496     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9497
9498     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9499     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9500
9501     *buf = '\0';
9502
9503     if( oob[0] >= 0 || oob[1] >= 0 ) {
9504         for( i=0; i<2; i++ ) {
9505             int idx = oob[i];
9506
9507             if( idx >= 0 ) {
9508                 if( i > 0 && oob[0] >= 0 ) {
9509                     strcat( buf, "   " );
9510                 }
9511
9512                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9513                 sprintf( buf+strlen(buf), "%s%.2f", 
9514                     pvInfoList[idx].score >= 0 ? "+" : "",
9515                     pvInfoList[idx].score / 100.0 );
9516             }
9517         }
9518     }
9519 }
9520
9521 /* Save game in PGN style and close the file */
9522 int
9523 SaveGamePGN(f)
9524      FILE *f;
9525 {
9526     int i, offset, linelen, newblock;
9527     time_t tm;
9528 //    char *movetext;
9529     char numtext[32];
9530     int movelen, numlen, blank;
9531     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9532
9533     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9534     
9535     tm = time((time_t *) NULL);
9536     
9537     PrintPGNTags(f, &gameInfo);
9538     
9539     if (backwardMostMove > 0 || startedFromSetupPosition) {
9540         char *fen = PositionToFEN(backwardMostMove, NULL);
9541         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9542         fprintf(f, "\n{--------------\n");
9543         PrintPosition(f, backwardMostMove);
9544         fprintf(f, "--------------}\n");
9545         free(fen);
9546     }
9547     else {
9548         /* [AS] Out of book annotation */
9549         if( appData.saveOutOfBookInfo ) {
9550             char buf[64];
9551
9552             GetOutOfBookInfo( buf );
9553
9554             if( buf[0] != '\0' ) {
9555                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9556             }
9557         }
9558
9559         fprintf(f, "\n");
9560     }
9561
9562     i = backwardMostMove;
9563     linelen = 0;
9564     newblock = TRUE;
9565
9566     while (i < forwardMostMove) {
9567         /* Print comments preceding this move */
9568         if (commentList[i] != NULL) {
9569             if (linelen > 0) fprintf(f, "\n");
9570             fprintf(f, "{\n%s}\n", commentList[i]);
9571             linelen = 0;
9572             newblock = TRUE;
9573         }
9574
9575         /* Format move number */
9576         if ((i % 2) == 0) {
9577             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9578         } else {
9579             if (newblock) {
9580                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9581             } else {
9582                 numtext[0] = NULLCHAR;
9583             }
9584         }
9585         numlen = strlen(numtext);
9586         newblock = FALSE;
9587
9588         /* Print move number */
9589         blank = linelen > 0 && numlen > 0;
9590         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9591             fprintf(f, "\n");
9592             linelen = 0;
9593             blank = 0;
9594         }
9595         if (blank) {
9596             fprintf(f, " ");
9597             linelen++;
9598         }
9599         fprintf(f, numtext);
9600         linelen += numlen;
9601
9602         /* Get move */
9603         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9604         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9605
9606         /* Print move */
9607         blank = linelen > 0 && movelen > 0;
9608         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9609             fprintf(f, "\n");
9610             linelen = 0;
9611             blank = 0;
9612         }
9613         if (blank) {
9614             fprintf(f, " ");
9615             linelen++;
9616         }
9617         fprintf(f, move_buffer);
9618         linelen += movelen;
9619
9620         /* [AS] Add PV info if present */
9621         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9622             /* [HGM] add time */
9623             char buf[MSG_SIZ]; int seconds = 0;
9624
9625             if(i >= backwardMostMove) {
9626                 if(WhiteOnMove(i))
9627                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9628                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9629                 else
9630                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9631                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9632             }
9633             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9634
9635             if( seconds <= 0) buf[0] = 0; else
9636             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9637                 seconds = (seconds + 4)/10; // round to full seconds
9638                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9639                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9640             }
9641
9642             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9643                 pvInfoList[i].score >= 0 ? "+" : "",
9644                 pvInfoList[i].score / 100.0,
9645                 pvInfoList[i].depth,
9646                 buf );
9647
9648             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9649
9650             /* Print score/depth */
9651             blank = linelen > 0 && movelen > 0;
9652             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9653                 fprintf(f, "\n");
9654                 linelen = 0;
9655                 blank = 0;
9656             }
9657             if (blank) {
9658                 fprintf(f, " ");
9659                 linelen++;
9660             }
9661             fprintf(f, move_buffer);
9662             linelen += movelen;
9663         }
9664
9665         i++;
9666     }
9667     
9668     /* Start a new line */
9669     if (linelen > 0) fprintf(f, "\n");
9670
9671     /* Print comments after last move */
9672     if (commentList[i] != NULL) {
9673         fprintf(f, "{\n%s}\n", commentList[i]);
9674     }
9675
9676     /* Print result */
9677     if (gameInfo.resultDetails != NULL &&
9678         gameInfo.resultDetails[0] != NULLCHAR) {
9679         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9680                 PGNResult(gameInfo.result));
9681     } else {
9682         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9683     }
9684
9685     fclose(f);
9686     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9687     return TRUE;
9688 }
9689
9690 /* Save game in old style and close the file */
9691 int
9692 SaveGameOldStyle(f)
9693      FILE *f;
9694 {
9695     int i, offset;
9696     time_t tm;
9697     
9698     tm = time((time_t *) NULL);
9699     
9700     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9701     PrintOpponents(f);
9702     
9703     if (backwardMostMove > 0 || startedFromSetupPosition) {
9704         fprintf(f, "\n[--------------\n");
9705         PrintPosition(f, backwardMostMove);
9706         fprintf(f, "--------------]\n");
9707     } else {
9708         fprintf(f, "\n");
9709     }
9710
9711     i = backwardMostMove;
9712     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9713
9714     while (i < forwardMostMove) {
9715         if (commentList[i] != NULL) {
9716             fprintf(f, "[%s]\n", commentList[i]);
9717         }
9718
9719         if ((i % 2) == 1) {
9720             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9721             i++;
9722         } else {
9723             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9724             i++;
9725             if (commentList[i] != NULL) {
9726                 fprintf(f, "\n");
9727                 continue;
9728             }
9729             if (i >= forwardMostMove) {
9730                 fprintf(f, "\n");
9731                 break;
9732             }
9733             fprintf(f, "%s\n", parseList[i]);
9734             i++;
9735         }
9736     }
9737     
9738     if (commentList[i] != NULL) {
9739         fprintf(f, "[%s]\n", commentList[i]);
9740     }
9741
9742     /* This isn't really the old style, but it's close enough */
9743     if (gameInfo.resultDetails != NULL &&
9744         gameInfo.resultDetails[0] != NULLCHAR) {
9745         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9746                 gameInfo.resultDetails);
9747     } else {
9748         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9749     }
9750
9751     fclose(f);
9752     return TRUE;
9753 }
9754
9755 /* Save the current game to open file f and close the file */
9756 int
9757 SaveGame(f, dummy, dummy2)
9758      FILE *f;
9759      int dummy;
9760      char *dummy2;
9761 {
9762     if (gameMode == EditPosition) EditPositionDone();
9763     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9764     if (appData.oldSaveStyle)
9765       return SaveGameOldStyle(f);
9766     else
9767       return SaveGamePGN(f);
9768 }
9769
9770 /* Save the current position to the given file */
9771 int
9772 SavePositionToFile(filename)
9773      char *filename;
9774 {
9775     FILE *f;
9776     char buf[MSG_SIZ];
9777
9778     if (strcmp(filename, "-") == 0) {
9779         return SavePosition(stdout, 0, NULL);
9780     } else {
9781         f = fopen(filename, "a");
9782         if (f == NULL) {
9783             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9784             DisplayError(buf, errno);
9785             return FALSE;
9786         } else {
9787             SavePosition(f, 0, NULL);
9788             return TRUE;
9789         }
9790     }
9791 }
9792
9793 /* Save the current position to the given open file and close the file */
9794 int
9795 SavePosition(f, dummy, dummy2)
9796      FILE *f;
9797      int dummy;
9798      char *dummy2;
9799 {
9800     time_t tm;
9801     char *fen;
9802     
9803     if (appData.oldSaveStyle) {
9804         tm = time((time_t *) NULL);
9805     
9806         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9807         PrintOpponents(f);
9808         fprintf(f, "[--------------\n");
9809         PrintPosition(f, currentMove);
9810         fprintf(f, "--------------]\n");
9811     } else {
9812         fen = PositionToFEN(currentMove, NULL);
9813         fprintf(f, "%s\n", fen);
9814         free(fen);
9815     }
9816     fclose(f);
9817     return TRUE;
9818 }
9819
9820 void
9821 ReloadCmailMsgEvent(unregister)
9822      int unregister;
9823 {
9824 #if !WIN32
9825     static char *inFilename = NULL;
9826     static char *outFilename;
9827     int i;
9828     struct stat inbuf, outbuf;
9829     int status;
9830     
9831     /* Any registered moves are unregistered if unregister is set, */
9832     /* i.e. invoked by the signal handler */
9833     if (unregister) {
9834         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9835             cmailMoveRegistered[i] = FALSE;
9836             if (cmailCommentList[i] != NULL) {
9837                 free(cmailCommentList[i]);
9838                 cmailCommentList[i] = NULL;
9839             }
9840         }
9841         nCmailMovesRegistered = 0;
9842     }
9843
9844     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9845         cmailResult[i] = CMAIL_NOT_RESULT;
9846     }
9847     nCmailResults = 0;
9848
9849     if (inFilename == NULL) {
9850         /* Because the filenames are static they only get malloced once  */
9851         /* and they never get freed                                      */
9852         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9853         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9854
9855         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9856         sprintf(outFilename, "%s.out", appData.cmailGameName);
9857     }
9858     
9859     status = stat(outFilename, &outbuf);
9860     if (status < 0) {
9861         cmailMailedMove = FALSE;
9862     } else {
9863         status = stat(inFilename, &inbuf);
9864         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9865     }
9866     
9867     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9868        counts the games, notes how each one terminated, etc.
9869        
9870        It would be nice to remove this kludge and instead gather all
9871        the information while building the game list.  (And to keep it
9872        in the game list nodes instead of having a bunch of fixed-size
9873        parallel arrays.)  Note this will require getting each game's
9874        termination from the PGN tags, as the game list builder does
9875        not process the game moves.  --mann
9876        */
9877     cmailMsgLoaded = TRUE;
9878     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9879     
9880     /* Load first game in the file or popup game menu */
9881     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9882
9883 #endif /* !WIN32 */
9884     return;
9885 }
9886
9887 int
9888 RegisterMove()
9889 {
9890     FILE *f;
9891     char string[MSG_SIZ];
9892
9893     if (   cmailMailedMove
9894         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9895         return TRUE;            /* Allow free viewing  */
9896     }
9897
9898     /* Unregister move to ensure that we don't leave RegisterMove        */
9899     /* with the move registered when the conditions for registering no   */
9900     /* longer hold                                                       */
9901     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9902         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9903         nCmailMovesRegistered --;
9904
9905         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9906           {
9907               free(cmailCommentList[lastLoadGameNumber - 1]);
9908               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9909           }
9910     }
9911
9912     if (cmailOldMove == -1) {
9913         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9914         return FALSE;
9915     }
9916
9917     if (currentMove > cmailOldMove + 1) {
9918         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9919         return FALSE;
9920     }
9921
9922     if (currentMove < cmailOldMove) {
9923         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9924         return FALSE;
9925     }
9926
9927     if (forwardMostMove > currentMove) {
9928         /* Silently truncate extra moves */
9929         TruncateGame();
9930     }
9931
9932     if (   (currentMove == cmailOldMove + 1)
9933         || (   (currentMove == cmailOldMove)
9934             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9935                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9936         if (gameInfo.result != GameUnfinished) {
9937             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9938         }
9939
9940         if (commentList[currentMove] != NULL) {
9941             cmailCommentList[lastLoadGameNumber - 1]
9942               = StrSave(commentList[currentMove]);
9943         }
9944         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9945
9946         if (appData.debugMode)
9947           fprintf(debugFP, "Saving %s for game %d\n",
9948                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9949
9950         sprintf(string,
9951                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9952         
9953         f = fopen(string, "w");
9954         if (appData.oldSaveStyle) {
9955             SaveGameOldStyle(f); /* also closes the file */
9956             
9957             sprintf(string, "%s.pos.out", appData.cmailGameName);
9958             f = fopen(string, "w");
9959             SavePosition(f, 0, NULL); /* also closes the file */
9960         } else {
9961             fprintf(f, "{--------------\n");
9962             PrintPosition(f, currentMove);
9963             fprintf(f, "--------------}\n\n");
9964             
9965             SaveGame(f, 0, NULL); /* also closes the file*/
9966         }
9967         
9968         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9969         nCmailMovesRegistered ++;
9970     } else if (nCmailGames == 1) {
9971         DisplayError(_("You have not made a move yet"), 0);
9972         return FALSE;
9973     }
9974
9975     return TRUE;
9976 }
9977
9978 void
9979 MailMoveEvent()
9980 {
9981 #if !WIN32
9982     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9983     FILE *commandOutput;
9984     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9985     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
9986     int nBuffers;
9987     int i;
9988     int archived;
9989     char *arcDir;
9990
9991     if (! cmailMsgLoaded) {
9992         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9993         return;
9994     }
9995
9996     if (nCmailGames == nCmailResults) {
9997         DisplayError(_("No unfinished games"), 0);
9998         return;
9999     }
10000
10001 #if CMAIL_PROHIBIT_REMAIL
10002     if (cmailMailedMove) {
10003         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);
10004         DisplayError(msg, 0);
10005         return;
10006     }
10007 #endif
10008
10009     if (! (cmailMailedMove || RegisterMove())) return;
10010     
10011     if (   cmailMailedMove
10012         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10013         sprintf(string, partCommandString,
10014                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10015         commandOutput = popen(string, "r");
10016
10017         if (commandOutput == NULL) {
10018             DisplayError(_("Failed to invoke cmail"), 0);
10019         } else {
10020             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10021                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10022             }
10023             if (nBuffers > 1) {
10024                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10025                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10026                 nBytes = MSG_SIZ - 1;
10027             } else {
10028                 (void) memcpy(msg, buffer, nBytes);
10029             }
10030             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10031
10032             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10033                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10034
10035                 archived = TRUE;
10036                 for (i = 0; i < nCmailGames; i ++) {
10037                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10038                         archived = FALSE;
10039                     }
10040                 }
10041                 if (   archived
10042                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10043                         != NULL)) {
10044                     sprintf(buffer, "%s/%s.%s.archive",
10045                             arcDir,
10046                             appData.cmailGameName,
10047                             gameInfo.date);
10048                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10049                     cmailMsgLoaded = FALSE;
10050                 }
10051             }
10052
10053             DisplayInformation(msg);
10054             pclose(commandOutput);
10055         }
10056     } else {
10057         if ((*cmailMsg) != '\0') {
10058             DisplayInformation(cmailMsg);
10059         }
10060     }
10061
10062     return;
10063 #endif /* !WIN32 */
10064 }
10065
10066 char *
10067 CmailMsg()
10068 {
10069 #if WIN32
10070     return NULL;
10071 #else
10072     int  prependComma = 0;
10073     char number[5];
10074     char string[MSG_SIZ];       /* Space for game-list */
10075     int  i;
10076     
10077     if (!cmailMsgLoaded) return "";
10078
10079     if (cmailMailedMove) {
10080         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10081     } else {
10082         /* Create a list of games left */
10083         sprintf(string, "[");
10084         for (i = 0; i < nCmailGames; i ++) {
10085             if (! (   cmailMoveRegistered[i]
10086                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10087                 if (prependComma) {
10088                     sprintf(number, ",%d", i + 1);
10089                 } else {
10090                     sprintf(number, "%d", i + 1);
10091                     prependComma = 1;
10092                 }
10093                 
10094                 strcat(string, number);
10095             }
10096         }
10097         strcat(string, "]");
10098
10099         if (nCmailMovesRegistered + nCmailResults == 0) {
10100             switch (nCmailGames) {
10101               case 1:
10102                 sprintf(cmailMsg,
10103                         _("Still need to make move for game\n"));
10104                 break;
10105                 
10106               case 2:
10107                 sprintf(cmailMsg,
10108                         _("Still need to make moves for both games\n"));
10109                 break;
10110                 
10111               default:
10112                 sprintf(cmailMsg,
10113                         _("Still need to make moves for all %d games\n"),
10114                         nCmailGames);
10115                 break;
10116             }
10117         } else {
10118             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10119               case 1:
10120                 sprintf(cmailMsg,
10121                         _("Still need to make a move for game %s\n"),
10122                         string);
10123                 break;
10124                 
10125               case 0:
10126                 if (nCmailResults == nCmailGames) {
10127                     sprintf(cmailMsg, _("No unfinished games\n"));
10128                 } else {
10129                     sprintf(cmailMsg, _("Ready to send mail\n"));
10130                 }
10131                 break;
10132                 
10133               default:
10134                 sprintf(cmailMsg,
10135                         _("Still need to make moves for games %s\n"),
10136                         string);
10137             }
10138         }
10139     }
10140     return cmailMsg;
10141 #endif /* WIN32 */
10142 }
10143
10144 void
10145 ResetGameEvent()
10146 {
10147     if (gameMode == Training)
10148       SetTrainingModeOff();
10149
10150     Reset(TRUE, TRUE);
10151     cmailMsgLoaded = FALSE;
10152     if (appData.icsActive) {
10153       SendToICS(ics_prefix);
10154       SendToICS("refresh\n");
10155     }
10156 }
10157
10158 void
10159 ExitEvent(status)
10160      int status;
10161 {
10162     exiting++;
10163     if (exiting > 2) {
10164       /* Give up on clean exit */
10165       exit(status);
10166     }
10167     if (exiting > 1) {
10168       /* Keep trying for clean exit */
10169       return;
10170     }
10171
10172     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10173
10174     if (telnetISR != NULL) {
10175       RemoveInputSource(telnetISR);
10176     }
10177     if (icsPR != NoProc) {
10178       DestroyChildProcess(icsPR, TRUE);
10179     }
10180
10181     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10182     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10183
10184     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10185     /* make sure this other one finishes before killing it!                  */
10186     if(endingGame) { int count = 0;
10187         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10188         while(endingGame && count++ < 10) DoSleep(1);
10189         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10190     }
10191
10192     /* Kill off chess programs */
10193     if (first.pr != NoProc) {
10194         ExitAnalyzeMode();
10195         
10196         DoSleep( appData.delayBeforeQuit );
10197         SendToProgram("quit\n", &first);
10198         DoSleep( appData.delayAfterQuit );
10199         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10200     }
10201     if (second.pr != NoProc) {
10202         DoSleep( appData.delayBeforeQuit );
10203         SendToProgram("quit\n", &second);
10204         DoSleep( appData.delayAfterQuit );
10205         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10206     }
10207     if (first.isr != NULL) {
10208         RemoveInputSource(first.isr);
10209     }
10210     if (second.isr != NULL) {
10211         RemoveInputSource(second.isr);
10212     }
10213
10214     ShutDownFrontEnd();
10215     exit(status);
10216 }
10217
10218 void
10219 PauseEvent()
10220 {
10221     if (appData.debugMode)
10222         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10223     if (pausing) {
10224         pausing = FALSE;
10225         ModeHighlight();
10226         if (gameMode == MachinePlaysWhite ||
10227             gameMode == MachinePlaysBlack) {
10228             StartClocks();
10229         } else {
10230             DisplayBothClocks();
10231         }
10232         if (gameMode == PlayFromGameFile) {
10233             if (appData.timeDelay >= 0) 
10234                 AutoPlayGameLoop();
10235         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10236             Reset(FALSE, TRUE);
10237             SendToICS(ics_prefix);
10238             SendToICS("refresh\n");
10239         } else if (currentMove < forwardMostMove) {
10240             ForwardInner(forwardMostMove);
10241         }
10242         pauseExamInvalid = FALSE;
10243     } else {
10244         switch (gameMode) {
10245           default:
10246             return;
10247           case IcsExamining:
10248             pauseExamForwardMostMove = forwardMostMove;
10249             pauseExamInvalid = FALSE;
10250             /* fall through */
10251           case IcsObserving:
10252           case IcsPlayingWhite:
10253           case IcsPlayingBlack:
10254             pausing = TRUE;
10255             ModeHighlight();
10256             return;
10257           case PlayFromGameFile:
10258             (void) StopLoadGameTimer();
10259             pausing = TRUE;
10260             ModeHighlight();
10261             break;
10262           case BeginningOfGame:
10263             if (appData.icsActive) return;
10264             /* else fall through */
10265           case MachinePlaysWhite:
10266           case MachinePlaysBlack:
10267           case TwoMachinesPlay:
10268             if (forwardMostMove == 0)
10269               return;           /* don't pause if no one has moved */
10270             if ((gameMode == MachinePlaysWhite &&
10271                  !WhiteOnMove(forwardMostMove)) ||
10272                 (gameMode == MachinePlaysBlack &&
10273                  WhiteOnMove(forwardMostMove))) {
10274                 StopClocks();
10275             }
10276             pausing = TRUE;
10277             ModeHighlight();
10278             break;
10279         }
10280     }
10281 }
10282
10283 void
10284 EditCommentEvent()
10285 {
10286     char title[MSG_SIZ];
10287
10288     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10289         strcpy(title, _("Edit comment"));
10290     } else {
10291         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10292                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10293                 parseList[currentMove - 1]);
10294     }
10295
10296     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10297 }
10298
10299
10300 void
10301 EditTagsEvent()
10302 {
10303     char *tags = PGNTags(&gameInfo);
10304     EditTagsPopUp(tags);
10305     free(tags);
10306 }
10307
10308 void
10309 AnalyzeModeEvent()
10310 {
10311     if (appData.noChessProgram || gameMode == AnalyzeMode)
10312       return;
10313
10314     if (gameMode != AnalyzeFile) {
10315         if (!appData.icsEngineAnalyze) {
10316                EditGameEvent();
10317                if (gameMode != EditGame) return;
10318         }
10319         ResurrectChessProgram();
10320         SendToProgram("analyze\n", &first);
10321         first.analyzing = TRUE;
10322         /*first.maybeThinking = TRUE;*/
10323         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10324         AnalysisPopUp(_("Analysis"),
10325                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10326     }
10327     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10328     pausing = FALSE;
10329     ModeHighlight();
10330     SetGameInfo();
10331
10332     StartAnalysisClock();
10333     GetTimeMark(&lastNodeCountTime);
10334     lastNodeCount = 0;
10335 }
10336
10337 void
10338 AnalyzeFileEvent()
10339 {
10340     if (appData.noChessProgram || gameMode == AnalyzeFile)
10341       return;
10342
10343     if (gameMode != AnalyzeMode) {
10344         EditGameEvent();
10345         if (gameMode != EditGame) return;
10346         ResurrectChessProgram();
10347         SendToProgram("analyze\n", &first);
10348         first.analyzing = TRUE;
10349         /*first.maybeThinking = TRUE;*/
10350         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10351         AnalysisPopUp(_("Analysis"),
10352                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
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     AnalysisPopDown();
10851     thinkOutput[0] = NULLCHAR;
10852 }
10853
10854 void
10855 EditPositionDone()
10856 {
10857     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10858
10859     startedFromSetupPosition = TRUE;
10860     InitChessProgram(&first, FALSE);
10861     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10862     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10863         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10864         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10865     } else castlingRights[0][2] = -1;
10866     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10867         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10868         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10869     } else castlingRights[0][5] = -1;
10870     SendToProgram("force\n", &first);
10871     if (blackPlaysFirst) {
10872         strcpy(moveList[0], "");
10873         strcpy(parseList[0], "");
10874         currentMove = forwardMostMove = backwardMostMove = 1;
10875         CopyBoard(boards[1], boards[0]);
10876         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10877         { int i;
10878           epStatus[1] = epStatus[0];
10879           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10880         }
10881     } else {
10882         currentMove = forwardMostMove = backwardMostMove = 0;
10883     }
10884     SendBoard(&first, forwardMostMove);
10885     if (appData.debugMode) {
10886         fprintf(debugFP, "EditPosDone\n");
10887     }
10888     DisplayTitle("");
10889     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10890     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10891     gameMode = EditGame;
10892     ModeHighlight();
10893     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10894     ClearHighlights(); /* [AS] */
10895 }
10896
10897 /* Pause for `ms' milliseconds */
10898 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10899 void
10900 TimeDelay(ms)
10901      long ms;
10902 {
10903     TimeMark m1, m2;
10904
10905     GetTimeMark(&m1);
10906     do {
10907         GetTimeMark(&m2);
10908     } while (SubtractTimeMarks(&m2, &m1) < ms);
10909 }
10910
10911 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10912 void
10913 SendMultiLineToICS(buf)
10914      char *buf;
10915 {
10916     char temp[MSG_SIZ+1], *p;
10917     int len;
10918
10919     len = strlen(buf);
10920     if (len > MSG_SIZ)
10921       len = MSG_SIZ;
10922   
10923     strncpy(temp, buf, len);
10924     temp[len] = 0;
10925
10926     p = temp;
10927     while (*p) {
10928         if (*p == '\n' || *p == '\r')
10929           *p = ' ';
10930         ++p;
10931     }
10932
10933     strcat(temp, "\n");
10934     SendToICS(temp);
10935     SendToPlayer(temp, strlen(temp));
10936 }
10937
10938 void
10939 SetWhiteToPlayEvent()
10940 {
10941     if (gameMode == EditPosition) {
10942         blackPlaysFirst = FALSE;
10943         DisplayBothClocks();    /* works because currentMove is 0 */
10944     } else if (gameMode == IcsExamining) {
10945         SendToICS(ics_prefix);
10946         SendToICS("tomove white\n");
10947     }
10948 }
10949
10950 void
10951 SetBlackToPlayEvent()
10952 {
10953     if (gameMode == EditPosition) {
10954         blackPlaysFirst = TRUE;
10955         currentMove = 1;        /* kludge */
10956         DisplayBothClocks();
10957         currentMove = 0;
10958     } else if (gameMode == IcsExamining) {
10959         SendToICS(ics_prefix);
10960         SendToICS("tomove black\n");
10961     }
10962 }
10963
10964 void
10965 EditPositionMenuEvent(selection, x, y)
10966      ChessSquare selection;
10967      int x, y;
10968 {
10969     char buf[MSG_SIZ];
10970     ChessSquare piece = boards[0][y][x];
10971
10972     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10973
10974     switch (selection) {
10975       case ClearBoard:
10976         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10977             SendToICS(ics_prefix);
10978             SendToICS("bsetup clear\n");
10979         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10980             SendToICS(ics_prefix);
10981             SendToICS("clearboard\n");
10982         } else {
10983             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10984                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10985                 for (y = 0; y < BOARD_HEIGHT; y++) {
10986                     if (gameMode == IcsExamining) {
10987                         if (boards[currentMove][y][x] != EmptySquare) {
10988                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
10989                                     AAA + x, ONE + y);
10990                             SendToICS(buf);
10991                         }
10992                     } else {
10993                         boards[0][y][x] = p;
10994                     }
10995                 }
10996             }
10997         }
10998         if (gameMode == EditPosition) {
10999             DrawPosition(FALSE, boards[0]);
11000         }
11001         break;
11002
11003       case WhitePlay:
11004         SetWhiteToPlayEvent();
11005         break;
11006
11007       case BlackPlay:
11008         SetBlackToPlayEvent();
11009         break;
11010
11011       case EmptySquare:
11012         if (gameMode == IcsExamining) {
11013             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11014             SendToICS(buf);
11015         } else {
11016             boards[0][y][x] = EmptySquare;
11017             DrawPosition(FALSE, boards[0]);
11018         }
11019         break;
11020
11021       case PromotePiece:
11022         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11023            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11024             selection = (ChessSquare) (PROMOTED piece);
11025         } else if(piece == EmptySquare) selection = WhiteSilver;
11026         else selection = (ChessSquare)((int)piece - 1);
11027         goto defaultlabel;
11028
11029       case DemotePiece:
11030         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11031            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11032             selection = (ChessSquare) (DEMOTED piece);
11033         } else if(piece == EmptySquare) selection = BlackSilver;
11034         else selection = (ChessSquare)((int)piece + 1);       
11035         goto defaultlabel;
11036
11037       case WhiteQueen:
11038       case BlackQueen:
11039         if(gameInfo.variant == VariantShatranj ||
11040            gameInfo.variant == VariantXiangqi  ||
11041            gameInfo.variant == VariantCourier    )
11042             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11043         goto defaultlabel;
11044
11045       case WhiteKing:
11046       case BlackKing:
11047         if(gameInfo.variant == VariantXiangqi)
11048             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11049         if(gameInfo.variant == VariantKnightmate)
11050             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11051       default:
11052         defaultlabel:
11053         if (gameMode == IcsExamining) {
11054             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11055                     PieceToChar(selection), AAA + x, ONE + y);
11056             SendToICS(buf);
11057         } else {
11058             boards[0][y][x] = selection;
11059             DrawPosition(FALSE, boards[0]);
11060         }
11061         break;
11062     }
11063 }
11064
11065
11066 void
11067 DropMenuEvent(selection, x, y)
11068      ChessSquare selection;
11069      int x, y;
11070 {
11071     ChessMove moveType;
11072
11073     switch (gameMode) {
11074       case IcsPlayingWhite:
11075       case MachinePlaysBlack:
11076         if (!WhiteOnMove(currentMove)) {
11077             DisplayMoveError(_("It is Black's turn"));
11078             return;
11079         }
11080         moveType = WhiteDrop;
11081         break;
11082       case IcsPlayingBlack:
11083       case MachinePlaysWhite:
11084         if (WhiteOnMove(currentMove)) {
11085             DisplayMoveError(_("It is White's turn"));
11086             return;
11087         }
11088         moveType = BlackDrop;
11089         break;
11090       case EditGame:
11091         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11092         break;
11093       default:
11094         return;
11095     }
11096
11097     if (moveType == BlackDrop && selection < BlackPawn) {
11098       selection = (ChessSquare) ((int) selection
11099                                  + (int) BlackPawn - (int) WhitePawn);
11100     }
11101     if (boards[currentMove][y][x] != EmptySquare) {
11102         DisplayMoveError(_("That square is occupied"));
11103         return;
11104     }
11105
11106     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11107 }
11108
11109 void
11110 AcceptEvent()
11111 {
11112     /* Accept a pending offer of any kind from opponent */
11113     
11114     if (appData.icsActive) {
11115         SendToICS(ics_prefix);
11116         SendToICS("accept\n");
11117     } else if (cmailMsgLoaded) {
11118         if (currentMove == cmailOldMove &&
11119             commentList[cmailOldMove] != NULL &&
11120             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11121                    "Black offers a draw" : "White offers a draw")) {
11122             TruncateGame();
11123             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11124             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11125         } else {
11126             DisplayError(_("There is no pending offer on this move"), 0);
11127             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11128         }
11129     } else {
11130         /* Not used for offers from chess program */
11131     }
11132 }
11133
11134 void
11135 DeclineEvent()
11136 {
11137     /* Decline a pending offer of any kind from opponent */
11138     
11139     if (appData.icsActive) {
11140         SendToICS(ics_prefix);
11141         SendToICS("decline\n");
11142     } else if (cmailMsgLoaded) {
11143         if (currentMove == cmailOldMove &&
11144             commentList[cmailOldMove] != NULL &&
11145             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11146                    "Black offers a draw" : "White offers a draw")) {
11147 #ifdef NOTDEF
11148             AppendComment(cmailOldMove, "Draw declined");
11149             DisplayComment(cmailOldMove - 1, "Draw declined");
11150 #endif /*NOTDEF*/
11151         } else {
11152             DisplayError(_("There is no pending offer on this move"), 0);
11153         }
11154     } else {
11155         /* Not used for offers from chess program */
11156     }
11157 }
11158
11159 void
11160 RematchEvent()
11161 {
11162     /* Issue ICS rematch command */
11163     if (appData.icsActive) {
11164         SendToICS(ics_prefix);
11165         SendToICS("rematch\n");
11166     }
11167 }
11168
11169 void
11170 CallFlagEvent()
11171 {
11172     /* Call your opponent's flag (claim a win on time) */
11173     if (appData.icsActive) {
11174         SendToICS(ics_prefix);
11175         SendToICS("flag\n");
11176     } else {
11177         switch (gameMode) {
11178           default:
11179             return;
11180           case MachinePlaysWhite:
11181             if (whiteFlag) {
11182                 if (blackFlag)
11183                   GameEnds(GameIsDrawn, "Both players ran out of time",
11184                            GE_PLAYER);
11185                 else
11186                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11187             } else {
11188                 DisplayError(_("Your opponent is not out of time"), 0);
11189             }
11190             break;
11191           case MachinePlaysBlack:
11192             if (blackFlag) {
11193                 if (whiteFlag)
11194                   GameEnds(GameIsDrawn, "Both players ran out of time",
11195                            GE_PLAYER);
11196                 else
11197                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11198             } else {
11199                 DisplayError(_("Your opponent is not out of time"), 0);
11200             }
11201             break;
11202         }
11203     }
11204 }
11205
11206 void
11207 DrawEvent()
11208 {
11209     /* Offer draw or accept pending draw offer from opponent */
11210     
11211     if (appData.icsActive) {
11212         /* Note: tournament rules require draw offers to be
11213            made after you make your move but before you punch
11214            your clock.  Currently ICS doesn't let you do that;
11215            instead, you immediately punch your clock after making
11216            a move, but you can offer a draw at any time. */
11217         
11218         SendToICS(ics_prefix);
11219         SendToICS("draw\n");
11220     } else if (cmailMsgLoaded) {
11221         if (currentMove == cmailOldMove &&
11222             commentList[cmailOldMove] != NULL &&
11223             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11224                    "Black offers a draw" : "White offers a draw")) {
11225             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11226             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11227         } else if (currentMove == cmailOldMove + 1) {
11228             char *offer = WhiteOnMove(cmailOldMove) ?
11229               "White offers a draw" : "Black offers a draw";
11230             AppendComment(currentMove, offer);
11231             DisplayComment(currentMove - 1, offer);
11232             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11233         } else {
11234             DisplayError(_("You must make your move before offering a draw"), 0);
11235             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11236         }
11237     } else if (first.offeredDraw) {
11238         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11239     } else {
11240         if (first.sendDrawOffers) {
11241             SendToProgram("draw\n", &first);
11242             userOfferedDraw = TRUE;
11243         }
11244     }
11245 }
11246
11247 void
11248 AdjournEvent()
11249 {
11250     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11251     
11252     if (appData.icsActive) {
11253         SendToICS(ics_prefix);
11254         SendToICS("adjourn\n");
11255     } else {
11256         /* Currently GNU Chess doesn't offer or accept Adjourns */
11257     }
11258 }
11259
11260
11261 void
11262 AbortEvent()
11263 {
11264     /* Offer Abort or accept pending Abort offer from opponent */
11265     
11266     if (appData.icsActive) {
11267         SendToICS(ics_prefix);
11268         SendToICS("abort\n");
11269     } else {
11270         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11271     }
11272 }
11273
11274 void
11275 ResignEvent()
11276 {
11277     /* Resign.  You can do this even if it's not your turn. */
11278     
11279     if (appData.icsActive) {
11280         SendToICS(ics_prefix);
11281         SendToICS("resign\n");
11282     } else {
11283         switch (gameMode) {
11284           case MachinePlaysWhite:
11285             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11286             break;
11287           case MachinePlaysBlack:
11288             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11289             break;
11290           case EditGame:
11291             if (cmailMsgLoaded) {
11292                 TruncateGame();
11293                 if (WhiteOnMove(cmailOldMove)) {
11294                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11295                 } else {
11296                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11297                 }
11298                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11299             }
11300             break;
11301           default:
11302             break;
11303         }
11304     }
11305 }
11306
11307
11308 void
11309 StopObservingEvent()
11310 {
11311     /* Stop observing current games */
11312     SendToICS(ics_prefix);
11313     SendToICS("unobserve\n");
11314 }
11315
11316 void
11317 StopExaminingEvent()
11318 {
11319     /* Stop observing current game */
11320     SendToICS(ics_prefix);
11321     SendToICS("unexamine\n");
11322 }
11323
11324 void
11325 ForwardInner(target)
11326      int target;
11327 {
11328     int limit;
11329
11330     if (appData.debugMode)
11331         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11332                 target, currentMove, forwardMostMove);
11333
11334     if (gameMode == EditPosition)
11335       return;
11336
11337     if (gameMode == PlayFromGameFile && !pausing)
11338       PauseEvent();
11339     
11340     if (gameMode == IcsExamining && pausing)
11341       limit = pauseExamForwardMostMove;
11342     else
11343       limit = forwardMostMove;
11344     
11345     if (target > limit) target = limit;
11346
11347     if (target > 0 && moveList[target - 1][0]) {
11348         int fromX, fromY, toX, toY;
11349         toX = moveList[target - 1][2] - AAA;
11350         toY = moveList[target - 1][3] - ONE;
11351         if (moveList[target - 1][1] == '@') {
11352             if (appData.highlightLastMove) {
11353                 SetHighlights(-1, -1, toX, toY);
11354             }
11355         } else {
11356             fromX = moveList[target - 1][0] - AAA;
11357             fromY = moveList[target - 1][1] - ONE;
11358             if (target == currentMove + 1) {
11359                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11360             }
11361             if (appData.highlightLastMove) {
11362                 SetHighlights(fromX, fromY, toX, toY);
11363             }
11364         }
11365     }
11366     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11367         gameMode == Training || gameMode == PlayFromGameFile || 
11368         gameMode == AnalyzeFile) {
11369         while (currentMove < target) {
11370             SendMoveToProgram(currentMove++, &first);
11371         }
11372     } else {
11373         currentMove = target;
11374     }
11375     
11376     if (gameMode == EditGame || gameMode == EndOfGame) {
11377         whiteTimeRemaining = timeRemaining[0][currentMove];
11378         blackTimeRemaining = timeRemaining[1][currentMove];
11379     }
11380     DisplayBothClocks();
11381     DisplayMove(currentMove - 1);
11382     DrawPosition(FALSE, boards[currentMove]);
11383     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11384     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11385         DisplayComment(currentMove - 1, commentList[currentMove]);
11386     }
11387 }
11388
11389
11390 void
11391 ForwardEvent()
11392 {
11393     if (gameMode == IcsExamining && !pausing) {
11394         SendToICS(ics_prefix);
11395         SendToICS("forward\n");
11396     } else {
11397         ForwardInner(currentMove + 1);
11398     }
11399 }
11400
11401 void
11402 ToEndEvent()
11403 {
11404     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11405         /* to optimze, we temporarily turn off analysis mode while we feed
11406          * the remaining moves to the engine. Otherwise we get analysis output
11407          * after each move.
11408          */ 
11409         if (first.analysisSupport) {
11410           SendToProgram("exit\nforce\n", &first);
11411           first.analyzing = FALSE;
11412         }
11413     }
11414         
11415     if (gameMode == IcsExamining && !pausing) {
11416         SendToICS(ics_prefix);
11417         SendToICS("forward 999999\n");
11418     } else {
11419         ForwardInner(forwardMostMove);
11420     }
11421
11422     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11423         /* we have fed all the moves, so reactivate analysis mode */
11424         SendToProgram("analyze\n", &first);
11425         first.analyzing = TRUE;
11426         /*first.maybeThinking = TRUE;*/
11427         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11428     }
11429 }
11430
11431 void
11432 BackwardInner(target)
11433      int target;
11434 {
11435     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11436
11437     if (appData.debugMode)
11438         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11439                 target, currentMove, forwardMostMove);
11440
11441     if (gameMode == EditPosition) return;
11442     if (currentMove <= backwardMostMove) {
11443         ClearHighlights();
11444         DrawPosition(full_redraw, boards[currentMove]);
11445         return;
11446     }
11447     if (gameMode == PlayFromGameFile && !pausing)
11448       PauseEvent();
11449     
11450     if (moveList[target][0]) {
11451         int fromX, fromY, toX, toY;
11452         toX = moveList[target][2] - AAA;
11453         toY = moveList[target][3] - ONE;
11454         if (moveList[target][1] == '@') {
11455             if (appData.highlightLastMove) {
11456                 SetHighlights(-1, -1, toX, toY);
11457             }
11458         } else {
11459             fromX = moveList[target][0] - AAA;
11460             fromY = moveList[target][1] - ONE;
11461             if (target == currentMove - 1) {
11462                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11463             }
11464             if (appData.highlightLastMove) {
11465                 SetHighlights(fromX, fromY, toX, toY);
11466             }
11467         }
11468     }
11469     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11470         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11471         while (currentMove > target) {
11472             SendToProgram("undo\n", &first);
11473             currentMove--;
11474         }
11475     } else {
11476         currentMove = target;
11477     }
11478     
11479     if (gameMode == EditGame || gameMode == EndOfGame) {
11480         whiteTimeRemaining = timeRemaining[0][currentMove];
11481         blackTimeRemaining = timeRemaining[1][currentMove];
11482     }
11483     DisplayBothClocks();
11484     DisplayMove(currentMove - 1);
11485     DrawPosition(full_redraw, boards[currentMove]);
11486     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11487     // [HGM] PV info: routine tests if comment empty
11488     DisplayComment(currentMove - 1, commentList[currentMove]);
11489 }
11490
11491 void
11492 BackwardEvent()
11493 {
11494     if (gameMode == IcsExamining && !pausing) {
11495         SendToICS(ics_prefix);
11496         SendToICS("backward\n");
11497     } else {
11498         BackwardInner(currentMove - 1);
11499     }
11500 }
11501
11502 void
11503 ToStartEvent()
11504 {
11505     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11506         /* to optimze, we temporarily turn off analysis mode while we undo
11507          * all the moves. Otherwise we get analysis output after each undo.
11508          */ 
11509         if (first.analysisSupport) {
11510           SendToProgram("exit\nforce\n", &first);
11511           first.analyzing = FALSE;
11512         }
11513     }
11514
11515     if (gameMode == IcsExamining && !pausing) {
11516         SendToICS(ics_prefix);
11517         SendToICS("backward 999999\n");
11518     } else {
11519         BackwardInner(backwardMostMove);
11520     }
11521
11522     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11523         /* we have fed all the moves, so reactivate analysis mode */
11524         SendToProgram("analyze\n", &first);
11525         first.analyzing = TRUE;
11526         /*first.maybeThinking = TRUE;*/
11527         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11528     }
11529 }
11530
11531 void
11532 ToNrEvent(int to)
11533 {
11534   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11535   if (to >= forwardMostMove) to = forwardMostMove;
11536   if (to <= backwardMostMove) to = backwardMostMove;
11537   if (to < currentMove) {
11538     BackwardInner(to);
11539   } else {
11540     ForwardInner(to);
11541   }
11542 }
11543
11544 void
11545 RevertEvent()
11546 {
11547     if (gameMode != IcsExamining) {
11548         DisplayError(_("You are not examining a game"), 0);
11549         return;
11550     }
11551     if (pausing) {
11552         DisplayError(_("You can't revert while pausing"), 0);
11553         return;
11554     }
11555     SendToICS(ics_prefix);
11556     SendToICS("revert\n");
11557 }
11558
11559 void
11560 RetractMoveEvent()
11561 {
11562     switch (gameMode) {
11563       case MachinePlaysWhite:
11564       case MachinePlaysBlack:
11565         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11566             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11567             return;
11568         }
11569         if (forwardMostMove < 2) return;
11570         currentMove = forwardMostMove = forwardMostMove - 2;
11571         whiteTimeRemaining = timeRemaining[0][currentMove];
11572         blackTimeRemaining = timeRemaining[1][currentMove];
11573         DisplayBothClocks();
11574         DisplayMove(currentMove - 1);
11575         ClearHighlights();/*!! could figure this out*/
11576         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11577         SendToProgram("remove\n", &first);
11578         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11579         break;
11580
11581       case BeginningOfGame:
11582       default:
11583         break;
11584
11585       case IcsPlayingWhite:
11586       case IcsPlayingBlack:
11587         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11588             SendToICS(ics_prefix);
11589             SendToICS("takeback 2\n");
11590         } else {
11591             SendToICS(ics_prefix);
11592             SendToICS("takeback 1\n");
11593         }
11594         break;
11595     }
11596 }
11597
11598 void
11599 MoveNowEvent()
11600 {
11601     ChessProgramState *cps;
11602
11603     switch (gameMode) {
11604       case MachinePlaysWhite:
11605         if (!WhiteOnMove(forwardMostMove)) {
11606             DisplayError(_("It is your turn"), 0);
11607             return;
11608         }
11609         cps = &first;
11610         break;
11611       case MachinePlaysBlack:
11612         if (WhiteOnMove(forwardMostMove)) {
11613             DisplayError(_("It is your turn"), 0);
11614             return;
11615         }
11616         cps = &first;
11617         break;
11618       case TwoMachinesPlay:
11619         if (WhiteOnMove(forwardMostMove) ==
11620             (first.twoMachinesColor[0] == 'w')) {
11621             cps = &first;
11622         } else {
11623             cps = &second;
11624         }
11625         break;
11626       case BeginningOfGame:
11627       default:
11628         return;
11629     }
11630     SendToProgram("?\n", cps);
11631 }
11632
11633 void
11634 TruncateGameEvent()
11635 {
11636     EditGameEvent();
11637     if (gameMode != EditGame) return;
11638     TruncateGame();
11639 }
11640
11641 void
11642 TruncateGame()
11643 {
11644     if (forwardMostMove > currentMove) {
11645         if (gameInfo.resultDetails != NULL) {
11646             free(gameInfo.resultDetails);
11647             gameInfo.resultDetails = NULL;
11648             gameInfo.result = GameUnfinished;
11649         }
11650         forwardMostMove = currentMove;
11651         HistorySet(parseList, backwardMostMove, forwardMostMove,
11652                    currentMove-1);
11653     }
11654 }
11655
11656 void
11657 HintEvent()
11658 {
11659     if (appData.noChessProgram) return;
11660     switch (gameMode) {
11661       case MachinePlaysWhite:
11662         if (WhiteOnMove(forwardMostMove)) {
11663             DisplayError(_("Wait until your turn"), 0);
11664             return;
11665         }
11666         break;
11667       case BeginningOfGame:
11668       case MachinePlaysBlack:
11669         if (!WhiteOnMove(forwardMostMove)) {
11670             DisplayError(_("Wait until your turn"), 0);
11671             return;
11672         }
11673         break;
11674       default:
11675         DisplayError(_("No hint available"), 0);
11676         return;
11677     }
11678     SendToProgram("hint\n", &first);
11679     hintRequested = TRUE;
11680 }
11681
11682 void
11683 BookEvent()
11684 {
11685     if (appData.noChessProgram) return;
11686     switch (gameMode) {
11687       case MachinePlaysWhite:
11688         if (WhiteOnMove(forwardMostMove)) {
11689             DisplayError(_("Wait until your turn"), 0);
11690             return;
11691         }
11692         break;
11693       case BeginningOfGame:
11694       case MachinePlaysBlack:
11695         if (!WhiteOnMove(forwardMostMove)) {
11696             DisplayError(_("Wait until your turn"), 0);
11697             return;
11698         }
11699         break;
11700       case EditPosition:
11701         EditPositionDone();
11702         break;
11703       case TwoMachinesPlay:
11704         return;
11705       default:
11706         break;
11707     }
11708     SendToProgram("bk\n", &first);
11709     bookOutput[0] = NULLCHAR;
11710     bookRequested = TRUE;
11711 }
11712
11713 void
11714 AboutGameEvent()
11715 {
11716     char *tags = PGNTags(&gameInfo);
11717     TagsPopUp(tags, CmailMsg());
11718     free(tags);
11719 }
11720
11721 /* end button procedures */
11722
11723 void
11724 PrintPosition(fp, move)
11725      FILE *fp;
11726      int move;
11727 {
11728     int i, j;
11729     
11730     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11731         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11732             char c = PieceToChar(boards[move][i][j]);
11733             fputc(c == 'x' ? '.' : c, fp);
11734             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11735         }
11736     }
11737     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11738       fprintf(fp, "white to play\n");
11739     else
11740       fprintf(fp, "black to play\n");
11741 }
11742
11743 void
11744 PrintOpponents(fp)
11745      FILE *fp;
11746 {
11747     if (gameInfo.white != NULL) {
11748         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11749     } else {
11750         fprintf(fp, "\n");
11751     }
11752 }
11753
11754 /* Find last component of program's own name, using some heuristics */
11755 void
11756 TidyProgramName(prog, host, buf)
11757      char *prog, *host, buf[MSG_SIZ];
11758 {
11759     char *p, *q;
11760     int local = (strcmp(host, "localhost") == 0);
11761     while (!local && (p = strchr(prog, ';')) != NULL) {
11762         p++;
11763         while (*p == ' ') p++;
11764         prog = p;
11765     }
11766     if (*prog == '"' || *prog == '\'') {
11767         q = strchr(prog + 1, *prog);
11768     } else {
11769         q = strchr(prog, ' ');
11770     }
11771     if (q == NULL) q = prog + strlen(prog);
11772     p = q;
11773     while (p >= prog && *p != '/' && *p != '\\') p--;
11774     p++;
11775     if(p == prog && *p == '"') p++;
11776     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11777     memcpy(buf, p, q - p);
11778     buf[q - p] = NULLCHAR;
11779     if (!local) {
11780         strcat(buf, "@");
11781         strcat(buf, host);
11782     }
11783 }
11784
11785 char *
11786 TimeControlTagValue()
11787 {
11788     char buf[MSG_SIZ];
11789     if (!appData.clockMode) {
11790         strcpy(buf, "-");
11791     } else if (movesPerSession > 0) {
11792         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11793     } else if (timeIncrement == 0) {
11794         sprintf(buf, "%ld", timeControl/1000);
11795     } else {
11796         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11797     }
11798     return StrSave(buf);
11799 }
11800
11801 void
11802 SetGameInfo()
11803 {
11804     /* This routine is used only for certain modes */
11805     VariantClass v = gameInfo.variant;
11806     ClearGameInfo(&gameInfo);
11807     gameInfo.variant = v;
11808
11809     switch (gameMode) {
11810       case MachinePlaysWhite:
11811         gameInfo.event = StrSave( appData.pgnEventHeader );
11812         gameInfo.site = StrSave(HostName());
11813         gameInfo.date = PGNDate();
11814         gameInfo.round = StrSave("-");
11815         gameInfo.white = StrSave(first.tidy);
11816         gameInfo.black = StrSave(UserName());
11817         gameInfo.timeControl = TimeControlTagValue();
11818         break;
11819
11820       case MachinePlaysBlack:
11821         gameInfo.event = StrSave( appData.pgnEventHeader );
11822         gameInfo.site = StrSave(HostName());
11823         gameInfo.date = PGNDate();
11824         gameInfo.round = StrSave("-");
11825         gameInfo.white = StrSave(UserName());
11826         gameInfo.black = StrSave(first.tidy);
11827         gameInfo.timeControl = TimeControlTagValue();
11828         break;
11829
11830       case TwoMachinesPlay:
11831         gameInfo.event = StrSave( appData.pgnEventHeader );
11832         gameInfo.site = StrSave(HostName());
11833         gameInfo.date = PGNDate();
11834         if (matchGame > 0) {
11835             char buf[MSG_SIZ];
11836             sprintf(buf, "%d", matchGame);
11837             gameInfo.round = StrSave(buf);
11838         } else {
11839             gameInfo.round = StrSave("-");
11840         }
11841         if (first.twoMachinesColor[0] == 'w') {
11842             gameInfo.white = StrSave(first.tidy);
11843             gameInfo.black = StrSave(second.tidy);
11844         } else {
11845             gameInfo.white = StrSave(second.tidy);
11846             gameInfo.black = StrSave(first.tidy);
11847         }
11848         gameInfo.timeControl = TimeControlTagValue();
11849         break;
11850
11851       case EditGame:
11852         gameInfo.event = StrSave("Edited game");
11853         gameInfo.site = StrSave(HostName());
11854         gameInfo.date = PGNDate();
11855         gameInfo.round = StrSave("-");
11856         gameInfo.white = StrSave("-");
11857         gameInfo.black = StrSave("-");
11858         break;
11859
11860       case EditPosition:
11861         gameInfo.event = StrSave("Edited position");
11862         gameInfo.site = StrSave(HostName());
11863         gameInfo.date = PGNDate();
11864         gameInfo.round = StrSave("-");
11865         gameInfo.white = StrSave("-");
11866         gameInfo.black = StrSave("-");
11867         break;
11868
11869       case IcsPlayingWhite:
11870       case IcsPlayingBlack:
11871       case IcsObserving:
11872       case IcsExamining:
11873         break;
11874
11875       case PlayFromGameFile:
11876         gameInfo.event = StrSave("Game from non-PGN file");
11877         gameInfo.site = StrSave(HostName());
11878         gameInfo.date = PGNDate();
11879         gameInfo.round = StrSave("-");
11880         gameInfo.white = StrSave("?");
11881         gameInfo.black = StrSave("?");
11882         break;
11883
11884       default:
11885         break;
11886     }
11887 }
11888
11889 void
11890 ReplaceComment(index, text)
11891      int index;
11892      char *text;
11893 {
11894     int len;
11895
11896     while (*text == '\n') text++;
11897     len = strlen(text);
11898     while (len > 0 && text[len - 1] == '\n') len--;
11899
11900     if (commentList[index] != NULL)
11901       free(commentList[index]);
11902
11903     if (len == 0) {
11904         commentList[index] = NULL;
11905         return;
11906     }
11907     commentList[index] = (char *) malloc(len + 2);
11908     strncpy(commentList[index], text, len);
11909     commentList[index][len] = '\n';
11910     commentList[index][len + 1] = NULLCHAR;
11911 }
11912
11913 void
11914 CrushCRs(text)
11915      char *text;
11916 {
11917   char *p = text;
11918   char *q = text;
11919   char ch;
11920
11921   do {
11922     ch = *p++;
11923     if (ch == '\r') continue;
11924     *q++ = ch;
11925   } while (ch != '\0');
11926 }
11927
11928 void
11929 AppendComment(index, text)
11930      int index;
11931      char *text;
11932 {
11933     int oldlen, len;
11934     char *old;
11935
11936     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11937
11938     CrushCRs(text);
11939     while (*text == '\n') text++;
11940     len = strlen(text);
11941     while (len > 0 && text[len - 1] == '\n') len--;
11942
11943     if (len == 0) return;
11944
11945     if (commentList[index] != NULL) {
11946         old = commentList[index];
11947         oldlen = strlen(old);
11948         commentList[index] = (char *) malloc(oldlen + len + 2);
11949         strcpy(commentList[index], old);
11950         free(old);
11951         strncpy(&commentList[index][oldlen], text, len);
11952         commentList[index][oldlen + len] = '\n';
11953         commentList[index][oldlen + len + 1] = NULLCHAR;
11954     } else {
11955         commentList[index] = (char *) malloc(len + 2);
11956         strncpy(commentList[index], text, len);
11957         commentList[index][len] = '\n';
11958         commentList[index][len + 1] = NULLCHAR;
11959     }
11960 }
11961
11962 static char * FindStr( char * text, char * sub_text )
11963 {
11964     char * result = strstr( text, sub_text );
11965
11966     if( result != NULL ) {
11967         result += strlen( sub_text );
11968     }
11969
11970     return result;
11971 }
11972
11973 /* [AS] Try to extract PV info from PGN comment */
11974 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11975 char *GetInfoFromComment( int index, char * text )
11976 {
11977     char * sep = text;
11978
11979     if( text != NULL && index > 0 ) {
11980         int score = 0;
11981         int depth = 0;
11982         int time = -1, sec = 0, deci;
11983         char * s_eval = FindStr( text, "[%eval " );
11984         char * s_emt = FindStr( text, "[%emt " );
11985
11986         if( s_eval != NULL || s_emt != NULL ) {
11987             /* New style */
11988             char delim;
11989
11990             if( s_eval != NULL ) {
11991                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11992                     return text;
11993                 }
11994
11995                 if( delim != ']' ) {
11996                     return text;
11997                 }
11998             }
11999
12000             if( s_emt != NULL ) {
12001             }
12002         }
12003         else {
12004             /* We expect something like: [+|-]nnn.nn/dd */
12005             int score_lo = 0;
12006
12007             sep = strchr( text, '/' );
12008             if( sep == NULL || sep < (text+4) ) {
12009                 return text;
12010             }
12011
12012             time = -1; sec = -1; deci = -1;
12013             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12014                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12015                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12016                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12017                 return text;
12018             }
12019
12020             if( score_lo < 0 || score_lo >= 100 ) {
12021                 return text;
12022             }
12023
12024             if(sec >= 0) time = 600*time + 10*sec; else
12025             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12026
12027             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12028
12029             /* [HGM] PV time: now locate end of PV info */
12030             while( *++sep >= '0' && *sep <= '9'); // strip depth
12031             if(time >= 0)
12032             while( *++sep >= '0' && *sep <= '9'); // strip time
12033             if(sec >= 0)
12034             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12035             if(deci >= 0)
12036             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12037             while(*sep == ' ') sep++;
12038         }
12039
12040         if( depth <= 0 ) {
12041             return text;
12042         }
12043
12044         if( time < 0 ) {
12045             time = -1;
12046         }
12047
12048         pvInfoList[index-1].depth = depth;
12049         pvInfoList[index-1].score = score;
12050         pvInfoList[index-1].time  = 10*time; // centi-sec
12051     }
12052     return sep;
12053 }
12054
12055 void
12056 SendToProgram(message, cps)
12057      char *message;
12058      ChessProgramState *cps;
12059 {
12060     int count, outCount, error;
12061     char buf[MSG_SIZ];
12062
12063     if (cps->pr == NULL) return;
12064     Attention(cps);
12065     
12066     if (appData.debugMode) {
12067         TimeMark now;
12068         GetTimeMark(&now);
12069         fprintf(debugFP, "%ld >%-6s: %s", 
12070                 SubtractTimeMarks(&now, &programStartTime),
12071                 cps->which, message);
12072     }
12073     
12074     count = strlen(message);
12075     outCount = OutputToProcess(cps->pr, message, count, &error);
12076     if (outCount < count && !exiting 
12077                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12078         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12079         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12080             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12081                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12082                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12083             } else {
12084                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12085             }
12086             gameInfo.resultDetails = buf;
12087         }
12088         DisplayFatalError(buf, error, 1);
12089     }
12090 }
12091
12092 void
12093 ReceiveFromProgram(isr, closure, message, count, error)
12094      InputSourceRef isr;
12095      VOIDSTAR closure;
12096      char *message;
12097      int count;
12098      int error;
12099 {
12100     char *end_str;
12101     char buf[MSG_SIZ];
12102     ChessProgramState *cps = (ChessProgramState *)closure;
12103
12104     if (isr != cps->isr) return; /* Killed intentionally */
12105     if (count <= 0) {
12106         if (count == 0) {
12107             sprintf(buf,
12108                     _("Error: %s chess program (%s) exited unexpectedly"),
12109                     cps->which, cps->program);
12110         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12111                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12112                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12113                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12114                 } else {
12115                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12116                 }
12117                 gameInfo.resultDetails = buf;
12118             }
12119             RemoveInputSource(cps->isr);
12120             DisplayFatalError(buf, 0, 1);
12121         } else {
12122             sprintf(buf,
12123                     _("Error reading from %s chess program (%s)"),
12124                     cps->which, cps->program);
12125             RemoveInputSource(cps->isr);
12126
12127             /* [AS] Program is misbehaving badly... kill it */
12128             if( count == -2 ) {
12129                 DestroyChildProcess( cps->pr, 9 );
12130                 cps->pr = NoProc;
12131             }
12132
12133             DisplayFatalError(buf, error, 1);
12134         }
12135         return;
12136     }
12137     
12138     if ((end_str = strchr(message, '\r')) != NULL)
12139       *end_str = NULLCHAR;
12140     if ((end_str = strchr(message, '\n')) != NULL)
12141       *end_str = NULLCHAR;
12142     
12143     if (appData.debugMode) {
12144         TimeMark now; int print = 1;
12145         char *quote = ""; char c; int i;
12146
12147         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12148                 char start = message[0];
12149                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12150                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12151                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12152                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12153                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12154                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12155                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12156                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12157                         { quote = "# "; print = (appData.engineComments == 2); }
12158                 message[0] = start; // restore original message
12159         }
12160         if(print) {
12161                 GetTimeMark(&now);
12162                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12163                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12164                         quote,
12165                         message);
12166         }
12167     }
12168
12169     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12170     if (appData.icsEngineAnalyze) {
12171         if (strstr(message, "whisper") != NULL ||
12172              strstr(message, "kibitz") != NULL || 
12173             strstr(message, "tellics") != NULL) return;
12174     }
12175
12176     HandleMachineMove(message, cps);
12177 }
12178
12179
12180 void
12181 SendTimeControl(cps, mps, tc, inc, sd, st)
12182      ChessProgramState *cps;
12183      int mps, inc, sd, st;
12184      long tc;
12185 {
12186     char buf[MSG_SIZ];
12187     int seconds;
12188
12189     if( timeControl_2 > 0 ) {
12190         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12191             tc = timeControl_2;
12192         }
12193     }
12194     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12195     inc /= cps->timeOdds;
12196     st  /= cps->timeOdds;
12197
12198     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12199
12200     if (st > 0) {
12201       /* Set exact time per move, normally using st command */
12202       if (cps->stKludge) {
12203         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12204         seconds = st % 60;
12205         if (seconds == 0) {
12206           sprintf(buf, "level 1 %d\n", st/60);
12207         } else {
12208           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12209         }
12210       } else {
12211         sprintf(buf, "st %d\n", st);
12212       }
12213     } else {
12214       /* Set conventional or incremental time control, using level command */
12215       if (seconds == 0) {
12216         /* Note old gnuchess bug -- minutes:seconds used to not work.
12217            Fixed in later versions, but still avoid :seconds
12218            when seconds is 0. */
12219         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12220       } else {
12221         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12222                 seconds, inc/1000);
12223       }
12224     }
12225     SendToProgram(buf, cps);
12226
12227     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12228     /* Orthogonally, limit search to given depth */
12229     if (sd > 0) {
12230       if (cps->sdKludge) {
12231         sprintf(buf, "depth\n%d\n", sd);
12232       } else {
12233         sprintf(buf, "sd %d\n", sd);
12234       }
12235       SendToProgram(buf, cps);
12236     }
12237
12238     if(cps->nps > 0) { /* [HGM] nps */
12239         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12240         else {
12241                 sprintf(buf, "nps %d\n", cps->nps);
12242               SendToProgram(buf, cps);
12243         }
12244     }
12245 }
12246
12247 ChessProgramState *WhitePlayer()
12248 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12249 {
12250     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12251        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12252         return &second;
12253     return &first;
12254 }
12255
12256 void
12257 SendTimeRemaining(cps, machineWhite)
12258      ChessProgramState *cps;
12259      int /*boolean*/ machineWhite;
12260 {
12261     char message[MSG_SIZ];
12262     long time, otime;
12263
12264     /* Note: this routine must be called when the clocks are stopped
12265        or when they have *just* been set or switched; otherwise
12266        it will be off by the time since the current tick started.
12267     */
12268     if (machineWhite) {
12269         time = whiteTimeRemaining / 10;
12270         otime = blackTimeRemaining / 10;
12271     } else {
12272         time = blackTimeRemaining / 10;
12273         otime = whiteTimeRemaining / 10;
12274     }
12275     /* [HGM] translate opponent's time by time-odds factor */
12276     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12277     if (appData.debugMode) {
12278         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12279     }
12280
12281     if (time <= 0) time = 1;
12282     if (otime <= 0) otime = 1;
12283     
12284     sprintf(message, "time %ld\n", time);
12285     SendToProgram(message, cps);
12286
12287     sprintf(message, "otim %ld\n", otime);
12288     SendToProgram(message, cps);
12289 }
12290
12291 int
12292 BoolFeature(p, name, loc, cps)
12293      char **p;
12294      char *name;
12295      int *loc;
12296      ChessProgramState *cps;
12297 {
12298   char buf[MSG_SIZ];
12299   int len = strlen(name);
12300   int val;
12301   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12302     (*p) += len + 1;
12303     sscanf(*p, "%d", &val);
12304     *loc = (val != 0);
12305     while (**p && **p != ' ') (*p)++;
12306     sprintf(buf, "accepted %s\n", name);
12307     SendToProgram(buf, cps);
12308     return TRUE;
12309   }
12310   return FALSE;
12311 }
12312
12313 int
12314 IntFeature(p, name, loc, cps)
12315      char **p;
12316      char *name;
12317      int *loc;
12318      ChessProgramState *cps;
12319 {
12320   char buf[MSG_SIZ];
12321   int len = strlen(name);
12322   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12323     (*p) += len + 1;
12324     sscanf(*p, "%d", loc);
12325     while (**p && **p != ' ') (*p)++;
12326     sprintf(buf, "accepted %s\n", name);
12327     SendToProgram(buf, cps);
12328     return TRUE;
12329   }
12330   return FALSE;
12331 }
12332
12333 int
12334 StringFeature(p, name, loc, cps)
12335      char **p;
12336      char *name;
12337      char loc[];
12338      ChessProgramState *cps;
12339 {
12340   char buf[MSG_SIZ];
12341   int len = strlen(name);
12342   if (strncmp((*p), name, len) == 0
12343       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12344     (*p) += len + 2;
12345     sscanf(*p, "%[^\"]", loc);
12346     while (**p && **p != '\"') (*p)++;
12347     if (**p == '\"') (*p)++;
12348     sprintf(buf, "accepted %s\n", name);
12349     SendToProgram(buf, cps);
12350     return TRUE;
12351   }
12352   return FALSE;
12353 }
12354
12355 int 
12356 ParseOption(Option *opt, ChessProgramState *cps)
12357 // [HGM] options: process the string that defines an engine option, and determine
12358 // name, type, default value, and allowed value range
12359 {
12360         char *p, *q, buf[MSG_SIZ];
12361         int n, min = (-1)<<31, max = 1<<31, def;
12362
12363         if(p = strstr(opt->name, " -spin ")) {
12364             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12365             if(max < min) max = min; // enforce consistency
12366             if(def < min) def = min;
12367             if(def > max) def = max;
12368             opt->value = def;
12369             opt->min = min;
12370             opt->max = max;
12371             opt->type = Spin;
12372         } else if((p = strstr(opt->name, " -slider "))) {
12373             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12374             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12375             if(max < min) max = min; // enforce consistency
12376             if(def < min) def = min;
12377             if(def > max) def = max;
12378             opt->value = def;
12379             opt->min = min;
12380             opt->max = max;
12381             opt->type = Spin; // Slider;
12382         } else if((p = strstr(opt->name, " -string "))) {
12383             opt->textValue = p+9;
12384             opt->type = TextBox;
12385         } else if((p = strstr(opt->name, " -file "))) {
12386             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12387             opt->textValue = p+7;
12388             opt->type = TextBox; // FileName;
12389         } else if((p = strstr(opt->name, " -path "))) {
12390             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12391             opt->textValue = p+7;
12392             opt->type = TextBox; // PathName;
12393         } else if(p = strstr(opt->name, " -check ")) {
12394             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12395             opt->value = (def != 0);
12396             opt->type = CheckBox;
12397         } else if(p = strstr(opt->name, " -combo ")) {
12398             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12399             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12400             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12401             opt->value = n = 0;
12402             while(q = StrStr(q, " /// ")) {
12403                 n++; *q = 0;    // count choices, and null-terminate each of them
12404                 q += 5;
12405                 if(*q == '*') { // remember default, which is marked with * prefix
12406                     q++;
12407                     opt->value = n;
12408                 }
12409                 cps->comboList[cps->comboCnt++] = q;
12410             }
12411             cps->comboList[cps->comboCnt++] = NULL;
12412             opt->max = n + 1;
12413             opt->type = ComboBox;
12414         } else if(p = strstr(opt->name, " -button")) {
12415             opt->type = Button;
12416         } else if(p = strstr(opt->name, " -save")) {
12417             opt->type = SaveButton;
12418         } else return FALSE;
12419         *p = 0; // terminate option name
12420         // now look if the command-line options define a setting for this engine option.
12421         if(cps->optionSettings && cps->optionSettings[0])
12422             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12423         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12424                 sprintf(buf, "option %s", p);
12425                 if(p = strstr(buf, ",")) *p = 0;
12426                 strcat(buf, "\n");
12427                 SendToProgram(buf, cps);
12428         }
12429         return TRUE;
12430 }
12431
12432 void
12433 FeatureDone(cps, val)
12434      ChessProgramState* cps;
12435      int val;
12436 {
12437   DelayedEventCallback cb = GetDelayedEvent();
12438   if ((cb == InitBackEnd3 && cps == &first) ||
12439       (cb == TwoMachinesEventIfReady && cps == &second)) {
12440     CancelDelayedEvent();
12441     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12442   }
12443   cps->initDone = val;
12444 }
12445
12446 /* Parse feature command from engine */
12447 void
12448 ParseFeatures(args, cps)
12449      char* args;
12450      ChessProgramState *cps;  
12451 {
12452   char *p = args;
12453   char *q;
12454   int val;
12455   char buf[MSG_SIZ];
12456
12457   for (;;) {
12458     while (*p == ' ') p++;
12459     if (*p == NULLCHAR) return;
12460
12461     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12462     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12463     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12464     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12465     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12466     if (BoolFeature(&p, "reuse", &val, cps)) {
12467       /* Engine can disable reuse, but can't enable it if user said no */
12468       if (!val) cps->reuse = FALSE;
12469       continue;
12470     }
12471     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12472     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12473       if (gameMode == TwoMachinesPlay) {
12474         DisplayTwoMachinesTitle();
12475       } else {
12476         DisplayTitle("");
12477       }
12478       continue;
12479     }
12480     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12481     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12482     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12483     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12484     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12485     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12486     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12487     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12488     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12489     if (IntFeature(&p, "done", &val, cps)) {
12490       FeatureDone(cps, val);
12491       continue;
12492     }
12493     /* Added by Tord: */
12494     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12495     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12496     /* End of additions by Tord */
12497
12498     /* [HGM] added features: */
12499     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12500     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12501     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12502     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12503     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12504     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12505     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12506         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12507             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12508             SendToProgram(buf, cps);
12509             continue;
12510         }
12511         if(cps->nrOptions >= MAX_OPTIONS) {
12512             cps->nrOptions--;
12513             sprintf(buf, "%s engine has too many options\n", cps->which);
12514             DisplayError(buf, 0);
12515         }
12516         continue;
12517     }
12518     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12519     /* End of additions by HGM */
12520
12521     /* unknown feature: complain and skip */
12522     q = p;
12523     while (*q && *q != '=') q++;
12524     sprintf(buf, "rejected %.*s\n", q-p, p);
12525     SendToProgram(buf, cps);
12526     p = q;
12527     if (*p == '=') {
12528       p++;
12529       if (*p == '\"') {
12530         p++;
12531         while (*p && *p != '\"') p++;
12532         if (*p == '\"') p++;
12533       } else {
12534         while (*p && *p != ' ') p++;
12535       }
12536     }
12537   }
12538
12539 }
12540
12541 void
12542 PeriodicUpdatesEvent(newState)
12543      int newState;
12544 {
12545     if (newState == appData.periodicUpdates)
12546       return;
12547
12548     appData.periodicUpdates=newState;
12549
12550     /* Display type changes, so update it now */
12551     DisplayAnalysis();
12552
12553     /* Get the ball rolling again... */
12554     if (newState) {
12555         AnalysisPeriodicEvent(1);
12556         StartAnalysisClock();
12557     }
12558 }
12559
12560 void
12561 PonderNextMoveEvent(newState)
12562      int newState;
12563 {
12564     if (newState == appData.ponderNextMove) return;
12565     if (gameMode == EditPosition) EditPositionDone();
12566     if (newState) {
12567         SendToProgram("hard\n", &first);
12568         if (gameMode == TwoMachinesPlay) {
12569             SendToProgram("hard\n", &second);
12570         }
12571     } else {
12572         SendToProgram("easy\n", &first);
12573         thinkOutput[0] = NULLCHAR;
12574         if (gameMode == TwoMachinesPlay) {
12575             SendToProgram("easy\n", &second);
12576         }
12577     }
12578     appData.ponderNextMove = newState;
12579 }
12580
12581 void
12582 NewSettingEvent(option, command, value)
12583      char *command;
12584      int option, value;
12585 {
12586     char buf[MSG_SIZ];
12587
12588     if (gameMode == EditPosition) EditPositionDone();
12589     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12590     SendToProgram(buf, &first);
12591     if (gameMode == TwoMachinesPlay) {
12592         SendToProgram(buf, &second);
12593     }
12594 }
12595
12596 void
12597 ShowThinkingEvent()
12598 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12599 {
12600     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12601     int newState = appData.showThinking
12602         // [HGM] thinking: other features now need thinking output as well
12603         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12604     
12605     if (oldState == newState) return;
12606     oldState = newState;
12607     if (gameMode == EditPosition) EditPositionDone();
12608     if (oldState) {
12609         SendToProgram("post\n", &first);
12610         if (gameMode == TwoMachinesPlay) {
12611             SendToProgram("post\n", &second);
12612         }
12613     } else {
12614         SendToProgram("nopost\n", &first);
12615         thinkOutput[0] = NULLCHAR;
12616         if (gameMode == TwoMachinesPlay) {
12617             SendToProgram("nopost\n", &second);
12618         }
12619     }
12620 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12621 }
12622
12623 void
12624 AskQuestionEvent(title, question, replyPrefix, which)
12625      char *title; char *question; char *replyPrefix; char *which;
12626 {
12627   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12628   if (pr == NoProc) return;
12629   AskQuestion(title, question, replyPrefix, pr);
12630 }
12631
12632 void
12633 DisplayMove(moveNumber)
12634      int moveNumber;
12635 {
12636     char message[MSG_SIZ];
12637     char res[MSG_SIZ];
12638     char cpThinkOutput[MSG_SIZ];
12639
12640     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12641     
12642     if (moveNumber == forwardMostMove - 1 || 
12643         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12644
12645         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12646
12647         if (strchr(cpThinkOutput, '\n')) {
12648             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12649         }
12650     } else {
12651         *cpThinkOutput = NULLCHAR;
12652     }
12653
12654     /* [AS] Hide thinking from human user */
12655     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12656         *cpThinkOutput = NULLCHAR;
12657         if( thinkOutput[0] != NULLCHAR ) {
12658             int i;
12659
12660             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12661                 cpThinkOutput[i] = '.';
12662             }
12663             cpThinkOutput[i] = NULLCHAR;
12664             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12665         }
12666     }
12667
12668     if (moveNumber == forwardMostMove - 1 &&
12669         gameInfo.resultDetails != NULL) {
12670         if (gameInfo.resultDetails[0] == NULLCHAR) {
12671             sprintf(res, " %s", PGNResult(gameInfo.result));
12672         } else {
12673             sprintf(res, " {%s} %s",
12674                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12675         }
12676     } else {
12677         res[0] = NULLCHAR;
12678     }
12679
12680     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12681         DisplayMessage(res, cpThinkOutput);
12682     } else {
12683         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12684                 WhiteOnMove(moveNumber) ? " " : ".. ",
12685                 parseList[moveNumber], res);
12686         DisplayMessage(message, cpThinkOutput);
12687     }
12688 }
12689
12690 void
12691 DisplayAnalysisText(text)
12692      char *text;
12693 {
12694     char buf[MSG_SIZ];
12695
12696     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12697                || appData.icsEngineAnalyze) {
12698         sprintf(buf, "Analysis (%s)", first.tidy);
12699         AnalysisPopUp(buf, text);
12700     }
12701 }
12702
12703 static int
12704 only_one_move(str)
12705      char *str;
12706 {
12707     while (*str && isspace(*str)) ++str;
12708     while (*str && !isspace(*str)) ++str;
12709     if (!*str) return 1;
12710     while (*str && isspace(*str)) ++str;
12711     if (!*str) return 1;
12712     return 0;
12713 }
12714
12715 void
12716 DisplayAnalysis()
12717 {
12718     char buf[MSG_SIZ];
12719     char lst[MSG_SIZ / 2];
12720     double nps;
12721     static char *xtra[] = { "", " (--)", " (++)" };
12722     int h, m, s, cs;
12723   
12724     if (programStats.time == 0) {
12725         programStats.time = 1;
12726     }
12727   
12728     if (programStats.got_only_move) {
12729         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12730     } else {
12731         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12732
12733         nps = (u64ToDouble(programStats.nodes) /
12734              ((double)programStats.time /100.0));
12735
12736         cs = programStats.time % 100;
12737         s = programStats.time / 100;
12738         h = (s / (60*60));
12739         s = s - h*60*60;
12740         m = (s/60);
12741         s = s - m*60;
12742
12743         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12744           if (programStats.move_name[0] != NULLCHAR) {
12745             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12746                     programStats.depth,
12747                     programStats.nr_moves-programStats.moves_left,
12748                     programStats.nr_moves, programStats.move_name,
12749                     ((float)programStats.score)/100.0, lst,
12750                     only_one_move(lst)?
12751                     xtra[programStats.got_fail] : "",
12752                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12753           } else {
12754             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12755                     programStats.depth,
12756                     programStats.nr_moves-programStats.moves_left,
12757                     programStats.nr_moves, ((float)programStats.score)/100.0,
12758                     lst,
12759                     only_one_move(lst)?
12760                     xtra[programStats.got_fail] : "",
12761                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12762           }
12763         } else {
12764             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12765                     programStats.depth,
12766                     ((float)programStats.score)/100.0,
12767                     lst,
12768                     only_one_move(lst)?
12769                     xtra[programStats.got_fail] : "",
12770                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12771         }
12772     }
12773     DisplayAnalysisText(buf);
12774 }
12775
12776 void
12777 DisplayComment(moveNumber, text)
12778      int moveNumber;
12779      char *text;
12780 {
12781     char title[MSG_SIZ];
12782     char buf[8000]; // comment can be long!
12783     int score, depth;
12784
12785     if( appData.autoDisplayComment ) {
12786         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12787             strcpy(title, "Comment");
12788         } else {
12789             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12790                     WhiteOnMove(moveNumber) ? " " : ".. ",
12791                     parseList[moveNumber]);
12792         }
12793         // [HGM] PV info: display PV info together with (or as) comment
12794         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12795             if(text == NULL) text = "";                                           
12796             score = pvInfoList[moveNumber].score;
12797             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12798                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12799             text = buf;
12800         }
12801     } else title[0] = 0;
12802
12803     if (text != NULL)
12804         CommentPopUp(title, text);
12805 }
12806
12807 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12808  * might be busy thinking or pondering.  It can be omitted if your
12809  * gnuchess is configured to stop thinking immediately on any user
12810  * input.  However, that gnuchess feature depends on the FIONREAD
12811  * ioctl, which does not work properly on some flavors of Unix.
12812  */
12813 void
12814 Attention(cps)
12815      ChessProgramState *cps;
12816 {
12817 #if ATTENTION
12818     if (!cps->useSigint) return;
12819     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12820     switch (gameMode) {
12821       case MachinePlaysWhite:
12822       case MachinePlaysBlack:
12823       case TwoMachinesPlay:
12824       case IcsPlayingWhite:
12825       case IcsPlayingBlack:
12826       case AnalyzeMode:
12827       case AnalyzeFile:
12828         /* Skip if we know it isn't thinking */
12829         if (!cps->maybeThinking) return;
12830         if (appData.debugMode)
12831           fprintf(debugFP, "Interrupting %s\n", cps->which);
12832         InterruptChildProcess(cps->pr);
12833         cps->maybeThinking = FALSE;
12834         break;
12835       default:
12836         break;
12837     }
12838 #endif /*ATTENTION*/
12839 }
12840
12841 int
12842 CheckFlags()
12843 {
12844     if (whiteTimeRemaining <= 0) {
12845         if (!whiteFlag) {
12846             whiteFlag = TRUE;
12847             if (appData.icsActive) {
12848                 if (appData.autoCallFlag &&
12849                     gameMode == IcsPlayingBlack && !blackFlag) {
12850                   SendToICS(ics_prefix);
12851                   SendToICS("flag\n");
12852                 }
12853             } else {
12854                 if (blackFlag) {
12855                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12856                 } else {
12857                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12858                     if (appData.autoCallFlag) {
12859                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12860                         return TRUE;
12861                     }
12862                 }
12863             }
12864         }
12865     }
12866     if (blackTimeRemaining <= 0) {
12867         if (!blackFlag) {
12868             blackFlag = TRUE;
12869             if (appData.icsActive) {
12870                 if (appData.autoCallFlag &&
12871                     gameMode == IcsPlayingWhite && !whiteFlag) {
12872                   SendToICS(ics_prefix);
12873                   SendToICS("flag\n");
12874                 }
12875             } else {
12876                 if (whiteFlag) {
12877                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12878                 } else {
12879                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12880                     if (appData.autoCallFlag) {
12881                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12882                         return TRUE;
12883                     }
12884                 }
12885             }
12886         }
12887     }
12888     return FALSE;
12889 }
12890
12891 void
12892 CheckTimeControl()
12893 {
12894     if (!appData.clockMode || appData.icsActive ||
12895         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12896
12897     /*
12898      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12899      */
12900     if ( !WhiteOnMove(forwardMostMove) )
12901         /* White made time control */
12902         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12903         /* [HGM] time odds: correct new time quota for time odds! */
12904                                             / WhitePlayer()->timeOdds;
12905       else
12906         /* Black made time control */
12907         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12908                                             / WhitePlayer()->other->timeOdds;
12909 }
12910
12911 void
12912 DisplayBothClocks()
12913 {
12914     int wom = gameMode == EditPosition ?
12915       !blackPlaysFirst : WhiteOnMove(currentMove);
12916     DisplayWhiteClock(whiteTimeRemaining, wom);
12917     DisplayBlackClock(blackTimeRemaining, !wom);
12918 }
12919
12920
12921 /* Timekeeping seems to be a portability nightmare.  I think everyone
12922    has ftime(), but I'm really not sure, so I'm including some ifdefs
12923    to use other calls if you don't.  Clocks will be less accurate if
12924    you have neither ftime nor gettimeofday.
12925 */
12926
12927 /* VS 2008 requires the #include outside of the function */
12928 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12929 #include <sys/timeb.h>
12930 #endif
12931
12932 /* Get the current time as a TimeMark */
12933 void
12934 GetTimeMark(tm)
12935      TimeMark *tm;
12936 {
12937 #if HAVE_GETTIMEOFDAY
12938
12939     struct timeval timeVal;
12940     struct timezone timeZone;
12941
12942     gettimeofday(&timeVal, &timeZone);
12943     tm->sec = (long) timeVal.tv_sec; 
12944     tm->ms = (int) (timeVal.tv_usec / 1000L);
12945
12946 #else /*!HAVE_GETTIMEOFDAY*/
12947 #if HAVE_FTIME
12948
12949 // include <sys/timeb.h> / moved to just above start of function
12950     struct timeb timeB;
12951
12952     ftime(&timeB);
12953     tm->sec = (long) timeB.time;
12954     tm->ms = (int) timeB.millitm;
12955
12956 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12957     tm->sec = (long) time(NULL);
12958     tm->ms = 0;
12959 #endif
12960 #endif
12961 }
12962
12963 /* Return the difference in milliseconds between two
12964    time marks.  We assume the difference will fit in a long!
12965 */
12966 long
12967 SubtractTimeMarks(tm2, tm1)
12968      TimeMark *tm2, *tm1;
12969 {
12970     return 1000L*(tm2->sec - tm1->sec) +
12971            (long) (tm2->ms - tm1->ms);
12972 }
12973
12974
12975 /*
12976  * Code to manage the game clocks.
12977  *
12978  * In tournament play, black starts the clock and then white makes a move.
12979  * We give the human user a slight advantage if he is playing white---the
12980  * clocks don't run until he makes his first move, so it takes zero time.
12981  * Also, we don't account for network lag, so we could get out of sync
12982  * with GNU Chess's clock -- but then, referees are always right.  
12983  */
12984
12985 static TimeMark tickStartTM;
12986 static long intendedTickLength;
12987
12988 long
12989 NextTickLength(timeRemaining)
12990      long timeRemaining;
12991 {
12992     long nominalTickLength, nextTickLength;
12993
12994     if (timeRemaining > 0L && timeRemaining <= 10000L)
12995       nominalTickLength = 100L;
12996     else
12997       nominalTickLength = 1000L;
12998     nextTickLength = timeRemaining % nominalTickLength;
12999     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13000
13001     return nextTickLength;
13002 }
13003
13004 /* Adjust clock one minute up or down */
13005 void
13006 AdjustClock(Boolean which, int dir)
13007 {
13008     if(which) blackTimeRemaining += 60000*dir;
13009     else      whiteTimeRemaining += 60000*dir;
13010     DisplayBothClocks();
13011 }
13012
13013 /* Stop clocks and reset to a fresh time control */
13014 void
13015 ResetClocks() 
13016 {
13017     (void) StopClockTimer();
13018     if (appData.icsActive) {
13019         whiteTimeRemaining = blackTimeRemaining = 0;
13020     } else { /* [HGM] correct new time quote for time odds */
13021         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13022         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13023     }
13024     if (whiteFlag || blackFlag) {
13025         DisplayTitle("");
13026         whiteFlag = blackFlag = FALSE;
13027     }
13028     DisplayBothClocks();
13029 }
13030
13031 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13032
13033 /* Decrement running clock by amount of time that has passed */
13034 void
13035 DecrementClocks()
13036 {
13037     long timeRemaining;
13038     long lastTickLength, fudge;
13039     TimeMark now;
13040
13041     if (!appData.clockMode) return;
13042     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13043         
13044     GetTimeMark(&now);
13045
13046     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13047
13048     /* Fudge if we woke up a little too soon */
13049     fudge = intendedTickLength - lastTickLength;
13050     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13051
13052     if (WhiteOnMove(forwardMostMove)) {
13053         if(whiteNPS >= 0) lastTickLength = 0;
13054         timeRemaining = whiteTimeRemaining -= lastTickLength;
13055         DisplayWhiteClock(whiteTimeRemaining - fudge,
13056                           WhiteOnMove(currentMove));
13057     } else {
13058         if(blackNPS >= 0) lastTickLength = 0;
13059         timeRemaining = blackTimeRemaining -= lastTickLength;
13060         DisplayBlackClock(blackTimeRemaining - fudge,
13061                           !WhiteOnMove(currentMove));
13062     }
13063
13064     if (CheckFlags()) return;
13065         
13066     tickStartTM = now;
13067     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13068     StartClockTimer(intendedTickLength);
13069
13070     /* if the time remaining has fallen below the alarm threshold, sound the
13071      * alarm. if the alarm has sounded and (due to a takeback or time control
13072      * with increment) the time remaining has increased to a level above the
13073      * threshold, reset the alarm so it can sound again. 
13074      */
13075     
13076     if (appData.icsActive && appData.icsAlarm) {
13077
13078         /* make sure we are dealing with the user's clock */
13079         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13080                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13081            )) return;
13082
13083         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13084             alarmSounded = FALSE;
13085         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13086             PlayAlarmSound();
13087             alarmSounded = TRUE;
13088         }
13089     }
13090 }
13091
13092
13093 /* A player has just moved, so stop the previously running
13094    clock and (if in clock mode) start the other one.
13095    We redisplay both clocks in case we're in ICS mode, because
13096    ICS gives us an update to both clocks after every move.
13097    Note that this routine is called *after* forwardMostMove
13098    is updated, so the last fractional tick must be subtracted
13099    from the color that is *not* on move now.
13100 */
13101 void
13102 SwitchClocks()
13103 {
13104     long lastTickLength;
13105     TimeMark now;
13106     int flagged = FALSE;
13107
13108     GetTimeMark(&now);
13109
13110     if (StopClockTimer() && appData.clockMode) {
13111         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13112         if (WhiteOnMove(forwardMostMove)) {
13113             if(blackNPS >= 0) lastTickLength = 0;
13114             blackTimeRemaining -= lastTickLength;
13115            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13116 //         if(pvInfoList[forwardMostMove-1].time == -1)
13117                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13118                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13119         } else {
13120            if(whiteNPS >= 0) lastTickLength = 0;
13121            whiteTimeRemaining -= lastTickLength;
13122            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13123 //         if(pvInfoList[forwardMostMove-1].time == -1)
13124                  pvInfoList[forwardMostMove-1].time = 
13125                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13126         }
13127         flagged = CheckFlags();
13128     }
13129     CheckTimeControl();
13130
13131     if (flagged || !appData.clockMode) return;
13132
13133     switch (gameMode) {
13134       case MachinePlaysBlack:
13135       case MachinePlaysWhite:
13136       case BeginningOfGame:
13137         if (pausing) return;
13138         break;
13139
13140       case EditGame:
13141       case PlayFromGameFile:
13142       case IcsExamining:
13143         return;
13144
13145       default:
13146         break;
13147     }
13148
13149     tickStartTM = now;
13150     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13151       whiteTimeRemaining : blackTimeRemaining);
13152     StartClockTimer(intendedTickLength);
13153 }
13154         
13155
13156 /* Stop both clocks */
13157 void
13158 StopClocks()
13159 {       
13160     long lastTickLength;
13161     TimeMark now;
13162
13163     if (!StopClockTimer()) return;
13164     if (!appData.clockMode) return;
13165
13166     GetTimeMark(&now);
13167
13168     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13169     if (WhiteOnMove(forwardMostMove)) {
13170         if(whiteNPS >= 0) lastTickLength = 0;
13171         whiteTimeRemaining -= lastTickLength;
13172         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13173     } else {
13174         if(blackNPS >= 0) lastTickLength = 0;
13175         blackTimeRemaining -= lastTickLength;
13176         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13177     }
13178     CheckFlags();
13179 }
13180         
13181 /* Start clock of player on move.  Time may have been reset, so
13182    if clock is already running, stop and restart it. */
13183 void
13184 StartClocks()
13185 {
13186     (void) StopClockTimer(); /* in case it was running already */
13187     DisplayBothClocks();
13188     if (CheckFlags()) return;
13189
13190     if (!appData.clockMode) return;
13191     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13192
13193     GetTimeMark(&tickStartTM);
13194     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13195       whiteTimeRemaining : blackTimeRemaining);
13196
13197    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13198     whiteNPS = blackNPS = -1; 
13199     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13200        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13201         whiteNPS = first.nps;
13202     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13203        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13204         blackNPS = first.nps;
13205     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13206         whiteNPS = second.nps;
13207     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13208         blackNPS = second.nps;
13209     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13210
13211     StartClockTimer(intendedTickLength);
13212 }
13213
13214 char *
13215 TimeString(ms)
13216      long ms;
13217 {
13218     long second, minute, hour, day;
13219     char *sign = "";
13220     static char buf[32];
13221     
13222     if (ms > 0 && ms <= 9900) {
13223       /* convert milliseconds to tenths, rounding up */
13224       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13225
13226       sprintf(buf, " %03.1f ", tenths/10.0);
13227       return buf;
13228     }
13229
13230     /* convert milliseconds to seconds, rounding up */
13231     /* use floating point to avoid strangeness of integer division
13232        with negative dividends on many machines */
13233     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13234
13235     if (second < 0) {
13236         sign = "-";
13237         second = -second;
13238     }
13239     
13240     day = second / (60 * 60 * 24);
13241     second = second % (60 * 60 * 24);
13242     hour = second / (60 * 60);
13243     second = second % (60 * 60);
13244     minute = second / 60;
13245     second = second % 60;
13246     
13247     if (day > 0)
13248       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13249               sign, day, hour, minute, second);
13250     else if (hour > 0)
13251       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13252     else
13253       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13254     
13255     return buf;
13256 }
13257
13258
13259 /*
13260  * This is necessary because some C libraries aren't ANSI C compliant yet.
13261  */
13262 char *
13263 StrStr(string, match)
13264      char *string, *match;
13265 {
13266     int i, length;
13267     
13268     length = strlen(match);
13269     
13270     for (i = strlen(string) - length; i >= 0; i--, string++)
13271       if (!strncmp(match, string, length))
13272         return string;
13273     
13274     return NULL;
13275 }
13276
13277 char *
13278 StrCaseStr(string, match)
13279      char *string, *match;
13280 {
13281     int i, j, length;
13282     
13283     length = strlen(match);
13284     
13285     for (i = strlen(string) - length; i >= 0; i--, string++) {
13286         for (j = 0; j < length; j++) {
13287             if (ToLower(match[j]) != ToLower(string[j]))
13288               break;
13289         }
13290         if (j == length) return string;
13291     }
13292
13293     return NULL;
13294 }
13295
13296 #ifndef _amigados
13297 int
13298 StrCaseCmp(s1, s2)
13299      char *s1, *s2;
13300 {
13301     char c1, c2;
13302     
13303     for (;;) {
13304         c1 = ToLower(*s1++);
13305         c2 = ToLower(*s2++);
13306         if (c1 > c2) return 1;
13307         if (c1 < c2) return -1;
13308         if (c1 == NULLCHAR) return 0;
13309     }
13310 }
13311
13312
13313 int
13314 ToLower(c)
13315      int c;
13316 {
13317     return isupper(c) ? tolower(c) : c;
13318 }
13319
13320
13321 int
13322 ToUpper(c)
13323      int c;
13324 {
13325     return islower(c) ? toupper(c) : c;
13326 }
13327 #endif /* !_amigados    */
13328
13329 char *
13330 StrSave(s)
13331      char *s;
13332 {
13333     char *ret;
13334
13335     if ((ret = (char *) malloc(strlen(s) + 1))) {
13336         strcpy(ret, s);
13337     }
13338     return ret;
13339 }
13340
13341 char *
13342 StrSavePtr(s, savePtr)
13343      char *s, **savePtr;
13344 {
13345     if (*savePtr) {
13346         free(*savePtr);
13347     }
13348     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13349         strcpy(*savePtr, s);
13350     }
13351     return(*savePtr);
13352 }
13353
13354 char *
13355 PGNDate()
13356 {
13357     time_t clock;
13358     struct tm *tm;
13359     char buf[MSG_SIZ];
13360
13361     clock = time((time_t *)NULL);
13362     tm = localtime(&clock);
13363     sprintf(buf, "%04d.%02d.%02d",
13364             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13365     return StrSave(buf);
13366 }
13367
13368
13369 char *
13370 PositionToFEN(move, overrideCastling)
13371      int move;
13372      char *overrideCastling;
13373 {
13374     int i, j, fromX, fromY, toX, toY;
13375     int whiteToPlay;
13376     char buf[128];
13377     char *p, *q;
13378     int emptycount;
13379     ChessSquare piece;
13380
13381     whiteToPlay = (gameMode == EditPosition) ?
13382       !blackPlaysFirst : (move % 2 == 0);
13383     p = buf;
13384
13385     /* Piece placement data */
13386     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13387         emptycount = 0;
13388         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13389             if (boards[move][i][j] == EmptySquare) {
13390                 emptycount++;
13391             } else { ChessSquare piece = boards[move][i][j];
13392                 if (emptycount > 0) {
13393                     if(emptycount<10) /* [HGM] can be >= 10 */
13394                         *p++ = '0' + emptycount;
13395                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13396                     emptycount = 0;
13397                 }
13398                 if(PieceToChar(piece) == '+') {
13399                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13400                     *p++ = '+';
13401                     piece = (ChessSquare)(DEMOTED piece);
13402                 } 
13403                 *p++ = PieceToChar(piece);
13404                 if(p[-1] == '~') {
13405                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13406                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13407                     *p++ = '~';
13408                 }
13409             }
13410         }
13411         if (emptycount > 0) {
13412             if(emptycount<10) /* [HGM] can be >= 10 */
13413                 *p++ = '0' + emptycount;
13414             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13415             emptycount = 0;
13416         }
13417         *p++ = '/';
13418     }
13419     *(p - 1) = ' ';
13420
13421     /* [HGM] print Crazyhouse or Shogi holdings */
13422     if( gameInfo.holdingsWidth ) {
13423         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13424         q = p;
13425         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13426             piece = boards[move][i][BOARD_WIDTH-1];
13427             if( piece != EmptySquare )
13428               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13429                   *p++ = PieceToChar(piece);
13430         }
13431         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13432             piece = boards[move][BOARD_HEIGHT-i-1][0];
13433             if( piece != EmptySquare )
13434               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13435                   *p++ = PieceToChar(piece);
13436         }
13437
13438         if( q == p ) *p++ = '-';
13439         *p++ = ']';
13440         *p++ = ' ';
13441     }
13442
13443     /* Active color */
13444     *p++ = whiteToPlay ? 'w' : 'b';
13445     *p++ = ' ';
13446
13447   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13448     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13449   } else {
13450   if(nrCastlingRights) {
13451      q = p;
13452      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13453        /* [HGM] write directly from rights */
13454            if(castlingRights[move][2] >= 0 &&
13455               castlingRights[move][0] >= 0   )
13456                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13457            if(castlingRights[move][2] >= 0 &&
13458               castlingRights[move][1] >= 0   )
13459                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13460            if(castlingRights[move][5] >= 0 &&
13461               castlingRights[move][3] >= 0   )
13462                 *p++ = castlingRights[move][3] + AAA;
13463            if(castlingRights[move][5] >= 0 &&
13464               castlingRights[move][4] >= 0   )
13465                 *p++ = castlingRights[move][4] + AAA;
13466      } else {
13467
13468         /* [HGM] write true castling rights */
13469         if( nrCastlingRights == 6 ) {
13470             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13471                castlingRights[move][2] >= 0  ) *p++ = 'K';
13472             if(castlingRights[move][1] == BOARD_LEFT &&
13473                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13474             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13475                castlingRights[move][5] >= 0  ) *p++ = 'k';
13476             if(castlingRights[move][4] == BOARD_LEFT &&
13477                castlingRights[move][5] >= 0  ) *p++ = 'q';
13478         }
13479      }
13480      if (q == p) *p++ = '-'; /* No castling rights */
13481      *p++ = ' ';
13482   }
13483
13484   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13485      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13486     /* En passant target square */
13487     if (move > backwardMostMove) {
13488         fromX = moveList[move - 1][0] - AAA;
13489         fromY = moveList[move - 1][1] - ONE;
13490         toX = moveList[move - 1][2] - AAA;
13491         toY = moveList[move - 1][3] - ONE;
13492         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13493             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13494             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13495             fromX == toX) {
13496             /* 2-square pawn move just happened */
13497             *p++ = toX + AAA;
13498             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13499         } else {
13500             *p++ = '-';
13501         }
13502     } else if(move == backwardMostMove) {
13503         // [HGM] perhaps we should always do it like this, and forget the above?
13504         if(epStatus[move] >= 0) {
13505             *p++ = epStatus[move] + AAA;
13506             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13507         } else {
13508             *p++ = '-';
13509         }
13510     } else {
13511         *p++ = '-';
13512     }
13513     *p++ = ' ';
13514   }
13515   }
13516
13517     /* [HGM] find reversible plies */
13518     {   int i = 0, j=move;
13519
13520         if (appData.debugMode) { int k;
13521             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13522             for(k=backwardMostMove; k<=forwardMostMove; k++)
13523                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13524
13525         }
13526
13527         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13528         if( j == backwardMostMove ) i += initialRulePlies;
13529         sprintf(p, "%d ", i);
13530         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13531     }
13532     /* Fullmove number */
13533     sprintf(p, "%d", (move / 2) + 1);
13534     
13535     return StrSave(buf);
13536 }
13537
13538 Boolean
13539 ParseFEN(board, blackPlaysFirst, fen)
13540     Board board;
13541      int *blackPlaysFirst;
13542      char *fen;
13543 {
13544     int i, j;
13545     char *p;
13546     int emptycount;
13547     ChessSquare piece;
13548
13549     p = fen;
13550
13551     /* [HGM] by default clear Crazyhouse holdings, if present */
13552     if(gameInfo.holdingsWidth) {
13553        for(i=0; i<BOARD_HEIGHT; i++) {
13554            board[i][0]             = EmptySquare; /* black holdings */
13555            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13556            board[i][1]             = (ChessSquare) 0; /* black counts */
13557            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13558        }
13559     }
13560
13561     /* Piece placement data */
13562     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13563         j = 0;
13564         for (;;) {
13565             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13566                 if (*p == '/') p++;
13567                 emptycount = gameInfo.boardWidth - j;
13568                 while (emptycount--)
13569                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13570                 break;
13571 #if(BOARD_SIZE >= 10)
13572             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13573                 p++; emptycount=10;
13574                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13575                 while (emptycount--)
13576                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13577 #endif
13578             } else if (isdigit(*p)) {
13579                 emptycount = *p++ - '0';
13580                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13581                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13582                 while (emptycount--)
13583                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13584             } else if (*p == '+' || isalpha(*p)) {
13585                 if (j >= gameInfo.boardWidth) return FALSE;
13586                 if(*p=='+') {
13587                     piece = CharToPiece(*++p);
13588                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13589                     piece = (ChessSquare) (PROMOTED piece ); p++;
13590                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13591                 } else piece = CharToPiece(*p++);
13592
13593                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13594                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13595                     piece = (ChessSquare) (PROMOTED piece);
13596                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13597                     p++;
13598                 }
13599                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13600             } else {
13601                 return FALSE;
13602             }
13603         }
13604     }
13605     while (*p == '/' || *p == ' ') p++;
13606
13607     /* [HGM] look for Crazyhouse holdings here */
13608     while(*p==' ') p++;
13609     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13610         if(*p == '[') p++;
13611         if(*p == '-' ) *p++; /* empty holdings */ else {
13612             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13613             /* if we would allow FEN reading to set board size, we would   */
13614             /* have to add holdings and shift the board read so far here   */
13615             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13616                 *p++;
13617                 if((int) piece >= (int) BlackPawn ) {
13618                     i = (int)piece - (int)BlackPawn;
13619                     i = PieceToNumber((ChessSquare)i);
13620                     if( i >= gameInfo.holdingsSize ) return FALSE;
13621                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13622                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13623                 } else {
13624                     i = (int)piece - (int)WhitePawn;
13625                     i = PieceToNumber((ChessSquare)i);
13626                     if( i >= gameInfo.holdingsSize ) return FALSE;
13627                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13628                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13629                 }
13630             }
13631         }
13632         if(*p == ']') *p++;
13633     }
13634
13635     while(*p == ' ') p++;
13636
13637     /* Active color */
13638     switch (*p++) {
13639       case 'w':
13640         *blackPlaysFirst = FALSE;
13641         break;
13642       case 'b': 
13643         *blackPlaysFirst = TRUE;
13644         break;
13645       default:
13646         return FALSE;
13647     }
13648
13649     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13650     /* return the extra info in global variiables             */
13651
13652     /* set defaults in case FEN is incomplete */
13653     FENepStatus = EP_UNKNOWN;
13654     for(i=0; i<nrCastlingRights; i++ ) {
13655         FENcastlingRights[i] =
13656             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13657     }   /* assume possible unless obviously impossible */
13658     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13659     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13660     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13661     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13662     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13663     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13664     FENrulePlies = 0;
13665
13666     while(*p==' ') p++;
13667     if(nrCastlingRights) {
13668       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13669           /* castling indicator present, so default becomes no castlings */
13670           for(i=0; i<nrCastlingRights; i++ ) {
13671                  FENcastlingRights[i] = -1;
13672           }
13673       }
13674       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13675              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13676              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13677              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13678         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13679
13680         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13681             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13682             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13683         }
13684         switch(c) {
13685           case'K':
13686               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13687               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13688               FENcastlingRights[2] = whiteKingFile;
13689               break;
13690           case'Q':
13691               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13692               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13693               FENcastlingRights[2] = whiteKingFile;
13694               break;
13695           case'k':
13696               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13697               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13698               FENcastlingRights[5] = blackKingFile;
13699               break;
13700           case'q':
13701               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13702               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13703               FENcastlingRights[5] = blackKingFile;
13704           case '-':
13705               break;
13706           default: /* FRC castlings */
13707               if(c >= 'a') { /* black rights */
13708                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13709                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13710                   if(i == BOARD_RGHT) break;
13711                   FENcastlingRights[5] = i;
13712                   c -= AAA;
13713                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13714                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13715                   if(c > i)
13716                       FENcastlingRights[3] = c;
13717                   else
13718                       FENcastlingRights[4] = c;
13719               } else { /* white rights */
13720                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13721                     if(board[0][i] == WhiteKing) break;
13722                   if(i == BOARD_RGHT) break;
13723                   FENcastlingRights[2] = i;
13724                   c -= AAA - 'a' + 'A';
13725                   if(board[0][c] >= WhiteKing) break;
13726                   if(c > i)
13727                       FENcastlingRights[0] = c;
13728                   else
13729                       FENcastlingRights[1] = c;
13730               }
13731         }
13732       }
13733     if (appData.debugMode) {
13734         fprintf(debugFP, "FEN castling rights:");
13735         for(i=0; i<nrCastlingRights; i++)
13736         fprintf(debugFP, " %d", FENcastlingRights[i]);
13737         fprintf(debugFP, "\n");
13738     }
13739
13740       while(*p==' ') p++;
13741     }
13742
13743     /* read e.p. field in games that know e.p. capture */
13744     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13745        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13746       if(*p=='-') {
13747         p++; FENepStatus = EP_NONE;
13748       } else {
13749          char c = *p++ - AAA;
13750
13751          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13752          if(*p >= '0' && *p <='9') *p++;
13753          FENepStatus = c;
13754       }
13755     }
13756
13757
13758     if(sscanf(p, "%d", &i) == 1) {
13759         FENrulePlies = i; /* 50-move ply counter */
13760         /* (The move number is still ignored)    */
13761     }
13762
13763     return TRUE;
13764 }
13765       
13766 void
13767 EditPositionPasteFEN(char *fen)
13768 {
13769   if (fen != NULL) {
13770     Board initial_position;
13771
13772     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13773       DisplayError(_("Bad FEN position in clipboard"), 0);
13774       return ;
13775     } else {
13776       int savedBlackPlaysFirst = blackPlaysFirst;
13777       EditPositionEvent();
13778       blackPlaysFirst = savedBlackPlaysFirst;
13779       CopyBoard(boards[0], initial_position);
13780           /* [HGM] copy FEN attributes as well */
13781           {   int i;
13782               initialRulePlies = FENrulePlies;
13783               epStatus[0] = FENepStatus;
13784               for( i=0; i<nrCastlingRights; i++ )
13785                   castlingRights[0][i] = FENcastlingRights[i];
13786           }
13787       EditPositionDone();
13788       DisplayBothClocks();
13789       DrawPosition(FALSE, boards[currentMove]);
13790     }
13791   }
13792 }