Adjustment to joining to work around server not always including space.
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                   Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((void));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom: 
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /* 
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  epStatus[MAX_MOVES];
445 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int   initialRulePlies, FENrulePlies;
450 char  FENepStatus;
451 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
452 int loadFlag = 0; 
453 int shuffleOpenings;
454 int mute; // mute all sounds
455
456 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
457     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460         BlackKing, BlackBishop, BlackKnight, BlackRook }
461 };
462
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467         BlackKing, BlackKing, BlackKnight, BlackRook }
468 };
469
470 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473     { BlackRook, BlackMan, BlackBishop, BlackQueen,
474         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
475 };
476
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481         BlackKing, BlackBishop, BlackKnight, BlackRook }
482 };
483
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
489 };
490
491
492 #if (BOARD_SIZE>=10)
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
498 };
499
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
505 };
506
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
509         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
511         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
512 };
513
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
516         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
518         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
519 };
520
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
523         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
525         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
526 };
527
528 #ifdef GOTHIC
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
531         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
533         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
534 };
535 #else // !GOTHIC
536 #define GothicArray CapablancaArray
537 #endif // !GOTHIC
538
539 #ifdef FALCON
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
542         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
544         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !FALCON
547 #define FalconArray CapablancaArray
548 #endif // !FALCON
549
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
556
557 #if (BOARD_SIZE>=12)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
563 };
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
567
568
569 Board initialPosition;
570
571
572 /* Convert str to a rating. Checks for special cases of "----",
573
574    "++++", etc. Also strips ()'s */
575 int
576 string_to_rating(str)
577   char *str;
578 {
579   while(*str && !isdigit(*str)) ++str;
580   if (!*str)
581     return 0;   /* One of the special "no rating" cases */
582   else
583     return atoi(str);
584 }
585
586 void
587 ClearProgramStats()
588 {
589     /* Init programStats */
590     programStats.movelist[0] = 0;
591     programStats.depth = 0;
592     programStats.nr_moves = 0;
593     programStats.moves_left = 0;
594     programStats.nodes = 0;
595     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
596     programStats.score = 0;
597     programStats.got_only_move = 0;
598     programStats.got_fail = 0;
599     programStats.line_is_book = 0;
600 }
601
602 void
603 InitBackEnd1()
604 {
605     int matched, min, sec;
606
607     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
608
609     GetTimeMark(&programStartTime);
610     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
611
612     ClearProgramStats();
613     programStats.ok_to_send = 1;
614     programStats.seen_stat = 0;
615
616     /*
617      * Initialize game list
618      */
619     ListNew(&gameList);
620
621
622     /*
623      * Internet chess server status
624      */
625     if (appData.icsActive) {
626         appData.matchMode = FALSE;
627         appData.matchGames = 0;
628 #if ZIPPY       
629         appData.noChessProgram = !appData.zippyPlay;
630 #else
631         appData.zippyPlay = FALSE;
632         appData.zippyTalk = FALSE;
633         appData.noChessProgram = TRUE;
634 #endif
635         if (*appData.icsHelper != NULLCHAR) {
636             appData.useTelnet = TRUE;
637             appData.telnetProgram = appData.icsHelper;
638         }
639     } else {
640         appData.zippyTalk = appData.zippyPlay = FALSE;
641     }
642
643     /* [AS] Initialize pv info list [HGM] and game state */
644     {
645         int i, j;
646
647         for( i=0; i<MAX_MOVES; i++ ) {
648             pvInfoList[i].depth = -1;
649             epStatus[i]=EP_NONE;
650             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
651         }
652     }
653
654     /*
655      * Parse timeControl resource
656      */
657     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658                           appData.movesPerSession)) {
659         char buf[MSG_SIZ];
660         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661         DisplayFatalError(buf, 0, 2);
662     }
663
664     /*
665      * Parse searchTime resource
666      */
667     if (*appData.searchTime != NULLCHAR) {
668         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
669         if (matched == 1) {
670             searchTime = min * 60;
671         } else if (matched == 2) {
672             searchTime = min * 60 + sec;
673         } else {
674             char buf[MSG_SIZ];
675             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676             DisplayFatalError(buf, 0, 2);
677         }
678     }
679
680     /* [AS] Adjudication threshold */
681     adjudicateLossThreshold = appData.adjudicateLossThreshold;
682     
683     first.which = "first";
684     second.which = "second";
685     first.maybeThinking = second.maybeThinking = FALSE;
686     first.pr = second.pr = NoProc;
687     first.isr = second.isr = NULL;
688     first.sendTime = second.sendTime = 2;
689     first.sendDrawOffers = 1;
690     if (appData.firstPlaysBlack) {
691         first.twoMachinesColor = "black\n";
692         second.twoMachinesColor = "white\n";
693     } else {
694         first.twoMachinesColor = "white\n";
695         second.twoMachinesColor = "black\n";
696     }
697     first.program = appData.firstChessProgram;
698     second.program = appData.secondChessProgram;
699     first.host = appData.firstHost;
700     second.host = appData.secondHost;
701     first.dir = appData.firstDirectory;
702     second.dir = appData.secondDirectory;
703     first.other = &second;
704     second.other = &first;
705     first.initString = appData.initString;
706     second.initString = appData.secondInitString;
707     first.computerString = appData.firstComputerString;
708     second.computerString = appData.secondComputerString;
709     first.useSigint = second.useSigint = TRUE;
710     first.useSigterm = second.useSigterm = TRUE;
711     first.reuse = appData.reuseFirst;
712     second.reuse = appData.reuseSecond;
713     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
714     second.nps = appData.secondNPS;
715     first.useSetboard = second.useSetboard = FALSE;
716     first.useSAN = second.useSAN = FALSE;
717     first.usePing = second.usePing = FALSE;
718     first.lastPing = second.lastPing = 0;
719     first.lastPong = second.lastPong = 0;
720     first.usePlayother = second.usePlayother = FALSE;
721     first.useColors = second.useColors = TRUE;
722     first.useUsermove = second.useUsermove = FALSE;
723     first.sendICS = second.sendICS = FALSE;
724     first.sendName = second.sendName = appData.icsActive;
725     first.sdKludge = second.sdKludge = FALSE;
726     first.stKludge = second.stKludge = FALSE;
727     TidyProgramName(first.program, first.host, first.tidy);
728     TidyProgramName(second.program, second.host, second.tidy);
729     first.matchWins = second.matchWins = 0;
730     strcpy(first.variants, appData.variant);
731     strcpy(second.variants, appData.variant);
732     first.analysisSupport = second.analysisSupport = 2; /* detect */
733     first.analyzing = second.analyzing = FALSE;
734     first.initDone = second.initDone = FALSE;
735
736     /* New features added by Tord: */
737     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739     /* End of new features added by Tord. */
740     first.fenOverride  = appData.fenOverride1;
741     second.fenOverride = appData.fenOverride2;
742
743     /* [HGM] time odds: set factor for each machine */
744     first.timeOdds  = appData.firstTimeOdds;
745     second.timeOdds = appData.secondTimeOdds;
746     { int norm = 1;
747         if(appData.timeOddsMode) {
748             norm = first.timeOdds;
749             if(norm > second.timeOdds) norm = second.timeOdds;
750         }
751         first.timeOdds /= norm;
752         second.timeOdds /= norm;
753     }
754
755     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756     first.accumulateTC = appData.firstAccumulateTC;
757     second.accumulateTC = appData.secondAccumulateTC;
758     first.maxNrOfSessions = second.maxNrOfSessions = 1;
759
760     /* [HGM] debug */
761     first.debug = second.debug = FALSE;
762     first.supportsNPS = second.supportsNPS = UNKNOWN;
763
764     /* [HGM] options */
765     first.optionSettings  = appData.firstOptions;
766     second.optionSettings = appData.secondOptions;
767
768     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770     first.isUCI = appData.firstIsUCI; /* [AS] */
771     second.isUCI = appData.secondIsUCI; /* [AS] */
772     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
774
775     if (appData.firstProtocolVersion > PROTOVER ||
776         appData.firstProtocolVersion < 1) {
777       char buf[MSG_SIZ];
778       sprintf(buf, _("protocol version %d not supported"),
779               appData.firstProtocolVersion);
780       DisplayFatalError(buf, 0, 2);
781     } else {
782       first.protocolVersion = appData.firstProtocolVersion;
783     }
784
785     if (appData.secondProtocolVersion > PROTOVER ||
786         appData.secondProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.secondProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       second.protocolVersion = appData.secondProtocolVersion;
793     }
794
795     if (appData.icsActive) {
796         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
797     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798         appData.clockMode = FALSE;
799         first.sendTime = second.sendTime = 0;
800     }
801     
802 #if ZIPPY
803     /* Override some settings from environment variables, for backward
804        compatibility.  Unfortunately it's not feasible to have the env
805        vars just set defaults, at least in xboard.  Ugh.
806     */
807     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
808       ZippyInit();
809     }
810 #endif
811     
812     if (appData.noChessProgram) {
813         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814         sprintf(programVersion, "%s", PACKAGE_STRING);
815     } else {
816       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
819     }
820
821     if (!appData.icsActive) {
822       char buf[MSG_SIZ];
823       /* Check for variants that are supported only in ICS mode,
824          or not at all.  Some that are accepted here nevertheless
825          have bugs; see comments below.
826       */
827       VariantClass variant = StringToVariant(appData.variant);
828       switch (variant) {
829       case VariantBughouse:     /* need four players and two boards */
830       case VariantKriegspiel:   /* need to hide pieces and move details */
831       /* case VariantFischeRandom: (Fabien: moved below) */
832         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833         DisplayFatalError(buf, 0, 2);
834         return;
835
836       case VariantUnknown:
837       case VariantLoadable:
838       case Variant29:
839       case Variant30:
840       case Variant31:
841       case Variant32:
842       case Variant33:
843       case Variant34:
844       case Variant35:
845       case Variant36:
846       default:
847         sprintf(buf, _("Unknown variant name %s"), appData.variant);
848         DisplayFatalError(buf, 0, 2);
849         return;
850
851       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
852       case VariantFairy:      /* [HGM] TestLegality definitely off! */
853       case VariantGothic:     /* [HGM] should work */
854       case VariantCapablanca: /* [HGM] should work */
855       case VariantCourier:    /* [HGM] initial forced moves not implemented */
856       case VariantShogi:      /* [HGM] drops not tested for legality */
857       case VariantKnightmate: /* [HGM] should work */
858       case VariantCylinder:   /* [HGM] untested */
859       case VariantFalcon:     /* [HGM] untested */
860       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861                                  offboard interposition not understood */
862       case VariantNormal:     /* definitely works! */
863       case VariantWildCastle: /* pieces not automatically shuffled */
864       case VariantNoCastle:   /* pieces not automatically shuffled */
865       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866       case VariantLosers:     /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantSuicide:    /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantGiveaway:   /* should work except for win condition,
871                                  and doesn't know captures are mandatory */
872       case VariantTwoKings:   /* should work */
873       case VariantAtomic:     /* should work except for win condition */
874       case Variant3Check:     /* should work except for win condition */
875       case VariantShatranj:   /* should work except for all win conditions */
876       case VariantBerolina:   /* might work if TestLegality is off */
877       case VariantCapaRandom: /* should work */
878       case VariantJanus:      /* should work */
879       case VariantSuper:      /* experimental */
880       case VariantGreat:      /* experimental, requires legality testing to be off */
881         break;
882       }
883     }
884
885     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
886     InitEngineUCI( installDir, &second );
887 }
888
889 int NextIntegerFromString( char ** str, long * value )
890 {
891     int result = -1;
892     char * s = *str;
893
894     while( *s == ' ' || *s == '\t' ) {
895         s++;
896     }
897
898     *value = 0;
899
900     if( *s >= '0' && *s <= '9' ) {
901         while( *s >= '0' && *s <= '9' ) {
902             *value = *value * 10 + (*s - '0');
903             s++;
904         }
905
906         result = 0;
907     }
908
909     *str = s;
910
911     return result;
912 }
913
914 int NextTimeControlFromString( char ** str, long * value )
915 {
916     long temp;
917     int result = NextIntegerFromString( str, &temp );
918
919     if( result == 0 ) {
920         *value = temp * 60; /* Minutes */
921         if( **str == ':' ) {
922             (*str)++;
923             result = NextIntegerFromString( str, &temp );
924             *value += temp; /* Seconds */
925         }
926     }
927
928     return result;
929 }
930
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
933     int result = -1; long temp, temp2;
934
935     if(**str != '+') return -1; // old params remain in force!
936     (*str)++;
937     if( NextTimeControlFromString( str, &temp ) ) return -1;
938
939     if(**str != '/') {
940         /* time only: incremental or sudden-death time control */
941         if(**str == '+') { /* increment follows; read it */
942             (*str)++;
943             if(result = NextIntegerFromString( str, &temp2)) return -1;
944             *inc = temp2 * 1000;
945         } else *inc = 0;
946         *moves = 0; *tc = temp * 1000; 
947         return 0;
948     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
949
950     (*str)++; /* classical time control */
951     result = NextTimeControlFromString( str, &temp2);
952     if(result == 0) {
953         *moves = temp/60;
954         *tc    = temp2 * 1000;
955         *inc   = 0;
956     }
957     return result;
958 }
959
960 int GetTimeQuota(int movenr)
961 {   /* [HGM] get time to add from the multi-session time-control string */
962     int moves=1; /* kludge to force reading of first session */
963     long time, increment;
964     char *s = fullTimeControlString;
965
966     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
967     do {
968         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970         if(movenr == -1) return time;    /* last move before new session     */
971         if(!moves) return increment;     /* current session is incremental   */
972         if(movenr >= 0) movenr -= moves; /* we already finished this session */
973     } while(movenr >= -1);               /* try again for next session       */
974
975     return 0; // no new time quota on this move
976 }
977
978 int
979 ParseTimeControl(tc, ti, mps)
980      char *tc;
981      int ti;
982      int mps;
983 {
984   long tc1;
985   long tc2;
986   char buf[MSG_SIZ];
987   
988   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
989   if(ti > 0) {
990     if(mps)
991       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992     else sprintf(buf, "+%s+%d", tc, ti);
993   } else {
994     if(mps)
995              sprintf(buf, "+%d/%s", mps, tc);
996     else sprintf(buf, "+%s", tc);
997   }
998   fullTimeControlString = StrSave(buf);
999   
1000   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1001     return FALSE;
1002   }
1003   
1004   if( *tc == '/' ) {
1005     /* Parse second time control */
1006     tc++;
1007     
1008     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1009       return FALSE;
1010     }
1011     
1012     if( tc2 == 0 ) {
1013       return FALSE;
1014     }
1015     
1016     timeControl_2 = tc2 * 1000;
1017   }
1018   else {
1019     timeControl_2 = 0;
1020   }
1021   
1022   if( tc1 == 0 ) {
1023     return FALSE;
1024   }
1025   
1026   timeControl = tc1 * 1000;
1027   
1028   if (ti >= 0) {
1029     timeIncrement = ti * 1000;  /* convert to ms */
1030     movesPerSession = 0;
1031   } else {
1032     timeIncrement = 0;
1033     movesPerSession = mps;
1034   }
1035   return TRUE;
1036 }
1037
1038 void
1039 InitBackEnd2()
1040 {
1041     if (appData.debugMode) {
1042         fprintf(debugFP, "%s\n", programVersion);
1043     }
1044
1045     if (appData.matchGames > 0) {
1046         appData.matchMode = TRUE;
1047     } else if (appData.matchMode) {
1048         appData.matchGames = 1;
1049     }
1050     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1051         appData.matchGames = appData.sameColorGames;
1052     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1053         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1054         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1055     }
1056     Reset(TRUE, FALSE);
1057     if (appData.noChessProgram || first.protocolVersion == 1) {
1058       InitBackEnd3();
1059     } else {
1060       /* kludge: allow timeout for initial "feature" commands */
1061       FreezeUI();
1062       DisplayMessage("", _("Starting chess program"));
1063       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1064     }
1065 }
1066
1067 void
1068 InitBackEnd3 P((void))
1069 {
1070     GameMode initialMode;
1071     char buf[MSG_SIZ];
1072     int err;
1073
1074     InitChessProgram(&first, startedFromSetupPosition);
1075
1076
1077     if (appData.icsActive) {
1078 #ifdef WIN32
1079         /* [DM] Make a console window if needed [HGM] merged ifs */
1080         ConsoleCreate(); 
1081 #endif
1082         err = establish();
1083         if (err != 0) {
1084             if (*appData.icsCommPort != NULLCHAR) {
1085                 sprintf(buf, _("Could not open comm port %s"),  
1086                         appData.icsCommPort);
1087             } else {
1088                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1089                         appData.icsHost, appData.icsPort);
1090             }
1091             DisplayFatalError(buf, err, 1);
1092             return;
1093         }
1094         SetICSMode();
1095         telnetISR =
1096           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1097         fromUserISR =
1098           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1099     } else if (appData.noChessProgram) {
1100         SetNCPMode();
1101     } else {
1102         SetGNUMode();
1103     }
1104
1105     if (*appData.cmailGameName != NULLCHAR) {
1106         SetCmailMode();
1107         OpenLoopback(&cmailPR);
1108         cmailISR =
1109           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1110     }
1111     
1112     ThawUI();
1113     DisplayMessage("", "");
1114     if (StrCaseCmp(appData.initialMode, "") == 0) {
1115       initialMode = BeginningOfGame;
1116     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1117       initialMode = TwoMachinesPlay;
1118     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1119       initialMode = AnalyzeFile; 
1120     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1121       initialMode = AnalyzeMode;
1122     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1123       initialMode = MachinePlaysWhite;
1124     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1125       initialMode = MachinePlaysBlack;
1126     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1127       initialMode = EditGame;
1128     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1129       initialMode = EditPosition;
1130     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1131       initialMode = Training;
1132     } else {
1133       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1134       DisplayFatalError(buf, 0, 2);
1135       return;
1136     }
1137
1138     if (appData.matchMode) {
1139         /* Set up machine vs. machine match */
1140         if (appData.noChessProgram) {
1141             DisplayFatalError(_("Can't have a match with no chess programs"),
1142                               0, 2);
1143             return;
1144         }
1145         matchMode = TRUE;
1146         matchGame = 1;
1147         if (*appData.loadGameFile != NULLCHAR) {
1148             int index = appData.loadGameIndex; // [HGM] autoinc
1149             if(index<0) lastIndex = index = 1;
1150             if (!LoadGameFromFile(appData.loadGameFile,
1151                                   index,
1152                                   appData.loadGameFile, FALSE)) {
1153                 DisplayFatalError(_("Bad game file"), 0, 1);
1154                 return;
1155             }
1156         } else if (*appData.loadPositionFile != NULLCHAR) {
1157             int index = appData.loadPositionIndex; // [HGM] autoinc
1158             if(index<0) lastIndex = index = 1;
1159             if (!LoadPositionFromFile(appData.loadPositionFile,
1160                                       index,
1161                                       appData.loadPositionFile)) {
1162                 DisplayFatalError(_("Bad position file"), 0, 1);
1163                 return;
1164             }
1165         }
1166         TwoMachinesEvent();
1167     } else if (*appData.cmailGameName != NULLCHAR) {
1168         /* Set up cmail mode */
1169         ReloadCmailMsgEvent(TRUE);
1170     } else {
1171         /* Set up other modes */
1172         if (initialMode == AnalyzeFile) {
1173           if (*appData.loadGameFile == NULLCHAR) {
1174             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1175             return;
1176           }
1177         }
1178         if (*appData.loadGameFile != NULLCHAR) {
1179             (void) LoadGameFromFile(appData.loadGameFile,
1180                                     appData.loadGameIndex,
1181                                     appData.loadGameFile, TRUE);
1182         } else if (*appData.loadPositionFile != NULLCHAR) {
1183             (void) LoadPositionFromFile(appData.loadPositionFile,
1184                                         appData.loadPositionIndex,
1185                                         appData.loadPositionFile);
1186             /* [HGM] try to make self-starting even after FEN load */
1187             /* to allow automatic setup of fairy variants with wtm */
1188             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1189                 gameMode = BeginningOfGame;
1190                 setboardSpoiledMachineBlack = 1;
1191             }
1192             /* [HGM] loadPos: make that every new game uses the setup */
1193             /* from file as long as we do not switch variant          */
1194             if(!blackPlaysFirst) { int i;
1195                 startedFromPositionFile = TRUE;
1196                 CopyBoard(filePosition, boards[0]);
1197                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1198             }
1199         }
1200         if (initialMode == AnalyzeMode) {
1201           if (appData.noChessProgram) {
1202             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1203             return;
1204           }
1205           if (appData.icsActive) {
1206             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1207             return;
1208           }
1209           AnalyzeModeEvent();
1210         } else if (initialMode == AnalyzeFile) {
1211           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1212           ShowThinkingEvent();
1213           AnalyzeFileEvent();
1214           AnalysisPeriodicEvent(1);
1215         } else if (initialMode == MachinePlaysWhite) {
1216           if (appData.noChessProgram) {
1217             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1218                               0, 2);
1219             return;
1220           }
1221           if (appData.icsActive) {
1222             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1223                               0, 2);
1224             return;
1225           }
1226           MachineWhiteEvent();
1227         } else if (initialMode == MachinePlaysBlack) {
1228           if (appData.noChessProgram) {
1229             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1230                               0, 2);
1231             return;
1232           }
1233           if (appData.icsActive) {
1234             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1235                               0, 2);
1236             return;
1237           }
1238           MachineBlackEvent();
1239         } else if (initialMode == TwoMachinesPlay) {
1240           if (appData.noChessProgram) {
1241             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1242                               0, 2);
1243             return;
1244           }
1245           if (appData.icsActive) {
1246             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1247                               0, 2);
1248             return;
1249           }
1250           TwoMachinesEvent();
1251         } else if (initialMode == EditGame) {
1252           EditGameEvent();
1253         } else if (initialMode == EditPosition) {
1254           EditPositionEvent();
1255         } else if (initialMode == Training) {
1256           if (*appData.loadGameFile == NULLCHAR) {
1257             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1258             return;
1259           }
1260           TrainingEvent();
1261         }
1262     }
1263 }
1264
1265 /*
1266  * Establish will establish a contact to a remote host.port.
1267  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1268  *  used to talk to the host.
1269  * Returns 0 if okay, error code if not.
1270  */
1271 int
1272 establish()
1273 {
1274     char buf[MSG_SIZ];
1275
1276     if (*appData.icsCommPort != NULLCHAR) {
1277         /* Talk to the host through a serial comm port */
1278         return OpenCommPort(appData.icsCommPort, &icsPR);
1279
1280     } else if (*appData.gateway != NULLCHAR) {
1281         if (*appData.remoteShell == NULLCHAR) {
1282             /* Use the rcmd protocol to run telnet program on a gateway host */
1283             snprintf(buf, sizeof(buf), "%s %s %s",
1284                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1285             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1286
1287         } else {
1288             /* Use the rsh program to run telnet program on a gateway host */
1289             if (*appData.remoteUser == NULLCHAR) {
1290                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1291                         appData.gateway, appData.telnetProgram,
1292                         appData.icsHost, appData.icsPort);
1293             } else {
1294                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1295                         appData.remoteShell, appData.gateway, 
1296                         appData.remoteUser, appData.telnetProgram,
1297                         appData.icsHost, appData.icsPort);
1298             }
1299             return StartChildProcess(buf, "", &icsPR);
1300
1301         }
1302     } else if (appData.useTelnet) {
1303         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1304
1305     } else {
1306         /* TCP socket interface differs somewhat between
1307            Unix and NT; handle details in the front end.
1308            */
1309         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1310     }
1311 }
1312
1313 void
1314 show_bytes(fp, buf, count)
1315      FILE *fp;
1316      char *buf;
1317      int count;
1318 {
1319     while (count--) {
1320         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1321             fprintf(fp, "\\%03o", *buf & 0xff);
1322         } else {
1323             putc(*buf, fp);
1324         }
1325         buf++;
1326     }
1327     fflush(fp);
1328 }
1329
1330 /* Returns an errno value */
1331 int
1332 OutputMaybeTelnet(pr, message, count, outError)
1333      ProcRef pr;
1334      char *message;
1335      int count;
1336      int *outError;
1337 {
1338     char buf[8192], *p, *q, *buflim;
1339     int left, newcount, outcount;
1340
1341     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1342         *appData.gateway != NULLCHAR) {
1343         if (appData.debugMode) {
1344             fprintf(debugFP, ">ICS: ");
1345             show_bytes(debugFP, message, count);
1346             fprintf(debugFP, "\n");
1347         }
1348         return OutputToProcess(pr, message, count, outError);
1349     }
1350
1351     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1352     p = message;
1353     q = buf;
1354     left = count;
1355     newcount = 0;
1356     while (left) {
1357         if (q >= buflim) {
1358             if (appData.debugMode) {
1359                 fprintf(debugFP, ">ICS: ");
1360                 show_bytes(debugFP, buf, newcount);
1361                 fprintf(debugFP, "\n");
1362             }
1363             outcount = OutputToProcess(pr, buf, newcount, outError);
1364             if (outcount < newcount) return -1; /* to be sure */
1365             q = buf;
1366             newcount = 0;
1367         }
1368         if (*p == '\n') {
1369             *q++ = '\r';
1370             newcount++;
1371         } else if (((unsigned char) *p) == TN_IAC) {
1372             *q++ = (char) TN_IAC;
1373             newcount ++;
1374         }
1375         *q++ = *p++;
1376         newcount++;
1377         left--;
1378     }
1379     if (appData.debugMode) {
1380         fprintf(debugFP, ">ICS: ");
1381         show_bytes(debugFP, buf, newcount);
1382         fprintf(debugFP, "\n");
1383     }
1384     outcount = OutputToProcess(pr, buf, newcount, outError);
1385     if (outcount < newcount) return -1; /* to be sure */
1386     return count;
1387 }
1388
1389 void
1390 read_from_player(isr, closure, message, count, error)
1391      InputSourceRef isr;
1392      VOIDSTAR closure;
1393      char *message;
1394      int count;
1395      int error;
1396 {
1397     int outError, outCount;
1398     static int gotEof = 0;
1399
1400     /* Pass data read from player on to ICS */
1401     if (count > 0) {
1402         gotEof = 0;
1403         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1404         if (outCount < count) {
1405             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1406         }
1407     } else if (count < 0) {
1408         RemoveInputSource(isr);
1409         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1410     } else if (gotEof++ > 0) {
1411         RemoveInputSource(isr);
1412         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1413     }
1414 }
1415
1416 void
1417 KeepAlive()
1418 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1419     SendToICS("date\n");
1420     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1421 }
1422
1423 /* added routine for printf style output to ics */
1424 void ics_printf(char *format, ...)
1425 {
1426     char buffer[MSG_SIZ];
1427     va_list args;
1428
1429     va_start(args, format);
1430     vsnprintf(buffer, sizeof(buffer), format, args);
1431     buffer[sizeof(buffer)-1] = '\0';
1432     SendToICS(buffer);
1433     va_end(args);
1434 }
1435
1436 void
1437 SendToICS(s)
1438      char *s;
1439 {
1440     int count, outCount, outError;
1441
1442     if (icsPR == NULL) return;
1443
1444     count = strlen(s);
1445     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1446     if (outCount < count) {
1447         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1448     }
1449 }
1450
1451 /* This is used for sending logon scripts to the ICS. Sending
1452    without a delay causes problems when using timestamp on ICC
1453    (at least on my machine). */
1454 void
1455 SendToICSDelayed(s,msdelay)
1456      char *s;
1457      long msdelay;
1458 {
1459     int count, outCount, outError;
1460
1461     if (icsPR == NULL) return;
1462
1463     count = strlen(s);
1464     if (appData.debugMode) {
1465         fprintf(debugFP, ">ICS: ");
1466         show_bytes(debugFP, s, count);
1467         fprintf(debugFP, "\n");
1468     }
1469     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1470                                       msdelay);
1471     if (outCount < count) {
1472         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1473     }
1474 }
1475
1476
1477 /* Remove all highlighting escape sequences in s
1478    Also deletes any suffix starting with '(' 
1479    */
1480 char *
1481 StripHighlightAndTitle(s)
1482      char *s;
1483 {
1484     static char retbuf[MSG_SIZ];
1485     char *p = retbuf;
1486
1487     while (*s != NULLCHAR) {
1488         while (*s == '\033') {
1489             while (*s != NULLCHAR && !isalpha(*s)) s++;
1490             if (*s != NULLCHAR) s++;
1491         }
1492         while (*s != NULLCHAR && *s != '\033') {
1493             if (*s == '(' || *s == '[') {
1494                 *p = NULLCHAR;
1495                 return retbuf;
1496             }
1497             *p++ = *s++;
1498         }
1499     }
1500     *p = NULLCHAR;
1501     return retbuf;
1502 }
1503
1504 /* Remove all highlighting escape sequences in s */
1505 char *
1506 StripHighlight(s)
1507      char *s;
1508 {
1509     static char retbuf[MSG_SIZ];
1510     char *p = retbuf;
1511
1512     while (*s != NULLCHAR) {
1513         while (*s == '\033') {
1514             while (*s != NULLCHAR && !isalpha(*s)) s++;
1515             if (*s != NULLCHAR) s++;
1516         }
1517         while (*s != NULLCHAR && *s != '\033') {
1518             *p++ = *s++;
1519         }
1520     }
1521     *p = NULLCHAR;
1522     return retbuf;
1523 }
1524
1525 char *variantNames[] = VARIANT_NAMES;
1526 char *
1527 VariantName(v)
1528      VariantClass v;
1529 {
1530     return variantNames[v];
1531 }
1532
1533
1534 /* Identify a variant from the strings the chess servers use or the
1535    PGN Variant tag names we use. */
1536 VariantClass
1537 StringToVariant(e)
1538      char *e;
1539 {
1540     char *p;
1541     int wnum = -1;
1542     VariantClass v = VariantNormal;
1543     int i, found = FALSE;
1544     char buf[MSG_SIZ];
1545
1546     if (!e) return v;
1547
1548     /* [HGM] skip over optional board-size prefixes */
1549     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1550         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1551         while( *e++ != '_');
1552     }
1553
1554     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1555         v = VariantNormal;
1556         found = TRUE;
1557     } else
1558     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1559       if (StrCaseStr(e, variantNames[i])) {
1560         v = (VariantClass) i;
1561         found = TRUE;
1562         break;
1563       }
1564     }
1565
1566     if (!found) {
1567       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1568           || StrCaseStr(e, "wild/fr") 
1569           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1570         v = VariantFischeRandom;
1571       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1572                  (i = 1, p = StrCaseStr(e, "w"))) {
1573         p += i;
1574         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1575         if (isdigit(*p)) {
1576           wnum = atoi(p);
1577         } else {
1578           wnum = -1;
1579         }
1580         switch (wnum) {
1581         case 0: /* FICS only, actually */
1582         case 1:
1583           /* Castling legal even if K starts on d-file */
1584           v = VariantWildCastle;
1585           break;
1586         case 2:
1587         case 3:
1588         case 4:
1589           /* Castling illegal even if K & R happen to start in
1590              normal positions. */
1591           v = VariantNoCastle;
1592           break;
1593         case 5:
1594         case 7:
1595         case 8:
1596         case 10:
1597         case 11:
1598         case 12:
1599         case 13:
1600         case 14:
1601         case 15:
1602         case 18:
1603         case 19:
1604           /* Castling legal iff K & R start in normal positions */
1605           v = VariantNormal;
1606           break;
1607         case 6:
1608         case 20:
1609         case 21:
1610           /* Special wilds for position setup; unclear what to do here */
1611           v = VariantLoadable;
1612           break;
1613         case 9:
1614           /* Bizarre ICC game */
1615           v = VariantTwoKings;
1616           break;
1617         case 16:
1618           v = VariantKriegspiel;
1619           break;
1620         case 17:
1621           v = VariantLosers;
1622           break;
1623         case 22:
1624           v = VariantFischeRandom;
1625           break;
1626         case 23:
1627           v = VariantCrazyhouse;
1628           break;
1629         case 24:
1630           v = VariantBughouse;
1631           break;
1632         case 25:
1633           v = Variant3Check;
1634           break;
1635         case 26:
1636           /* Not quite the same as FICS suicide! */
1637           v = VariantGiveaway;
1638           break;
1639         case 27:
1640           v = VariantAtomic;
1641           break;
1642         case 28:
1643           v = VariantShatranj;
1644           break;
1645
1646         /* Temporary names for future ICC types.  The name *will* change in 
1647            the next xboard/WinBoard release after ICC defines it. */
1648         case 29:
1649           v = Variant29;
1650           break;
1651         case 30:
1652           v = Variant30;
1653           break;
1654         case 31:
1655           v = Variant31;
1656           break;
1657         case 32:
1658           v = Variant32;
1659           break;
1660         case 33:
1661           v = Variant33;
1662           break;
1663         case 34:
1664           v = Variant34;
1665           break;
1666         case 35:
1667           v = Variant35;
1668           break;
1669         case 36:
1670           v = Variant36;
1671           break;
1672         case 37:
1673           v = VariantShogi;
1674           break;
1675         case 38:
1676           v = VariantXiangqi;
1677           break;
1678         case 39:
1679           v = VariantCourier;
1680           break;
1681         case 40:
1682           v = VariantGothic;
1683           break;
1684         case 41:
1685           v = VariantCapablanca;
1686           break;
1687         case 42:
1688           v = VariantKnightmate;
1689           break;
1690         case 43:
1691           v = VariantFairy;
1692           break;
1693         case 44:
1694           v = VariantCylinder;
1695           break;
1696         case 45:
1697           v = VariantFalcon;
1698           break;
1699         case 46:
1700           v = VariantCapaRandom;
1701           break;
1702         case 47:
1703           v = VariantBerolina;
1704           break;
1705         case 48:
1706           v = VariantJanus;
1707           break;
1708         case 49:
1709           v = VariantSuper;
1710           break;
1711         case 50:
1712           v = VariantGreat;
1713           break;
1714         case -1:
1715           /* Found "wild" or "w" in the string but no number;
1716              must assume it's normal chess. */
1717           v = VariantNormal;
1718           break;
1719         default:
1720           sprintf(buf, _("Unknown wild type %d"), wnum);
1721           DisplayError(buf, 0);
1722           v = VariantUnknown;
1723           break;
1724         }
1725       }
1726     }
1727     if (appData.debugMode) {
1728       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1729               e, wnum, VariantName(v));
1730     }
1731     return v;
1732 }
1733
1734 static int leftover_start = 0, leftover_len = 0;
1735 char star_match[STAR_MATCH_N][MSG_SIZ];
1736
1737 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1738    advance *index beyond it, and set leftover_start to the new value of
1739    *index; else return FALSE.  If pattern contains the character '*', it
1740    matches any sequence of characters not containing '\r', '\n', or the
1741    character following the '*' (if any), and the matched sequence(s) are
1742    copied into star_match.
1743    */
1744 int
1745 looking_at(buf, index, pattern)
1746      char *buf;
1747      int *index;
1748      char *pattern;
1749 {
1750     char *bufp = &buf[*index], *patternp = pattern;
1751     int star_count = 0;
1752     char *matchp = star_match[0];
1753     
1754     for (;;) {
1755         if (*patternp == NULLCHAR) {
1756             *index = leftover_start = bufp - buf;
1757             *matchp = NULLCHAR;
1758             return TRUE;
1759         }
1760         if (*bufp == NULLCHAR) return FALSE;
1761         if (*patternp == '*') {
1762             if (*bufp == *(patternp + 1)) {
1763                 *matchp = NULLCHAR;
1764                 matchp = star_match[++star_count];
1765                 patternp += 2;
1766                 bufp++;
1767                 continue;
1768             } else if (*bufp == '\n' || *bufp == '\r') {
1769                 patternp++;
1770                 if (*patternp == NULLCHAR)
1771                   continue;
1772                 else
1773                   return FALSE;
1774             } else {
1775                 *matchp++ = *bufp++;
1776                 continue;
1777             }
1778         }
1779         if (*patternp != *bufp) return FALSE;
1780         patternp++;
1781         bufp++;
1782     }
1783 }
1784
1785 void
1786 SendToPlayer(data, length)
1787      char *data;
1788      int length;
1789 {
1790     int error, outCount;
1791     outCount = OutputToProcess(NoProc, data, length, &error);
1792     if (outCount < length) {
1793         DisplayFatalError(_("Error writing to display"), error, 1);
1794     }
1795 }
1796
1797 void
1798 PackHolding(packed, holding)
1799      char packed[];
1800      char *holding;
1801 {
1802     char *p = holding;
1803     char *q = packed;
1804     int runlength = 0;
1805     int curr = 9999;
1806     do {
1807         if (*p == curr) {
1808             runlength++;
1809         } else {
1810             switch (runlength) {
1811               case 0:
1812                 break;
1813               case 1:
1814                 *q++ = curr;
1815                 break;
1816               case 2:
1817                 *q++ = curr;
1818                 *q++ = curr;
1819                 break;
1820               default:
1821                 sprintf(q, "%d", runlength);
1822                 while (*q) q++;
1823                 *q++ = curr;
1824                 break;
1825             }
1826             runlength = 1;
1827             curr = *p;
1828         }
1829     } while (*p++);
1830     *q = NULLCHAR;
1831 }
1832
1833 /* Telnet protocol requests from the front end */
1834 void
1835 TelnetRequest(ddww, option)
1836      unsigned char ddww, option;
1837 {
1838     unsigned char msg[3];
1839     int outCount, outError;
1840
1841     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1842
1843     if (appData.debugMode) {
1844         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1845         switch (ddww) {
1846           case TN_DO:
1847             ddwwStr = "DO";
1848             break;
1849           case TN_DONT:
1850             ddwwStr = "DONT";
1851             break;
1852           case TN_WILL:
1853             ddwwStr = "WILL";
1854             break;
1855           case TN_WONT:
1856             ddwwStr = "WONT";
1857             break;
1858           default:
1859             ddwwStr = buf1;
1860             sprintf(buf1, "%d", ddww);
1861             break;
1862         }
1863         switch (option) {
1864           case TN_ECHO:
1865             optionStr = "ECHO";
1866             break;
1867           default:
1868             optionStr = buf2;
1869             sprintf(buf2, "%d", option);
1870             break;
1871         }
1872         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1873     }
1874     msg[0] = TN_IAC;
1875     msg[1] = ddww;
1876     msg[2] = option;
1877     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1878     if (outCount < 3) {
1879         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1880     }
1881 }
1882
1883 void
1884 DoEcho()
1885 {
1886     if (!appData.icsActive) return;
1887     TelnetRequest(TN_DO, TN_ECHO);
1888 }
1889
1890 void
1891 DontEcho()
1892 {
1893     if (!appData.icsActive) return;
1894     TelnetRequest(TN_DONT, TN_ECHO);
1895 }
1896
1897 void
1898 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1899 {
1900     /* put the holdings sent to us by the server on the board holdings area */
1901     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1902     char p;
1903     ChessSquare piece;
1904
1905     if(gameInfo.holdingsWidth < 2)  return;
1906
1907     if( (int)lowestPiece >= BlackPawn ) {
1908         holdingsColumn = 0;
1909         countsColumn = 1;
1910         holdingsStartRow = BOARD_HEIGHT-1;
1911         direction = -1;
1912     } else {
1913         holdingsColumn = BOARD_WIDTH-1;
1914         countsColumn = BOARD_WIDTH-2;
1915         holdingsStartRow = 0;
1916         direction = 1;
1917     }
1918
1919     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1920         board[i][holdingsColumn] = EmptySquare;
1921         board[i][countsColumn]   = (ChessSquare) 0;
1922     }
1923     while( (p=*holdings++) != NULLCHAR ) {
1924         piece = CharToPiece( ToUpper(p) );
1925         if(piece == EmptySquare) continue;
1926         /*j = (int) piece - (int) WhitePawn;*/
1927         j = PieceToNumber(piece);
1928         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1929         if(j < 0) continue;               /* should not happen */
1930         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1931         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1932         board[holdingsStartRow+j*direction][countsColumn]++;
1933     }
1934
1935 }
1936
1937
1938 void
1939 VariantSwitch(Board board, VariantClass newVariant)
1940 {
1941    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1942
1943    startedFromPositionFile = FALSE;
1944    if(gameInfo.variant == newVariant) return;
1945
1946    /* [HGM] This routine is called each time an assignment is made to
1947     * gameInfo.variant during a game, to make sure the board sizes
1948     * are set to match the new variant. If that means adding or deleting
1949     * holdings, we shift the playing board accordingly
1950     * This kludge is needed because in ICS observe mode, we get boards
1951     * of an ongoing game without knowing the variant, and learn about the
1952     * latter only later. This can be because of the move list we requested,
1953     * in which case the game history is refilled from the beginning anyway,
1954     * but also when receiving holdings of a crazyhouse game. In the latter
1955     * case we want to add those holdings to the already received position.
1956     */
1957
1958    
1959    if (appData.debugMode) {
1960      fprintf(debugFP, "Switch board from %s to %s\n",
1961              VariantName(gameInfo.variant), VariantName(newVariant));
1962      setbuf(debugFP, NULL);
1963    }
1964    shuffleOpenings = 0;       /* [HGM] shuffle */
1965    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1966    switch(newVariant) 
1967      {
1968      case VariantShogi:
1969        newWidth = 9;  newHeight = 9;
1970        gameInfo.holdingsSize = 7;
1971      case VariantBughouse:
1972      case VariantCrazyhouse:
1973        newHoldingsWidth = 2; break;
1974      case VariantGreat:
1975        newWidth = 10;
1976      case VariantSuper:
1977        newHoldingsWidth = 2;
1978        gameInfo.holdingsSize = 8;
1979        return;
1980      case VariantGothic:
1981      case VariantCapablanca:
1982      case VariantCapaRandom:
1983        newWidth = 10;
1984      default:
1985        newHoldingsWidth = gameInfo.holdingsSize = 0;
1986      };
1987    
1988    if(newWidth  != gameInfo.boardWidth  ||
1989       newHeight != gameInfo.boardHeight ||
1990       newHoldingsWidth != gameInfo.holdingsWidth ) {
1991      
1992      /* shift position to new playing area, if needed */
1993      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1994        for(i=0; i<BOARD_HEIGHT; i++) 
1995          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1996            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1997              board[i][j];
1998        for(i=0; i<newHeight; i++) {
1999          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2000          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2001        }
2002      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2003        for(i=0; i<BOARD_HEIGHT; i++)
2004          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2005            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2006              board[i][j];
2007      }
2008      gameInfo.boardWidth  = newWidth;
2009      gameInfo.boardHeight = newHeight;
2010      gameInfo.holdingsWidth = newHoldingsWidth;
2011      gameInfo.variant = newVariant;
2012      InitDrawingSizes(-2, 0);
2013      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2014    } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2015    
2016    DrawPosition(TRUE, boards[currentMove]);
2017 }
2018
2019 static int loggedOn = FALSE;
2020
2021 /*-- Game start info cache: --*/
2022 int gs_gamenum;
2023 char gs_kind[MSG_SIZ];
2024 static char player1Name[128] = "";
2025 static char player2Name[128] = "";\r
2026 static char cont_seq[] = "\n\\   ";
2027 static int player1Rating = -1;
2028 static int player2Rating = -1;
2029 /*----------------------------*/
2030
2031 ColorClass curColor = ColorNormal;
2032 int suppressKibitz = 0;
2033
2034 void
2035 read_from_ics(isr, closure, data, count, error)
2036      InputSourceRef isr;
2037      VOIDSTAR closure;
2038      char *data;
2039      int count;
2040      int error;
2041 {
2042 #define BUF_SIZE 8192
2043 #define STARTED_NONE 0
2044 #define STARTED_MOVES 1
2045 #define STARTED_BOARD 2
2046 #define STARTED_OBSERVE 3
2047 #define STARTED_HOLDINGS 4
2048 #define STARTED_CHATTER 5
2049 #define STARTED_COMMENT 6
2050 #define STARTED_MOVES_NOHIDE 7
2051     
2052     static int started = STARTED_NONE;
2053     static char parse[20000];
2054     static int parse_pos = 0;
2055     static char buf[BUF_SIZE + 1];
2056     static int firstTime = TRUE, intfSet = FALSE;
2057     static ColorClass prevColor = ColorNormal;
2058     static int savingComment = FALSE;
2059     static int cmatch = 0; // continuation sequence match
2060     char *bp;
2061     char str[500];
2062     int i, oldi;
2063     int buf_len;
2064     int next_out;
2065     int tkind;
2066     int backup;    /* [DM] For zippy color lines */
2067     char *p;
2068     char talker[MSG_SIZ]; // [HGM] chat
2069     int channel;
2070
2071     if (appData.debugMode) {
2072       if (!error) {
2073         fprintf(debugFP, "<ICS: ");
2074         show_bytes(debugFP, data, count);
2075         fprintf(debugFP, "\n");
2076       }
2077     }
2078
2079     if (appData.debugMode) { int f = forwardMostMove;
2080         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2081                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2082     }
2083     if (count > 0) {
2084         /* If last read ended with a partial line that we couldn't parse,
2085            prepend it to the new read and try again. */
2086         if (leftover_len > 0) {
2087             for (i=0; i<leftover_len; i++)
2088               buf[i] = buf[leftover_start + i];
2089         }
2090
2091     /* copy new characters into the buffer */
2092     bp = buf + leftover_len;
2093     buf_len=leftover_len;
2094     for (i=0; i<count; i++)
2095     {
2096         // ignore these
2097         if (data[i] == '\r')
2098             continue;
2099
2100         // join lines split by ICS?
2101         if (!appData.noJoin)
2102         {
2103             /*
2104                 Joining just consists of finding matches against the
2105                 continuation sequence, and discarding that sequence
2106                 if found instead of copying it.  So, until a match
2107                 fails, there's nothing to do since it might be the
2108                 complete sequence, and thus, something we don't want
2109                 copied.
2110             */
2111             if (data[i] == cont_seq[cmatch])
2112             {
2113                 cmatch++;
2114                 if (cmatch == strlen(cont_seq))
2115                 {
2116                     cmatch = 0; // complete match.  just reset the counter
2117
2118                     /*
2119                         it's possible for the ICS to not include the space
2120                         at the end of the last word, making our [correct]
2121                         join operation fuse two separate words.  the server
2122                         does this when the space occurs at the width setting.
2123                     */
2124                     if (!buf_len || buf[buf_len-1] != ' ')
2125                     {
2126                         *bp++ = ' ';
2127                         buf_len++;
2128                     }
2129                 }
2130                 continue;
2131             }
2132             else if (cmatch)
2133             {
2134                 /*
2135                     match failed, so we have to copy what matched before
2136                     falling through and copying this character.  In reality,
2137                     this will only ever be just the newline character, but
2138                     it doesn't hurt to be precise.
2139                 */
2140                 strncpy(bp, cont_seq, cmatch);
2141                 bp += cmatch;
2142                 buf_len += cmatch;
2143                 cmatch = 0;
2144             }
2145         }
2146
2147         // copy this char
2148         *bp++ = data[i];
2149         buf_len++;
2150     }
2151
2152         buf[buf_len] = NULLCHAR;
2153         next_out = leftover_len;
2154         leftover_start = 0;
2155         
2156         i = 0;
2157         while (i < buf_len) {
2158             /* Deal with part of the TELNET option negotiation
2159                protocol.  We refuse to do anything beyond the
2160                defaults, except that we allow the WILL ECHO option,
2161                which ICS uses to turn off password echoing when we are
2162                directly connected to it.  We reject this option
2163                if localLineEditing mode is on (always on in xboard)
2164                and we are talking to port 23, which might be a real
2165                telnet server that will try to keep WILL ECHO on permanently.
2166              */
2167             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2168                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2169                 unsigned char option;
2170                 oldi = i;
2171                 switch ((unsigned char) buf[++i]) {
2172                   case TN_WILL:
2173                     if (appData.debugMode)
2174                       fprintf(debugFP, "\n<WILL ");
2175                     switch (option = (unsigned char) buf[++i]) {
2176                       case TN_ECHO:
2177                         if (appData.debugMode)
2178                           fprintf(debugFP, "ECHO ");
2179                         /* Reply only if this is a change, according
2180                            to the protocol rules. */
2181                         if (remoteEchoOption) break;
2182                         if (appData.localLineEditing &&
2183                             atoi(appData.icsPort) == TN_PORT) {
2184                             TelnetRequest(TN_DONT, TN_ECHO);
2185                         } else {
2186                             EchoOff();
2187                             TelnetRequest(TN_DO, TN_ECHO);
2188                             remoteEchoOption = TRUE;
2189                         }
2190                         break;
2191                       default:
2192                         if (appData.debugMode)
2193                           fprintf(debugFP, "%d ", option);
2194                         /* Whatever this is, we don't want it. */
2195                         TelnetRequest(TN_DONT, option);
2196                         break;
2197                     }
2198                     break;
2199                   case TN_WONT:
2200                     if (appData.debugMode)
2201                       fprintf(debugFP, "\n<WONT ");
2202                     switch (option = (unsigned char) buf[++i]) {
2203                       case TN_ECHO:
2204                         if (appData.debugMode)
2205                           fprintf(debugFP, "ECHO ");
2206                         /* Reply only if this is a change, according
2207                            to the protocol rules. */
2208                         if (!remoteEchoOption) break;
2209                         EchoOn();
2210                         TelnetRequest(TN_DONT, TN_ECHO);
2211                         remoteEchoOption = FALSE;
2212                         break;
2213                       default:
2214                         if (appData.debugMode)
2215                           fprintf(debugFP, "%d ", (unsigned char) option);
2216                         /* Whatever this is, it must already be turned
2217                            off, because we never agree to turn on
2218                            anything non-default, so according to the
2219                            protocol rules, we don't reply. */
2220                         break;
2221                     }
2222                     break;
2223                   case TN_DO:
2224                     if (appData.debugMode)
2225                       fprintf(debugFP, "\n<DO ");
2226                     switch (option = (unsigned char) buf[++i]) {
2227                       default:
2228                         /* Whatever this is, we refuse to do it. */
2229                         if (appData.debugMode)
2230                           fprintf(debugFP, "%d ", option);
2231                         TelnetRequest(TN_WONT, option);
2232                         break;
2233                     }
2234                     break;
2235                   case TN_DONT:
2236                     if (appData.debugMode)
2237                       fprintf(debugFP, "\n<DONT ");
2238                     switch (option = (unsigned char) buf[++i]) {
2239                       default:
2240                         if (appData.debugMode)
2241                           fprintf(debugFP, "%d ", option);
2242                         /* Whatever this is, we are already not doing
2243                            it, because we never agree to do anything
2244                            non-default, so according to the protocol
2245                            rules, we don't reply. */
2246                         break;
2247                     }
2248                     break;
2249                   case TN_IAC:
2250                     if (appData.debugMode)
2251                       fprintf(debugFP, "\n<IAC ");
2252                     /* Doubled IAC; pass it through */
2253                     i--;
2254                     break;
2255                   default:
2256                     if (appData.debugMode)
2257                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2258                     /* Drop all other telnet commands on the floor */
2259                     break;
2260                 }
2261                 if (oldi > next_out)
2262                   SendToPlayer(&buf[next_out], oldi - next_out);
2263                 if (++i > next_out)
2264                   next_out = i;
2265                 continue;
2266             }
2267                 
2268             /* OK, this at least will *usually* work */
2269             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2270                 loggedOn = TRUE;
2271             }
2272             
2273             if (loggedOn && !intfSet) {
2274                 if (ics_type == ICS_ICC) {
2275                   sprintf(str,
2276                           "/set-quietly interface %s\n/set-quietly style 12\n",
2277                           programVersion);
2278                 } else if (ics_type == ICS_CHESSNET) {
2279                   sprintf(str, "/style 12\n");
2280                 } else {
2281                   strcpy(str, "alias $ @\n$set interface ");
2282                   strcat(str, programVersion);
2283                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2284 #ifdef WIN32
2285                   strcat(str, "$iset nohighlight 1\n");
2286 #endif
2287                   strcat(str, "$iset lock 1\n$style 12\n");
2288                 }
2289                 SendToICS(str);
2290                 NotifyFrontendLogin();
2291                 intfSet = TRUE;
2292             }
2293
2294             if (started == STARTED_COMMENT) {
2295                 /* Accumulate characters in comment */
2296                 parse[parse_pos++] = buf[i];
2297                 if (buf[i] == '\n') {
2298                     parse[parse_pos] = NULLCHAR;
2299                     if(chattingPartner>=0) {
2300                         char mess[MSG_SIZ];
2301                         sprintf(mess, "%s%s", talker, parse);
2302                         OutputChatMessage(chattingPartner, mess);
2303                         chattingPartner = -1;
2304                     } else
2305                     if(!suppressKibitz) // [HGM] kibitz
2306                         AppendComment(forwardMostMove, StripHighlight(parse));
2307                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2308                         int nrDigit = 0, nrAlph = 0, i;
2309                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2310                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2311                         parse[parse_pos] = NULLCHAR;
2312                         // try to be smart: if it does not look like search info, it should go to
2313                         // ICS interaction window after all, not to engine-output window.
2314                         for(i=0; i<parse_pos; i++) { // count letters and digits
2315                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2316                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2317                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2318                         }
2319                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2320                             int depth=0; float score;
2321                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2322                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2323                                 pvInfoList[forwardMostMove-1].depth = depth;
2324                                 pvInfoList[forwardMostMove-1].score = 100*score;
2325                             }
2326                             OutputKibitz(suppressKibitz, parse);
2327                         } else {
2328                             char tmp[MSG_SIZ];
2329                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2330                             SendToPlayer(tmp, strlen(tmp));
2331                         }
2332                     }
2333                     started = STARTED_NONE;
2334                 } else {
2335                     /* Don't match patterns against characters in chatter */
2336                     i++;
2337                     continue;
2338                 }
2339             }
2340             if (started == STARTED_CHATTER) {
2341                 if (buf[i] != '\n') {
2342                     /* Don't match patterns against characters in chatter */
2343                     i++;
2344                     continue;
2345                 }
2346                 started = STARTED_NONE;
2347             }
2348
2349             /* Kludge to deal with rcmd protocol */
2350             if (firstTime && looking_at(buf, &i, "\001*")) {
2351                 DisplayFatalError(&buf[1], 0, 1);
2352                 continue;
2353             } else {
2354                 firstTime = FALSE;
2355             }
2356
2357             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2358                 ics_type = ICS_ICC;
2359                 ics_prefix = "/";
2360                 if (appData.debugMode)
2361                   fprintf(debugFP, "ics_type %d\n", ics_type);
2362                 continue;
2363             }
2364             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2365                 ics_type = ICS_FICS;
2366                 ics_prefix = "$";
2367                 if (appData.debugMode)
2368                   fprintf(debugFP, "ics_type %d\n", ics_type);
2369                 continue;
2370             }
2371             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2372                 ics_type = ICS_CHESSNET;
2373                 ics_prefix = "/";
2374                 if (appData.debugMode)
2375                   fprintf(debugFP, "ics_type %d\n", ics_type);
2376                 continue;
2377             }
2378
2379             if (!loggedOn &&
2380                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2381                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2382                  looking_at(buf, &i, "will be \"*\""))) {
2383               strcpy(ics_handle, star_match[0]);
2384               continue;
2385             }
2386
2387             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2388               char buf[MSG_SIZ];
2389               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2390               DisplayIcsInteractionTitle(buf);
2391               have_set_title = TRUE;
2392             }
2393
2394             /* skip finger notes */
2395             if (started == STARTED_NONE &&
2396                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2397                  (buf[i] == '1' && buf[i+1] == '0')) &&
2398                 buf[i+2] == ':' && buf[i+3] == ' ') {
2399               started = STARTED_CHATTER;
2400               i += 3;
2401               continue;
2402             }
2403
2404             /* skip formula vars */
2405             if (started == STARTED_NONE &&
2406                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2407               started = STARTED_CHATTER;
2408               i += 3;
2409               continue;
2410             }
2411
2412             oldi = i;
2413             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2414             if (appData.autoKibitz && started == STARTED_NONE && 
2415                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2416                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2417                 if(looking_at(buf, &i, "* kibitzes: ") &&
2418                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2419                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2420                         suppressKibitz = TRUE;
2421                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2422                                 && (gameMode == IcsPlayingWhite)) ||
2423                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2424                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2425                             started = STARTED_CHATTER; // own kibitz we simply discard
2426                         else {
2427                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2428                             parse_pos = 0; parse[0] = NULLCHAR;
2429                             savingComment = TRUE;
2430                             suppressKibitz = gameMode != IcsObserving ? 2 :
2431                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2432                         } 
2433                         continue;
2434                 } else
2435                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2436                     started = STARTED_CHATTER;
2437                     suppressKibitz = TRUE;
2438                 }
2439             } // [HGM] kibitz: end of patch
2440
2441 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2442
2443             // [HGM] chat: intercept tells by users for which we have an open chat window
2444             channel = -1;
2445             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2446                                            looking_at(buf, &i, "* whispers:") ||
2447                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2448                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2449                 int p;
2450                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2451                 chattingPartner = -1;
2452
2453                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2454                 for(p=0; p<MAX_CHAT; p++) {
2455                     if(channel == atoi(chatPartner[p])) {
2456                     talker[0] = '['; strcat(talker, "]");
2457                     chattingPartner = p; break;
2458                     }
2459                 } else
2460                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2461                 for(p=0; p<MAX_CHAT; p++) {
2462                     if(!strcmp("WHISPER", chatPartner[p])) {
2463                         talker[0] = '['; strcat(talker, "]");
2464                         chattingPartner = p; break;
2465                     }
2466                 }
2467                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2468                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2469                     talker[0] = 0;
2470                     chattingPartner = p; break;
2471                 }
2472                 if(chattingPartner<0) i = oldi; else {
2473                     started = STARTED_COMMENT;
2474                     parse_pos = 0; parse[0] = NULLCHAR;
2475                     savingComment = TRUE;
2476                     suppressKibitz = TRUE;
2477                 }
2478             } // [HGM] chat: end of patch
2479
2480             if (appData.zippyTalk || appData.zippyPlay) {
2481                 /* [DM] Backup address for color zippy lines */
2482                 backup = i;
2483 #if ZIPPY
2484        #ifdef WIN32
2485                if (loggedOn == TRUE)
2486                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2487                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2488        #else
2489                 if (ZippyControl(buf, &i) ||
2490                     ZippyConverse(buf, &i) ||
2491                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2492                       loggedOn = TRUE;
2493                       if (!appData.colorize) continue;
2494                 }
2495        #endif
2496 #endif
2497             } // [DM] 'else { ' deleted
2498                 if (
2499                     /* Regular tells and says */
2500                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2501                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2502                     looking_at(buf, &i, "* says: ") ||
2503                     /* Don't color "message" or "messages" output */
2504                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2505                     looking_at(buf, &i, "*. * at *:*: ") ||
2506                     looking_at(buf, &i, "--* (*:*): ") ||
2507                     /* Message notifications (same color as tells) */
2508                     looking_at(buf, &i, "* has left a message ") ||
2509                     looking_at(buf, &i, "* just sent you a message:\n") ||
2510                     /* Whispers and kibitzes */
2511                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2512                     looking_at(buf, &i, "* kibitzes: ") ||
2513                     /* Channel tells */
2514                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2515
2516                   if (tkind == 1 && strchr(star_match[0], ':')) {
2517                       /* Avoid "tells you:" spoofs in channels */
2518                      tkind = 3;
2519                   }
2520                   if (star_match[0][0] == NULLCHAR ||
2521                       strchr(star_match[0], ' ') ||
2522                       (tkind == 3 && strchr(star_match[1], ' '))) {
2523                     /* Reject bogus matches */
2524                     i = oldi;
2525                   } else {
2526                     if (appData.colorize) {
2527                       if (oldi > next_out) {
2528                         SendToPlayer(&buf[next_out], oldi - next_out);
2529                         next_out = oldi;
2530                       }
2531                       switch (tkind) {
2532                       case 1:
2533                         Colorize(ColorTell, FALSE);
2534                         curColor = ColorTell;
2535                         break;
2536                       case 2:
2537                         Colorize(ColorKibitz, FALSE);
2538                         curColor = ColorKibitz;
2539                         break;
2540                       case 3:
2541                         p = strrchr(star_match[1], '(');
2542                         if (p == NULL) {
2543                           p = star_match[1];
2544                         } else {
2545                           p++;
2546                         }
2547                         if (atoi(p) == 1) {
2548                           Colorize(ColorChannel1, FALSE);
2549                           curColor = ColorChannel1;
2550                         } else {
2551                           Colorize(ColorChannel, FALSE);
2552                           curColor = ColorChannel;
2553                         }
2554                         break;
2555                       case 5:
2556                         curColor = ColorNormal;
2557                         break;
2558                       }
2559                     }
2560                     if (started == STARTED_NONE && appData.autoComment &&
2561                         (gameMode == IcsObserving ||
2562                          gameMode == IcsPlayingWhite ||
2563                          gameMode == IcsPlayingBlack)) {
2564                       parse_pos = i - oldi;
2565                       memcpy(parse, &buf[oldi], parse_pos);
2566                       parse[parse_pos] = NULLCHAR;
2567                       started = STARTED_COMMENT;
2568                       savingComment = TRUE;
2569                     } else {
2570                       started = STARTED_CHATTER;
2571                       savingComment = FALSE;
2572                     }
2573                     loggedOn = TRUE;
2574                     continue;
2575                   }
2576                 }
2577
2578                 if (looking_at(buf, &i, "* s-shouts: ") ||
2579                     looking_at(buf, &i, "* c-shouts: ")) {
2580                     if (appData.colorize) {
2581                         if (oldi > next_out) {
2582                             SendToPlayer(&buf[next_out], oldi - next_out);
2583                             next_out = oldi;
2584                         }
2585                         Colorize(ColorSShout, FALSE);
2586                         curColor = ColorSShout;
2587                     }
2588                     loggedOn = TRUE;
2589                     started = STARTED_CHATTER;
2590                     continue;
2591                 }
2592
2593                 if (looking_at(buf, &i, "--->")) {
2594                     loggedOn = TRUE;
2595                     continue;
2596                 }
2597
2598                 if (looking_at(buf, &i, "* shouts: ") ||
2599                     looking_at(buf, &i, "--> ")) {
2600                     if (appData.colorize) {
2601                         if (oldi > next_out) {
2602                             SendToPlayer(&buf[next_out], oldi - next_out);
2603                             next_out = oldi;
2604                         }
2605                         Colorize(ColorShout, FALSE);
2606                         curColor = ColorShout;
2607                     }
2608                     loggedOn = TRUE;
2609                     started = STARTED_CHATTER;
2610                     continue;
2611                 }
2612
2613                 if (looking_at( buf, &i, "Challenge:")) {
2614                     if (appData.colorize) {
2615                         if (oldi > next_out) {
2616                             SendToPlayer(&buf[next_out], oldi - next_out);
2617                             next_out = oldi;
2618                         }
2619                         Colorize(ColorChallenge, FALSE);
2620                         curColor = ColorChallenge;
2621                     }
2622                     loggedOn = TRUE;
2623                     continue;
2624                 }
2625
2626                 if (looking_at(buf, &i, "* offers you") ||
2627                     looking_at(buf, &i, "* offers to be") ||
2628                     looking_at(buf, &i, "* would like to") ||
2629                     looking_at(buf, &i, "* requests to") ||
2630                     looking_at(buf, &i, "Your opponent offers") ||
2631                     looking_at(buf, &i, "Your opponent requests")) {
2632
2633                     if (appData.colorize) {
2634                         if (oldi > next_out) {
2635                             SendToPlayer(&buf[next_out], oldi - next_out);
2636                             next_out = oldi;
2637                         }
2638                         Colorize(ColorRequest, FALSE);
2639                         curColor = ColorRequest;
2640                     }
2641                     continue;
2642                 }
2643
2644                 if (looking_at(buf, &i, "* (*) seeking")) {
2645                     if (appData.colorize) {
2646                         if (oldi > next_out) {
2647                             SendToPlayer(&buf[next_out], oldi - next_out);
2648                             next_out = oldi;
2649                         }
2650                         Colorize(ColorSeek, FALSE);
2651                         curColor = ColorSeek;
2652                     }
2653                     continue;
2654             }
2655
2656             if (looking_at(buf, &i, "\\   ")) {
2657                 if (prevColor != ColorNormal) {
2658                     if (oldi > next_out) {
2659                         SendToPlayer(&buf[next_out], oldi - next_out);
2660                         next_out = oldi;
2661                     }
2662                     Colorize(prevColor, TRUE);
2663                     curColor = prevColor;
2664                 }
2665                 if (savingComment) {
2666                     parse_pos = i - oldi;
2667                     memcpy(parse, &buf[oldi], parse_pos);
2668                     parse[parse_pos] = NULLCHAR;
2669                     started = STARTED_COMMENT;
2670                 } else {
2671                     started = STARTED_CHATTER;
2672                 }
2673                 continue;
2674             }
2675
2676             if (looking_at(buf, &i, "Black Strength :") ||
2677                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2678                 looking_at(buf, &i, "<10>") ||
2679                 looking_at(buf, &i, "#@#")) {
2680                 /* Wrong board style */
2681                 loggedOn = TRUE;
2682                 SendToICS(ics_prefix);
2683                 SendToICS("set style 12\n");
2684                 SendToICS(ics_prefix);
2685                 SendToICS("refresh\n");
2686                 continue;
2687             }
2688             
2689             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2690                 ICSInitScript();
2691                 have_sent_ICS_logon = 1;
2692                 continue;
2693             }
2694               
2695             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2696                 (looking_at(buf, &i, "\n<12> ") ||
2697                  looking_at(buf, &i, "<12> "))) {
2698                 loggedOn = TRUE;
2699                 if (oldi > next_out) {
2700                     SendToPlayer(&buf[next_out], oldi - next_out);
2701                 }
2702                 next_out = i;
2703                 started = STARTED_BOARD;
2704                 parse_pos = 0;
2705                 continue;
2706             }
2707
2708             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2709                 looking_at(buf, &i, "<b1> ")) {
2710                 if (oldi > next_out) {
2711                     SendToPlayer(&buf[next_out], oldi - next_out);
2712                 }
2713                 next_out = i;
2714                 started = STARTED_HOLDINGS;
2715                 parse_pos = 0;
2716                 continue;
2717             }
2718
2719             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2720                 loggedOn = TRUE;
2721                 /* Header for a move list -- first line */
2722
2723                 switch (ics_getting_history) {
2724                   case H_FALSE:
2725                     switch (gameMode) {
2726                       case IcsIdle:
2727                       case BeginningOfGame:
2728                         /* User typed "moves" or "oldmoves" while we
2729                            were idle.  Pretend we asked for these
2730                            moves and soak them up so user can step
2731                            through them and/or save them.
2732                            */
2733                         Reset(FALSE, TRUE);
2734                         gameMode = IcsObserving;
2735                         ModeHighlight();
2736                         ics_gamenum = -1;
2737                         ics_getting_history = H_GOT_UNREQ_HEADER;
2738                         break;
2739                       case EditGame: /*?*/
2740                       case EditPosition: /*?*/
2741                         /* Should above feature work in these modes too? */
2742                         /* For now it doesn't */
2743                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2744                         break;
2745                       default:
2746                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2747                         break;
2748                     }
2749                     break;
2750                   case H_REQUESTED:
2751                     /* Is this the right one? */
2752                     if (gameInfo.white && gameInfo.black &&
2753                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2754                         strcmp(gameInfo.black, star_match[2]) == 0) {
2755                         /* All is well */
2756                         ics_getting_history = H_GOT_REQ_HEADER;
2757                     }
2758                     break;
2759                   case H_GOT_REQ_HEADER:
2760                   case H_GOT_UNREQ_HEADER:
2761                   case H_GOT_UNWANTED_HEADER:
2762                   case H_GETTING_MOVES:
2763                     /* Should not happen */
2764                     DisplayError(_("Error gathering move list: two headers"), 0);
2765                     ics_getting_history = H_FALSE;
2766                     break;
2767                 }
2768
2769                 /* Save player ratings into gameInfo if needed */
2770                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2771                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2772                     (gameInfo.whiteRating == -1 ||
2773                      gameInfo.blackRating == -1)) {
2774
2775                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2776                     gameInfo.blackRating = string_to_rating(star_match[3]);
2777                     if (appData.debugMode)
2778                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2779                               gameInfo.whiteRating, gameInfo.blackRating);
2780                 }
2781                 continue;
2782             }
2783
2784             if (looking_at(buf, &i,
2785               "* * match, initial time: * minute*, increment: * second")) {
2786                 /* Header for a move list -- second line */
2787                 /* Initial board will follow if this is a wild game */
2788                 if (gameInfo.event != NULL) free(gameInfo.event);
2789                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2790                 gameInfo.event = StrSave(str);
2791                 /* [HGM] we switched variant. Translate boards if needed. */
2792                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2793                 continue;
2794             }
2795
2796             if (looking_at(buf, &i, "Move  ")) {
2797                 /* Beginning of a move list */
2798                 switch (ics_getting_history) {
2799                   case H_FALSE:
2800                     /* Normally should not happen */
2801                     /* Maybe user hit reset while we were parsing */
2802                     break;
2803                   case H_REQUESTED:
2804                     /* Happens if we are ignoring a move list that is not
2805                      * the one we just requested.  Common if the user
2806                      * tries to observe two games without turning off
2807                      * getMoveList */
2808                     break;
2809                   case H_GETTING_MOVES:
2810                     /* Should not happen */
2811                     DisplayError(_("Error gathering move list: nested"), 0);
2812                     ics_getting_history = H_FALSE;
2813                     break;
2814                   case H_GOT_REQ_HEADER:
2815                     ics_getting_history = H_GETTING_MOVES;
2816                     started = STARTED_MOVES;
2817                     parse_pos = 0;
2818                     if (oldi > next_out) {
2819                         SendToPlayer(&buf[next_out], oldi - next_out);
2820                     }
2821                     break;
2822                   case H_GOT_UNREQ_HEADER:
2823                     ics_getting_history = H_GETTING_MOVES;
2824                     started = STARTED_MOVES_NOHIDE;
2825                     parse_pos = 0;
2826                     break;
2827                   case H_GOT_UNWANTED_HEADER:
2828                     ics_getting_history = H_FALSE;
2829                     break;
2830                 }
2831                 continue;
2832             }                           
2833             
2834             if (looking_at(buf, &i, "% ") ||
2835                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2836                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2837                 savingComment = FALSE;
2838                 switch (started) {
2839                   case STARTED_MOVES:
2840                   case STARTED_MOVES_NOHIDE:
2841                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2842                     parse[parse_pos + i - oldi] = NULLCHAR;
2843                     ParseGameHistory(parse);
2844 #if ZIPPY
2845                     if (appData.zippyPlay && first.initDone) {
2846                         FeedMovesToProgram(&first, forwardMostMove);
2847                         if (gameMode == IcsPlayingWhite) {
2848                             if (WhiteOnMove(forwardMostMove)) {
2849                                 if (first.sendTime) {
2850                                   if (first.useColors) {
2851                                     SendToProgram("black\n", &first); 
2852                                   }
2853                                   SendTimeRemaining(&first, TRUE);
2854                                 }
2855                                 if (first.useColors) {
2856                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2857                                 }
2858                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2859                                 first.maybeThinking = TRUE;
2860                             } else {
2861                                 if (first.usePlayother) {
2862                                   if (first.sendTime) {
2863                                     SendTimeRemaining(&first, TRUE);
2864                                   }
2865                                   SendToProgram("playother\n", &first);
2866                                   firstMove = FALSE;
2867                                 } else {
2868                                   firstMove = TRUE;
2869                                 }
2870                             }
2871                         } else if (gameMode == IcsPlayingBlack) {
2872                             if (!WhiteOnMove(forwardMostMove)) {
2873                                 if (first.sendTime) {
2874                                   if (first.useColors) {
2875                                     SendToProgram("white\n", &first);
2876                                   }
2877                                   SendTimeRemaining(&first, FALSE);
2878                                 }
2879                                 if (first.useColors) {
2880                                   SendToProgram("black\n", &first);
2881                                 }
2882                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2883                                 first.maybeThinking = TRUE;
2884                             } else {
2885                                 if (first.usePlayother) {
2886                                   if (first.sendTime) {
2887                                     SendTimeRemaining(&first, FALSE);
2888                                   }
2889                                   SendToProgram("playother\n", &first);
2890                                   firstMove = FALSE;
2891                                 } else {
2892                                   firstMove = TRUE;
2893                                 }
2894                             }
2895                         }                       
2896                     }
2897 #endif
2898                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2899                         /* Moves came from oldmoves or moves command
2900                            while we weren't doing anything else.
2901                            */
2902                         currentMove = forwardMostMove;
2903                         ClearHighlights();/*!!could figure this out*/
2904                         flipView = appData.flipView;
2905                         DrawPosition(FALSE, boards[currentMove]);
2906                         DisplayBothClocks();
2907                         sprintf(str, "%s vs. %s",
2908                                 gameInfo.white, gameInfo.black);
2909                         DisplayTitle(str);
2910                         gameMode = IcsIdle;
2911                     } else {
2912                         /* Moves were history of an active game */
2913                         if (gameInfo.resultDetails != NULL) {
2914                             free(gameInfo.resultDetails);
2915                             gameInfo.resultDetails = NULL;
2916                         }
2917                     }
2918                     HistorySet(parseList, backwardMostMove,
2919                                forwardMostMove, currentMove-1);
2920                     DisplayMove(currentMove - 1);
2921                     if (started == STARTED_MOVES) next_out = i;
2922                     started = STARTED_NONE;
2923                     ics_getting_history = H_FALSE;
2924                     break;
2925
2926                   case STARTED_OBSERVE:
2927                     started = STARTED_NONE;
2928                     SendToICS(ics_prefix);
2929                     SendToICS("refresh\n");
2930                     break;
2931
2932                   default:
2933                     break;
2934                 }
2935                 if(bookHit) { // [HGM] book: simulate book reply
2936                     static char bookMove[MSG_SIZ]; // a bit generous?
2937
2938                     programStats.nodes = programStats.depth = programStats.time = 
2939                     programStats.score = programStats.got_only_move = 0;
2940                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2941
2942                     strcpy(bookMove, "move ");
2943                     strcat(bookMove, bookHit);
2944                     HandleMachineMove(bookMove, &first);
2945                 }
2946                 continue;
2947             }
2948             
2949             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2950                  started == STARTED_HOLDINGS ||
2951                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2952                 /* Accumulate characters in move list or board */
2953                 parse[parse_pos++] = buf[i];
2954             }
2955             
2956             /* Start of game messages.  Mostly we detect start of game
2957                when the first board image arrives.  On some versions
2958                of the ICS, though, we need to do a "refresh" after starting
2959                to observe in order to get the current board right away. */
2960             if (looking_at(buf, &i, "Adding game * to observation list")) {
2961                 started = STARTED_OBSERVE;
2962                 continue;
2963             }
2964
2965             /* Handle auto-observe */
2966             if (appData.autoObserve &&
2967                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2968                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2969                 char *player;
2970                 /* Choose the player that was highlighted, if any. */
2971                 if (star_match[0][0] == '\033' ||
2972                     star_match[1][0] != '\033') {
2973                     player = star_match[0];
2974                 } else {
2975                     player = star_match[2];
2976                 }
2977                 sprintf(str, "%sobserve %s\n",
2978                         ics_prefix, StripHighlightAndTitle(player));
2979                 SendToICS(str);
2980
2981                 /* Save ratings from notify string */
2982                 strcpy(player1Name, star_match[0]);
2983                 player1Rating = string_to_rating(star_match[1]);
2984                 strcpy(player2Name, star_match[2]);
2985                 player2Rating = string_to_rating(star_match[3]);
2986
2987                 if (appData.debugMode)
2988                   fprintf(debugFP, 
2989                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2990                           player1Name, player1Rating,
2991                           player2Name, player2Rating);
2992
2993                 continue;
2994             }
2995
2996             /* Deal with automatic examine mode after a game,
2997                and with IcsObserving -> IcsExamining transition */
2998             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2999                 looking_at(buf, &i, "has made you an examiner of game *")) {
3000
3001                 int gamenum = atoi(star_match[0]);
3002                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3003                     gamenum == ics_gamenum) {
3004                     /* We were already playing or observing this game;
3005                        no need to refetch history */
3006                     gameMode = IcsExamining;
3007                     if (pausing) {
3008                         pauseExamForwardMostMove = forwardMostMove;
3009                     } else if (currentMove < forwardMostMove) {
3010                         ForwardInner(forwardMostMove);
3011                     }
3012                 } else {
3013                     /* I don't think this case really can happen */
3014                     SendToICS(ics_prefix);
3015                     SendToICS("refresh\n");
3016                 }
3017                 continue;
3018             }    
3019             
3020             /* Error messages */
3021 //          if (ics_user_moved) {
3022             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3023                 if (looking_at(buf, &i, "Illegal move") ||
3024                     looking_at(buf, &i, "Not a legal move") ||
3025                     looking_at(buf, &i, "Your king is in check") ||
3026                     looking_at(buf, &i, "It isn't your turn") ||
3027                     looking_at(buf, &i, "It is not your move")) {
3028                     /* Illegal move */
3029                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3030                         currentMove = --forwardMostMove;
3031                         DisplayMove(currentMove - 1); /* before DMError */
3032                         DrawPosition(FALSE, boards[currentMove]);
3033                         SwitchClocks();
3034                         DisplayBothClocks();
3035                     }
3036                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3037                     ics_user_moved = 0;
3038                     continue;
3039                 }
3040             }
3041
3042             if (looking_at(buf, &i, "still have time") ||
3043                 looking_at(buf, &i, "not out of time") ||
3044                 looking_at(buf, &i, "either player is out of time") ||
3045                 looking_at(buf, &i, "has timeseal; checking")) {
3046                 /* We must have called his flag a little too soon */
3047                 whiteFlag = blackFlag = FALSE;
3048                 continue;
3049             }
3050
3051             if (looking_at(buf, &i, "added * seconds to") ||
3052                 looking_at(buf, &i, "seconds were added to")) {
3053                 /* Update the clocks */
3054                 SendToICS(ics_prefix);
3055                 SendToICS("refresh\n");
3056                 continue;
3057             }
3058
3059             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3060                 ics_clock_paused = TRUE;
3061                 StopClocks();
3062                 continue;
3063             }
3064
3065             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3066                 ics_clock_paused = FALSE;
3067                 StartClocks();
3068                 continue;
3069             }
3070
3071             /* Grab player ratings from the Creating: message.
3072                Note we have to check for the special case when
3073                the ICS inserts things like [white] or [black]. */
3074             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3075                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3076                 /* star_matches:
3077                    0    player 1 name (not necessarily white)
3078                    1    player 1 rating
3079                    2    empty, white, or black (IGNORED)
3080                    3    player 2 name (not necessarily black)
3081                    4    player 2 rating
3082                    
3083                    The names/ratings are sorted out when the game
3084                    actually starts (below).
3085                 */
3086                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3087                 player1Rating = string_to_rating(star_match[1]);
3088                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3089                 player2Rating = string_to_rating(star_match[4]);
3090
3091                 if (appData.debugMode)
3092                   fprintf(debugFP, 
3093                           "Ratings from 'Creating:' %s %d, %s %d\n",
3094                           player1Name, player1Rating,
3095                           player2Name, player2Rating);
3096
3097                 continue;
3098             }
3099             
3100             /* Improved generic start/end-of-game messages */
3101             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3102                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3103                 /* If tkind == 0: */
3104                 /* star_match[0] is the game number */
3105                 /*           [1] is the white player's name */
3106                 /*           [2] is the black player's name */
3107                 /* For end-of-game: */
3108                 /*           [3] is the reason for the game end */
3109                 /*           [4] is a PGN end game-token, preceded by " " */
3110                 /* For start-of-game: */
3111                 /*           [3] begins with "Creating" or "Continuing" */
3112                 /*           [4] is " *" or empty (don't care). */
3113                 int gamenum = atoi(star_match[0]);
3114                 char *whitename, *blackname, *why, *endtoken;
3115                 ChessMove endtype = (ChessMove) 0;
3116
3117                 if (tkind == 0) {
3118                   whitename = star_match[1];
3119                   blackname = star_match[2];
3120                   why = star_match[3];
3121                   endtoken = star_match[4];
3122                 } else {
3123                   whitename = star_match[1];
3124                   blackname = star_match[3];
3125                   why = star_match[5];
3126                   endtoken = star_match[6];
3127                 }
3128
3129                 /* Game start messages */
3130                 if (strncmp(why, "Creating ", 9) == 0 ||
3131                     strncmp(why, "Continuing ", 11) == 0) {
3132                     gs_gamenum = gamenum;
3133                     strcpy(gs_kind, strchr(why, ' ') + 1);
3134 #if ZIPPY
3135                     if (appData.zippyPlay) {
3136                         ZippyGameStart(whitename, blackname);
3137                     }
3138 #endif /*ZIPPY*/
3139                     continue;
3140                 }
3141
3142                 /* Game end messages */
3143                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3144                     ics_gamenum != gamenum) {
3145                     continue;
3146                 }
3147                 while (endtoken[0] == ' ') endtoken++;
3148                 switch (endtoken[0]) {
3149                   case '*':
3150                   default:
3151                     endtype = GameUnfinished;
3152                     break;
3153                   case '0':
3154                     endtype = BlackWins;
3155                     break;
3156                   case '1':
3157                     if (endtoken[1] == '/')
3158                       endtype = GameIsDrawn;
3159                     else
3160                       endtype = WhiteWins;
3161                     break;
3162                 }
3163                 GameEnds(endtype, why, GE_ICS);
3164 #if ZIPPY
3165                 if (appData.zippyPlay && first.initDone) {
3166                     ZippyGameEnd(endtype, why);
3167                     if (first.pr == NULL) {
3168                       /* Start the next process early so that we'll
3169                          be ready for the next challenge */
3170                       StartChessProgram(&first);
3171                     }
3172                     /* Send "new" early, in case this command takes
3173                        a long time to finish, so that we'll be ready
3174                        for the next challenge. */
3175                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3176                     Reset(TRUE, TRUE);
3177                 }
3178 #endif /*ZIPPY*/
3179                 continue;
3180             }
3181
3182             if (looking_at(buf, &i, "Removing game * from observation") ||
3183                 looking_at(buf, &i, "no longer observing game *") ||
3184                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3185                 if (gameMode == IcsObserving &&
3186                     atoi(star_match[0]) == ics_gamenum)
3187                   {
3188                       /* icsEngineAnalyze */
3189                       if (appData.icsEngineAnalyze) {
3190                             ExitAnalyzeMode();
3191                             ModeHighlight();
3192                       }
3193                       StopClocks();
3194                       gameMode = IcsIdle;
3195                       ics_gamenum = -1;
3196                       ics_user_moved = FALSE;
3197                   }
3198                 continue;
3199             }
3200
3201             if (looking_at(buf, &i, "no longer examining game *")) {
3202                 if (gameMode == IcsExamining &&
3203                     atoi(star_match[0]) == ics_gamenum)
3204                   {
3205                       gameMode = IcsIdle;
3206                       ics_gamenum = -1;
3207                       ics_user_moved = FALSE;
3208                   }
3209                 continue;
3210             }
3211
3212             /* Advance leftover_start past any newlines we find,
3213                so only partial lines can get reparsed */
3214             if (looking_at(buf, &i, "\n")) {
3215                 prevColor = curColor;
3216                 if (curColor != ColorNormal) {
3217                     if (oldi > next_out) {
3218                         SendToPlayer(&buf[next_out], oldi - next_out);
3219                         next_out = oldi;
3220                     }
3221                     Colorize(ColorNormal, FALSE);
3222                     curColor = ColorNormal;
3223                 }
3224                 if (started == STARTED_BOARD) {
3225                     started = STARTED_NONE;
3226                     parse[parse_pos] = NULLCHAR;
3227                     ParseBoard12(parse);
3228                     ics_user_moved = 0;
3229
3230                     /* Send premove here */
3231                     if (appData.premove) {
3232                       char str[MSG_SIZ];
3233                       if (currentMove == 0 &&
3234                           gameMode == IcsPlayingWhite &&
3235                           appData.premoveWhite) {
3236                         sprintf(str, "%s%s\n", ics_prefix,
3237                                 appData.premoveWhiteText);
3238                         if (appData.debugMode)
3239                           fprintf(debugFP, "Sending premove:\n");
3240                         SendToICS(str);
3241                       } else if (currentMove == 1 &&
3242                                  gameMode == IcsPlayingBlack &&
3243                                  appData.premoveBlack) {
3244                         sprintf(str, "%s%s\n", ics_prefix,
3245                                 appData.premoveBlackText);
3246                         if (appData.debugMode)
3247                           fprintf(debugFP, "Sending premove:\n");
3248                         SendToICS(str);
3249                       } else if (gotPremove) {
3250                         gotPremove = 0;
3251                         ClearPremoveHighlights();
3252                         if (appData.debugMode)
3253                           fprintf(debugFP, "Sending premove:\n");
3254                           UserMoveEvent(premoveFromX, premoveFromY, 
3255                                         premoveToX, premoveToY, 
3256                                         premovePromoChar);
3257                       }
3258                     }
3259
3260                     /* Usually suppress following prompt */
3261                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3262                         if (looking_at(buf, &i, "*% ")) {
3263                             savingComment = FALSE;
3264                         }
3265                     }
3266                     next_out = i;
3267                 } else if (started == STARTED_HOLDINGS) {
3268                     int gamenum;
3269                     char new_piece[MSG_SIZ];
3270                     started = STARTED_NONE;
3271                     parse[parse_pos] = NULLCHAR;
3272                     if (appData.debugMode)
3273                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3274                                                         parse, currentMove);
3275                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3276                         gamenum == ics_gamenum) {
3277                         if (gameInfo.variant == VariantNormal) {
3278                           /* [HGM] We seem to switch variant during a game!
3279                            * Presumably no holdings were displayed, so we have
3280                            * to move the position two files to the right to
3281                            * create room for them!
3282                            */
3283                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3284                           /* Get a move list just to see the header, which
3285                              will tell us whether this is really bug or zh */
3286                           if (ics_getting_history == H_FALSE) {
3287                             ics_getting_history = H_REQUESTED;
3288                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3289                             SendToICS(str);
3290                           }
3291                         }
3292                         new_piece[0] = NULLCHAR;
3293                         sscanf(parse, "game %d white [%s black [%s <- %s",
3294                                &gamenum, white_holding, black_holding,
3295                                new_piece);
3296                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3297                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3298                         /* [HGM] copy holdings to board holdings area */
3299                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3300                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3301 #if ZIPPY
3302                         if (appData.zippyPlay && first.initDone) {
3303                             ZippyHoldings(white_holding, black_holding,
3304                                           new_piece);
3305                         }
3306 #endif /*ZIPPY*/
3307                         if (tinyLayout || smallLayout) {
3308                             char wh[16], bh[16];
3309                             PackHolding(wh, white_holding);
3310                             PackHolding(bh, black_holding);
3311                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3312                                     gameInfo.white, gameInfo.black);
3313                         } else {
3314                             sprintf(str, "%s [%s] vs. %s [%s]",
3315                                     gameInfo.white, white_holding,
3316                                     gameInfo.black, black_holding);
3317                         }
3318
3319                         DrawPosition(FALSE, boards[currentMove]);
3320                         DisplayTitle(str);
3321                     }
3322                     /* Suppress following prompt */
3323                     if (looking_at(buf, &i, "*% ")) {
3324                         savingComment = FALSE;
3325                     }
3326                     next_out = i;
3327                 }
3328                 continue;
3329             }
3330
3331             i++;                /* skip unparsed character and loop back */
3332         }
3333         
3334         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3335             started != STARTED_HOLDINGS && i > next_out) {
3336             SendToPlayer(&buf[next_out], i - next_out);
3337             next_out = i;
3338         }
3339         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3340         
3341         leftover_len = buf_len - leftover_start;
3342         /* if buffer ends with something we couldn't parse,
3343            reparse it after appending the next read */
3344         
3345     } else if (count == 0) {
3346         RemoveInputSource(isr);
3347         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3348     } else {
3349         DisplayFatalError(_("Error reading from ICS"), error, 1);
3350     }
3351 }
3352
3353
3354 /* Board style 12 looks like this:
3355    
3356    <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
3357    
3358  * The "<12> " is stripped before it gets to this routine.  The two
3359  * trailing 0's (flip state and clock ticking) are later addition, and
3360  * some chess servers may not have them, or may have only the first.
3361  * Additional trailing fields may be added in the future.  
3362  */
3363
3364 #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"
3365
3366 #define RELATION_OBSERVING_PLAYED    0
3367 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3368 #define RELATION_PLAYING_MYMOVE      1
3369 #define RELATION_PLAYING_NOTMYMOVE  -1
3370 #define RELATION_EXAMINING           2
3371 #define RELATION_ISOLATED_BOARD     -3
3372 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3373
3374 void
3375 ParseBoard12(string)
3376      char *string;
3377
3378     GameMode newGameMode;
3379     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3380     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3381     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3382     char to_play, board_chars[200];
3383     char move_str[500], str[500], elapsed_time[500];
3384     char black[32], white[32];
3385     Board board;
3386     int prevMove = currentMove;
3387     int ticking = 2;
3388     ChessMove moveType;
3389     int fromX, fromY, toX, toY;
3390     char promoChar;
3391     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3392     char *bookHit = NULL; // [HGM] book
3393
3394     fromX = fromY = toX = toY = -1;
3395     
3396     newGame = FALSE;
3397
3398     if (appData.debugMode)
3399       fprintf(debugFP, _("Parsing board: %s\n"), string);
3400
3401     move_str[0] = NULLCHAR;
3402     elapsed_time[0] = NULLCHAR;
3403     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3404         int  i = 0, j;
3405         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3406             if(string[i] == ' ') { ranks++; files = 0; }
3407             else files++;
3408             i++;
3409         }
3410         for(j = 0; j <i; j++) board_chars[j] = string[j];
3411         board_chars[i] = '\0';
3412         string += i + 1;
3413     }
3414     n = sscanf(string, PATTERN, &to_play, &double_push,
3415                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3416                &gamenum, white, black, &relation, &basetime, &increment,
3417                &white_stren, &black_stren, &white_time, &black_time,
3418                &moveNum, str, elapsed_time, move_str, &ics_flip,
3419                &ticking);
3420
3421     if (n < 21) {
3422         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3423         DisplayError(str, 0);
3424         return;
3425     }
3426
3427     /* Convert the move number to internal form */
3428     moveNum = (moveNum - 1) * 2;
3429     if (to_play == 'B') moveNum++;
3430     if (moveNum >= MAX_MOVES) {
3431       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3432                         0, 1);
3433       return;
3434     }
3435     
3436     switch (relation) {
3437       case RELATION_OBSERVING_PLAYED:
3438       case RELATION_OBSERVING_STATIC:
3439         if (gamenum == -1) {
3440             /* Old ICC buglet */
3441             relation = RELATION_OBSERVING_STATIC;
3442         }
3443         newGameMode = IcsObserving;
3444         break;
3445       case RELATION_PLAYING_MYMOVE:
3446       case RELATION_PLAYING_NOTMYMOVE:
3447         newGameMode =
3448           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3449             IcsPlayingWhite : IcsPlayingBlack;
3450         break;
3451       case RELATION_EXAMINING:
3452         newGameMode = IcsExamining;
3453         break;
3454       case RELATION_ISOLATED_BOARD:
3455       default:
3456         /* Just display this board.  If user was doing something else,
3457            we will forget about it until the next board comes. */ 
3458         newGameMode = IcsIdle;
3459         break;
3460       case RELATION_STARTING_POSITION:
3461         newGameMode = gameMode;
3462         break;
3463     }
3464     
3465     /* Modify behavior for initial board display on move listing
3466        of wild games.
3467        */
3468     switch (ics_getting_history) {
3469       case H_FALSE:
3470       case H_REQUESTED:
3471         break;
3472       case H_GOT_REQ_HEADER:
3473       case H_GOT_UNREQ_HEADER:
3474         /* This is the initial position of the current game */
3475         gamenum = ics_gamenum;
3476         moveNum = 0;            /* old ICS bug workaround */
3477         if (to_play == 'B') {
3478           startedFromSetupPosition = TRUE;
3479           blackPlaysFirst = TRUE;
3480           moveNum = 1;
3481           if (forwardMostMove == 0) forwardMostMove = 1;
3482           if (backwardMostMove == 0) backwardMostMove = 1;
3483           if (currentMove == 0) currentMove = 1;
3484         }
3485         newGameMode = gameMode;
3486         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3487         break;
3488       case H_GOT_UNWANTED_HEADER:
3489         /* This is an initial board that we don't want */
3490         return;
3491       case H_GETTING_MOVES:
3492         /* Should not happen */
3493         DisplayError(_("Error gathering move list: extra board"), 0);
3494         ics_getting_history = H_FALSE;
3495         return;
3496     }
3497     
3498     /* Take action if this is the first board of a new game, or of a
3499        different game than is currently being displayed.  */
3500     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3501         relation == RELATION_ISOLATED_BOARD) {
3502         
3503         /* Forget the old game and get the history (if any) of the new one */
3504         if (gameMode != BeginningOfGame) {
3505           Reset(FALSE, TRUE);
3506         }
3507         newGame = TRUE;
3508         if (appData.autoRaiseBoard) BoardToTop();
3509         prevMove = -3;
3510         if (gamenum == -1) {
3511             newGameMode = IcsIdle;
3512         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3513                    appData.getMoveList) {
3514             /* Need to get game history */
3515             ics_getting_history = H_REQUESTED;
3516             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3517             SendToICS(str);
3518         }
3519         
3520         /* Initially flip the board to have black on the bottom if playing
3521            black or if the ICS flip flag is set, but let the user change
3522            it with the Flip View button. */
3523         flipView = appData.autoFlipView ? 
3524           (newGameMode == IcsPlayingBlack) || ics_flip :
3525           appData.flipView;
3526         
3527         /* Done with values from previous mode; copy in new ones */
3528         gameMode = newGameMode;
3529         ModeHighlight();
3530         ics_gamenum = gamenum;
3531         if (gamenum == gs_gamenum) {
3532             int klen = strlen(gs_kind);
3533             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3534             sprintf(str, "ICS %s", gs_kind);
3535             gameInfo.event = StrSave(str);
3536         } else {
3537             gameInfo.event = StrSave("ICS game");
3538         }
3539         gameInfo.site = StrSave(appData.icsHost);
3540         gameInfo.date = PGNDate();
3541         gameInfo.round = StrSave("-");
3542         gameInfo.white = StrSave(white);
3543         gameInfo.black = StrSave(black);
3544         timeControl = basetime * 60 * 1000;
3545         timeControl_2 = 0;
3546         timeIncrement = increment * 1000;
3547         movesPerSession = 0;
3548         gameInfo.timeControl = TimeControlTagValue();
3549         VariantSwitch(board, StringToVariant(gameInfo.event) );
3550   if (appData.debugMode) {
3551     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3552     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3553     setbuf(debugFP, NULL);
3554   }
3555
3556         gameInfo.outOfBook = NULL;
3557         
3558         /* Do we have the ratings? */
3559         if (strcmp(player1Name, white) == 0 &&
3560             strcmp(player2Name, black) == 0) {
3561             if (appData.debugMode)
3562               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3563                       player1Rating, player2Rating);
3564             gameInfo.whiteRating = player1Rating;
3565             gameInfo.blackRating = player2Rating;
3566         } else if (strcmp(player2Name, white) == 0 &&
3567                    strcmp(player1Name, black) == 0) {
3568             if (appData.debugMode)
3569               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3570                       player2Rating, player1Rating);
3571             gameInfo.whiteRating = player2Rating;
3572             gameInfo.blackRating = player1Rating;
3573         }
3574         player1Name[0] = player2Name[0] = NULLCHAR;
3575
3576         /* Silence shouts if requested */
3577         if (appData.quietPlay &&
3578             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3579             SendToICS(ics_prefix);
3580             SendToICS("set shout 0\n");
3581         }
3582     }
3583     
3584     /* Deal with midgame name changes */
3585     if (!newGame) {
3586         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3587             if (gameInfo.white) free(gameInfo.white);
3588             gameInfo.white = StrSave(white);
3589         }
3590         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3591             if (gameInfo.black) free(gameInfo.black);
3592             gameInfo.black = StrSave(black);
3593         }
3594     }
3595     
3596     /* Throw away game result if anything actually changes in examine mode */
3597     if (gameMode == IcsExamining && !newGame) {
3598         gameInfo.result = GameUnfinished;
3599         if (gameInfo.resultDetails != NULL) {
3600             free(gameInfo.resultDetails);
3601             gameInfo.resultDetails = NULL;
3602         }
3603     }
3604     
3605     /* In pausing && IcsExamining mode, we ignore boards coming
3606        in if they are in a different variation than we are. */
3607     if (pauseExamInvalid) return;
3608     if (pausing && gameMode == IcsExamining) {
3609         if (moveNum <= pauseExamForwardMostMove) {
3610             pauseExamInvalid = TRUE;
3611             forwardMostMove = pauseExamForwardMostMove;
3612             return;
3613         }
3614     }
3615     
3616   if (appData.debugMode) {
3617     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3618   }
3619     /* Parse the board */
3620     for (k = 0; k < ranks; k++) {
3621       for (j = 0; j < files; j++)
3622         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3623       if(gameInfo.holdingsWidth > 1) {
3624            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3625            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3626       }
3627     }
3628     CopyBoard(boards[moveNum], board);
3629     if (moveNum == 0) {
3630         startedFromSetupPosition =
3631           !CompareBoards(board, initialPosition);
3632         if(startedFromSetupPosition)
3633             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3634     }
3635
3636     /* [HGM] Set castling rights. Take the outermost Rooks,
3637        to make it also work for FRC opening positions. Note that board12
3638        is really defective for later FRC positions, as it has no way to
3639        indicate which Rook can castle if they are on the same side of King.
3640        For the initial position we grant rights to the outermost Rooks,
3641        and remember thos rights, and we then copy them on positions
3642        later in an FRC game. This means WB might not recognize castlings with
3643        Rooks that have moved back to their original position as illegal,
3644        but in ICS mode that is not its job anyway.
3645     */
3646     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3647     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3648
3649         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3650             if(board[0][i] == WhiteRook) j = i;
3651         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3652         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3653             if(board[0][i] == WhiteRook) j = i;
3654         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3655         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3656             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3657         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3658         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3659             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3660         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3661
3662         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3663         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3664             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3665         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3666             if(board[BOARD_HEIGHT-1][k] == bKing)
3667                 initialRights[5] = castlingRights[moveNum][5] = k;
3668     } else { int r;
3669         r = castlingRights[moveNum][0] = initialRights[0];
3670         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3671         r = castlingRights[moveNum][1] = initialRights[1];
3672         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3673         r = castlingRights[moveNum][3] = initialRights[3];
3674         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3675         r = castlingRights[moveNum][4] = initialRights[4];
3676         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3677         /* wildcastle kludge: always assume King has rights */
3678         r = castlingRights[moveNum][2] = initialRights[2];
3679         r = castlingRights[moveNum][5] = initialRights[5];
3680     }
3681     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3682     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3683
3684     
3685     if (ics_getting_history == H_GOT_REQ_HEADER ||
3686         ics_getting_history == H_GOT_UNREQ_HEADER) {
3687         /* This was an initial position from a move list, not
3688            the current position */
3689         return;
3690     }
3691     
3692     /* Update currentMove and known move number limits */
3693     newMove = newGame || moveNum > forwardMostMove;
3694
3695     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3696     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3697         takeback = forwardMostMove - moveNum;
3698         for (i = 0; i < takeback; i++) {
3699              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3700              SendToProgram("undo\n", &first);
3701         }
3702     }
3703
3704     if (newGame) {
3705         forwardMostMove = backwardMostMove = currentMove = moveNum;
3706         if (gameMode == IcsExamining && moveNum == 0) {
3707           /* Workaround for ICS limitation: we are not told the wild
3708              type when starting to examine a game.  But if we ask for
3709              the move list, the move list header will tell us */
3710             ics_getting_history = H_REQUESTED;
3711             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3712             SendToICS(str);
3713         }
3714     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3715                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3716         forwardMostMove = moveNum;
3717         if (!pausing || currentMove > forwardMostMove)
3718           currentMove = forwardMostMove;
3719     } else {
3720         /* New part of history that is not contiguous with old part */ 
3721         if (pausing && gameMode == IcsExamining) {
3722             pauseExamInvalid = TRUE;
3723             forwardMostMove = pauseExamForwardMostMove;
3724             return;
3725         }
3726         forwardMostMove = backwardMostMove = currentMove = moveNum;
3727         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3728             ics_getting_history = H_REQUESTED;
3729             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3730             SendToICS(str);
3731         }
3732     }
3733     
3734     /* Update the clocks */
3735     if (strchr(elapsed_time, '.')) {
3736       /* Time is in ms */
3737       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3738       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3739     } else {
3740       /* Time is in seconds */
3741       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3742       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3743     }
3744       
3745
3746 #if ZIPPY
3747     if (appData.zippyPlay && newGame &&
3748         gameMode != IcsObserving && gameMode != IcsIdle &&
3749         gameMode != IcsExamining)
3750       ZippyFirstBoard(moveNum, basetime, increment);
3751 #endif
3752     
3753     /* Put the move on the move list, first converting
3754        to canonical algebraic form. */
3755     if (moveNum > 0) {
3756   if (appData.debugMode) {
3757     if (appData.debugMode) { int f = forwardMostMove;
3758         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3759                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3760     }
3761     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3762     fprintf(debugFP, "moveNum = %d\n", moveNum);
3763     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3764     setbuf(debugFP, NULL);
3765   }
3766         if (moveNum <= backwardMostMove) {
3767             /* We don't know what the board looked like before
3768                this move.  Punt. */
3769             strcpy(parseList[moveNum - 1], move_str);
3770             strcat(parseList[moveNum - 1], " ");
3771             strcat(parseList[moveNum - 1], elapsed_time);
3772             moveList[moveNum - 1][0] = NULLCHAR;
3773         } else if (strcmp(move_str, "none") == 0) {
3774             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3775             /* Again, we don't know what the board looked like;
3776                this is really the start of the game. */
3777             parseList[moveNum - 1][0] = NULLCHAR;
3778             moveList[moveNum - 1][0] = NULLCHAR;
3779             backwardMostMove = moveNum;
3780             startedFromSetupPosition = TRUE;
3781             fromX = fromY = toX = toY = -1;
3782         } else {
3783           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3784           //                 So we parse the long-algebraic move string in stead of the SAN move
3785           int valid; char buf[MSG_SIZ], *prom;
3786
3787           // str looks something like "Q/a1-a2"; kill the slash
3788           if(str[1] == '/') 
3789                 sprintf(buf, "%c%s", str[0], str+2);
3790           else  strcpy(buf, str); // might be castling
3791           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3792                 strcat(buf, prom); // long move lacks promo specification!
3793           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3794                 if(appData.debugMode) 
3795                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3796                 strcpy(move_str, buf);
3797           }
3798           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3799                                 &fromX, &fromY, &toX, &toY, &promoChar)
3800                || ParseOneMove(buf, moveNum - 1, &moveType,
3801                                 &fromX, &fromY, &toX, &toY, &promoChar);
3802           // end of long SAN patch
3803           if (valid) {
3804             (void) CoordsToAlgebraic(boards[moveNum - 1],
3805                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3806                                      fromY, fromX, toY, toX, promoChar,
3807                                      parseList[moveNum-1]);
3808             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3809                              castlingRights[moveNum]) ) {
3810               case MT_NONE:
3811               case MT_STALEMATE:
3812               default:
3813                 break;
3814               case MT_CHECK:
3815                 if(gameInfo.variant != VariantShogi)
3816                     strcat(parseList[moveNum - 1], "+");
3817                 break;
3818               case MT_CHECKMATE:
3819               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3820                 strcat(parseList[moveNum - 1], "#");
3821                 break;
3822             }
3823             strcat(parseList[moveNum - 1], " ");
3824             strcat(parseList[moveNum - 1], elapsed_time);
3825             /* currentMoveString is set as a side-effect of ParseOneMove */
3826             strcpy(moveList[moveNum - 1], currentMoveString);
3827             strcat(moveList[moveNum - 1], "\n");
3828           } else {
3829             /* Move from ICS was illegal!?  Punt. */
3830   if (appData.debugMode) {
3831     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3832     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3833   }
3834             strcpy(parseList[moveNum - 1], move_str);
3835             strcat(parseList[moveNum - 1], " ");
3836             strcat(parseList[moveNum - 1], elapsed_time);
3837             moveList[moveNum - 1][0] = NULLCHAR;
3838             fromX = fromY = toX = toY = -1;
3839           }
3840         }
3841   if (appData.debugMode) {
3842     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3843     setbuf(debugFP, NULL);
3844   }
3845
3846 #if ZIPPY
3847         /* Send move to chess program (BEFORE animating it). */
3848         if (appData.zippyPlay && !newGame && newMove && 
3849            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3850
3851             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3852                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3853                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3854                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3855                             move_str);
3856                     DisplayError(str, 0);
3857                 } else {
3858                     if (first.sendTime) {
3859                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3860                     }
3861                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3862                     if (firstMove && !bookHit) {
3863                         firstMove = FALSE;
3864                         if (first.useColors) {
3865                           SendToProgram(gameMode == IcsPlayingWhite ?
3866                                         "white\ngo\n" :
3867                                         "black\ngo\n", &first);
3868                         } else {
3869                           SendToProgram("go\n", &first);
3870                         }
3871                         first.maybeThinking = TRUE;
3872                     }
3873                 }
3874             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3875               if (moveList[moveNum - 1][0] == NULLCHAR) {
3876                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3877                 DisplayError(str, 0);
3878               } else {
3879                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3880                 SendMoveToProgram(moveNum - 1, &first);
3881               }
3882             }
3883         }
3884 #endif
3885     }
3886
3887     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3888         /* If move comes from a remote source, animate it.  If it
3889            isn't remote, it will have already been animated. */
3890         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3891             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3892         }
3893         if (!pausing && appData.highlightLastMove) {
3894             SetHighlights(fromX, fromY, toX, toY);
3895         }
3896     }
3897     
3898     /* Start the clocks */
3899     whiteFlag = blackFlag = FALSE;
3900     appData.clockMode = !(basetime == 0 && increment == 0);
3901     if (ticking == 0) {
3902       ics_clock_paused = TRUE;
3903       StopClocks();
3904     } else if (ticking == 1) {
3905       ics_clock_paused = FALSE;
3906     }
3907     if (gameMode == IcsIdle ||
3908         relation == RELATION_OBSERVING_STATIC ||
3909         relation == RELATION_EXAMINING ||
3910         ics_clock_paused)
3911       DisplayBothClocks();
3912     else
3913       StartClocks();
3914     
3915     /* Display opponents and material strengths */
3916     if (gameInfo.variant != VariantBughouse &&
3917         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3918         if (tinyLayout || smallLayout) {
3919             if(gameInfo.variant == VariantNormal)
3920                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3921                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3922                     basetime, increment);
3923             else
3924                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3925                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3926                     basetime, increment, (int) gameInfo.variant);
3927         } else {
3928             if(gameInfo.variant == VariantNormal)
3929                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3930                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3931                     basetime, increment);
3932             else
3933                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3934                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3935                     basetime, increment, VariantName(gameInfo.variant));
3936         }
3937         DisplayTitle(str);
3938   if (appData.debugMode) {
3939     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3940   }
3941     }
3942
3943    
3944     /* Display the board */
3945     if (!pausing && !appData.noGUI) {
3946       
3947       if (appData.premove)
3948           if (!gotPremove || 
3949              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3950              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3951               ClearPremoveHighlights();
3952
3953       DrawPosition(FALSE, boards[currentMove]);
3954       DisplayMove(moveNum - 1);
3955       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3956             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3957               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3958         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3959       }
3960     }
3961
3962     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3963 #if ZIPPY
3964     if(bookHit) { // [HGM] book: simulate book reply
3965         static char bookMove[MSG_SIZ]; // a bit generous?
3966
3967         programStats.nodes = programStats.depth = programStats.time = 
3968         programStats.score = programStats.got_only_move = 0;
3969         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3970
3971         strcpy(bookMove, "move ");
3972         strcat(bookMove, bookHit);
3973         HandleMachineMove(bookMove, &first);
3974     }
3975 #endif
3976 }
3977
3978 void
3979 GetMoveListEvent()
3980 {
3981     char buf[MSG_SIZ];
3982     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3983         ics_getting_history = H_REQUESTED;
3984         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3985         SendToICS(buf);
3986     }
3987 }
3988
3989 void
3990 AnalysisPeriodicEvent(force)
3991      int force;
3992 {
3993     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3994          && !force) || !appData.periodicUpdates)
3995       return;
3996
3997     /* Send . command to Crafty to collect stats */
3998     SendToProgram(".\n", &first);
3999
4000     /* Don't send another until we get a response (this makes
4001        us stop sending to old Crafty's which don't understand
4002        the "." command (sending illegal cmds resets node count & time,
4003        which looks bad)) */
4004     programStats.ok_to_send = 0;
4005 }
4006
4007 void ics_update_width(new_width)
4008         int new_width;
4009 {
4010         ics_printf("set width %d\n", new_width);
4011 }
4012
4013 void
4014 SendMoveToProgram(moveNum, cps)
4015      int moveNum;
4016      ChessProgramState *cps;
4017 {
4018     char buf[MSG_SIZ];
4019
4020     if (cps->useUsermove) {
4021       SendToProgram("usermove ", cps);
4022     }
4023     if (cps->useSAN) {
4024       char *space;
4025       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4026         int len = space - parseList[moveNum];
4027         memcpy(buf, parseList[moveNum], len);
4028         buf[len++] = '\n';
4029         buf[len] = NULLCHAR;
4030       } else {
4031         sprintf(buf, "%s\n", parseList[moveNum]);
4032       }
4033       SendToProgram(buf, cps);
4034     } else {
4035       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4036         AlphaRank(moveList[moveNum], 4);
4037         SendToProgram(moveList[moveNum], cps);
4038         AlphaRank(moveList[moveNum], 4); // and back
4039       } else
4040       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4041        * the engine. It would be nice to have a better way to identify castle 
4042        * moves here. */
4043       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4044                                                                          && cps->useOOCastle) {
4045         int fromX = moveList[moveNum][0] - AAA; 
4046         int fromY = moveList[moveNum][1] - ONE;
4047         int toX = moveList[moveNum][2] - AAA; 
4048         int toY = moveList[moveNum][3] - ONE;
4049         if((boards[moveNum][fromY][fromX] == WhiteKing 
4050             && boards[moveNum][toY][toX] == WhiteRook)
4051            || (boards[moveNum][fromY][fromX] == BlackKing 
4052                && boards[moveNum][toY][toX] == BlackRook)) {
4053           if(toX > fromX) SendToProgram("O-O\n", cps);
4054           else SendToProgram("O-O-O\n", cps);
4055         }
4056         else SendToProgram(moveList[moveNum], cps);
4057       }
4058       else SendToProgram(moveList[moveNum], cps);
4059       /* End of additions by Tord */
4060     }
4061
4062     /* [HGM] setting up the opening has brought engine in force mode! */
4063     /*       Send 'go' if we are in a mode where machine should play. */
4064     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4065         (gameMode == TwoMachinesPlay   ||
4066 #ifdef ZIPPY
4067          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4068 #endif
4069          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4070         SendToProgram("go\n", cps);
4071   if (appData.debugMode) {
4072     fprintf(debugFP, "(extra)\n");
4073   }
4074     }
4075     setboardSpoiledMachineBlack = 0;
4076 }
4077
4078 void
4079 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4080      ChessMove moveType;
4081      int fromX, fromY, toX, toY;
4082 {
4083     char user_move[MSG_SIZ];
4084
4085     switch (moveType) {
4086       default:
4087         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4088                 (int)moveType, fromX, fromY, toX, toY);
4089         DisplayError(user_move + strlen("say "), 0);
4090         break;
4091       case WhiteKingSideCastle:
4092       case BlackKingSideCastle:
4093       case WhiteQueenSideCastleWild:
4094       case BlackQueenSideCastleWild:
4095       /* PUSH Fabien */
4096       case WhiteHSideCastleFR:
4097       case BlackHSideCastleFR:
4098       /* POP Fabien */
4099         sprintf(user_move, "o-o\n");
4100         break;
4101       case WhiteQueenSideCastle:
4102       case BlackQueenSideCastle:
4103       case WhiteKingSideCastleWild:
4104       case BlackKingSideCastleWild:
4105       /* PUSH Fabien */
4106       case WhiteASideCastleFR:
4107       case BlackASideCastleFR:
4108       /* POP Fabien */
4109         sprintf(user_move, "o-o-o\n");
4110         break;
4111       case WhitePromotionQueen:
4112       case BlackPromotionQueen:
4113       case WhitePromotionRook:
4114       case BlackPromotionRook:
4115       case WhitePromotionBishop:
4116       case BlackPromotionBishop:
4117       case WhitePromotionKnight:
4118       case BlackPromotionKnight:
4119       case WhitePromotionKing:
4120       case BlackPromotionKing:
4121       case WhitePromotionChancellor:
4122       case BlackPromotionChancellor:
4123       case WhitePromotionArchbishop:
4124       case BlackPromotionArchbishop:
4125         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4126             sprintf(user_move, "%c%c%c%c=%c\n",
4127                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4128                 PieceToChar(WhiteFerz));
4129         else if(gameInfo.variant == VariantGreat)
4130             sprintf(user_move, "%c%c%c%c=%c\n",
4131                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4132                 PieceToChar(WhiteMan));
4133         else
4134             sprintf(user_move, "%c%c%c%c=%c\n",
4135                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4136                 PieceToChar(PromoPiece(moveType)));
4137         break;
4138       case WhiteDrop:
4139       case BlackDrop:
4140         sprintf(user_move, "%c@%c%c\n",
4141                 ToUpper(PieceToChar((ChessSquare) fromX)),
4142                 AAA + toX, ONE + toY);
4143         break;
4144       case NormalMove:
4145       case WhiteCapturesEnPassant:
4146       case BlackCapturesEnPassant:
4147       case IllegalMove:  /* could be a variant we don't quite understand */
4148         sprintf(user_move, "%c%c%c%c\n",
4149                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4150         break;
4151     }
4152     SendToICS(user_move);
4153     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4154         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4155 }
4156
4157 void
4158 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4159      int rf, ff, rt, ft;
4160      char promoChar;
4161      char move[7];
4162 {
4163     if (rf == DROP_RANK) {
4164         sprintf(move, "%c@%c%c\n",
4165                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4166     } else {
4167         if (promoChar == 'x' || promoChar == NULLCHAR) {
4168             sprintf(move, "%c%c%c%c\n",
4169                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4170         } else {
4171             sprintf(move, "%c%c%c%c%c\n",
4172                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4173         }
4174     }
4175 }
4176
4177 void
4178 ProcessICSInitScript(f)
4179      FILE *f;
4180 {
4181     char buf[MSG_SIZ];
4182
4183     while (fgets(buf, MSG_SIZ, f)) {
4184         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4185     }
4186
4187     fclose(f);
4188 }
4189
4190
4191 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4192 void
4193 AlphaRank(char *move, int n)
4194 {
4195 //    char *p = move, c; int x, y;
4196
4197     if (appData.debugMode) {
4198         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4199     }
4200
4201     if(move[1]=='*' && 
4202        move[2]>='0' && move[2]<='9' &&
4203        move[3]>='a' && move[3]<='x'    ) {
4204         move[1] = '@';
4205         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4206         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4207     } else
4208     if(move[0]>='0' && move[0]<='9' &&
4209        move[1]>='a' && move[1]<='x' &&
4210        move[2]>='0' && move[2]<='9' &&
4211        move[3]>='a' && move[3]<='x'    ) {
4212         /* input move, Shogi -> normal */
4213         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4214         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4215         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4216         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4217     } else
4218     if(move[1]=='@' &&
4219        move[3]>='0' && move[3]<='9' &&
4220        move[2]>='a' && move[2]<='x'    ) {
4221         move[1] = '*';
4222         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4223         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4224     } else
4225     if(
4226        move[0]>='a' && move[0]<='x' &&
4227        move[3]>='0' && move[3]<='9' &&
4228        move[2]>='a' && move[2]<='x'    ) {
4229          /* output move, normal -> Shogi */
4230         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4231         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4232         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4233         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4234         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4235     }
4236     if (appData.debugMode) {
4237         fprintf(debugFP, "   out = '%s'\n", move);
4238     }
4239 }
4240
4241 /* Parser for moves from gnuchess, ICS, or user typein box */
4242 Boolean
4243 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4244      char *move;
4245      int moveNum;
4246      ChessMove *moveType;
4247      int *fromX, *fromY, *toX, *toY;
4248      char *promoChar;
4249 {       
4250     if (appData.debugMode) {
4251         fprintf(debugFP, "move to parse: %s\n", move);
4252     }
4253     *moveType = yylexstr(moveNum, move);
4254
4255     switch (*moveType) {
4256       case WhitePromotionChancellor:
4257       case BlackPromotionChancellor:
4258       case WhitePromotionArchbishop:
4259       case BlackPromotionArchbishop:
4260       case WhitePromotionQueen:
4261       case BlackPromotionQueen:
4262       case WhitePromotionRook:
4263       case BlackPromotionRook:
4264       case WhitePromotionBishop:
4265       case BlackPromotionBishop:
4266       case WhitePromotionKnight:
4267       case BlackPromotionKnight:
4268       case WhitePromotionKing:
4269       case BlackPromotionKing:
4270       case NormalMove:
4271       case WhiteCapturesEnPassant:
4272       case BlackCapturesEnPassant:
4273       case WhiteKingSideCastle:
4274       case WhiteQueenSideCastle:
4275       case BlackKingSideCastle:
4276       case BlackQueenSideCastle:
4277       case WhiteKingSideCastleWild:
4278       case WhiteQueenSideCastleWild:
4279       case BlackKingSideCastleWild:
4280       case BlackQueenSideCastleWild:
4281       /* Code added by Tord: */
4282       case WhiteHSideCastleFR:
4283       case WhiteASideCastleFR:
4284       case BlackHSideCastleFR:
4285       case BlackASideCastleFR:
4286       /* End of code added by Tord */
4287       case IllegalMove:         /* bug or odd chess variant */
4288         *fromX = currentMoveString[0] - AAA;
4289         *fromY = currentMoveString[1] - ONE;
4290         *toX = currentMoveString[2] - AAA;
4291         *toY = currentMoveString[3] - ONE;
4292         *promoChar = currentMoveString[4];
4293         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4294             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4295     if (appData.debugMode) {
4296         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4297     }
4298             *fromX = *fromY = *toX = *toY = 0;
4299             return FALSE;
4300         }
4301         if (appData.testLegality) {
4302           return (*moveType != IllegalMove);
4303         } else {
4304           return !(fromX == fromY && toX == toY);
4305         }
4306
4307       case WhiteDrop:
4308       case BlackDrop:
4309         *fromX = *moveType == WhiteDrop ?
4310           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4311           (int) CharToPiece(ToLower(currentMoveString[0]));
4312         *fromY = DROP_RANK;
4313         *toX = currentMoveString[2] - AAA;
4314         *toY = currentMoveString[3] - ONE;
4315         *promoChar = NULLCHAR;
4316         return TRUE;
4317
4318       case AmbiguousMove:
4319       case ImpossibleMove:
4320       case (ChessMove) 0:       /* end of file */
4321       case ElapsedTime:
4322       case Comment:
4323       case PGNTag:
4324       case NAG:
4325       case WhiteWins:
4326       case BlackWins:
4327       case GameIsDrawn:
4328       default:
4329     if (appData.debugMode) {
4330         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4331     }
4332         /* bug? */
4333         *fromX = *fromY = *toX = *toY = 0;
4334         *promoChar = NULLCHAR;
4335         return FALSE;
4336     }
4337 }
4338
4339 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4340 // All positions will have equal probability, but the current method will not provide a unique
4341 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4342 #define DARK 1
4343 #define LITE 2
4344 #define ANY 3
4345
4346 int squaresLeft[4];
4347 int piecesLeft[(int)BlackPawn];
4348 int seed, nrOfShuffles;
4349
4350 void GetPositionNumber()
4351 {       // sets global variable seed
4352         int i;
4353
4354         seed = appData.defaultFrcPosition;
4355         if(seed < 0) { // randomize based on time for negative FRC position numbers
4356                 for(i=0; i<50; i++) seed += random();
4357                 seed = random() ^ random() >> 8 ^ random() << 8;
4358                 if(seed<0) seed = -seed;
4359         }
4360 }
4361
4362 int put(Board board, int pieceType, int rank, int n, int shade)
4363 // put the piece on the (n-1)-th empty squares of the given shade
4364 {
4365         int i;
4366
4367         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4368                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4369                         board[rank][i] = (ChessSquare) pieceType;
4370                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4371                         squaresLeft[ANY]--;
4372                         piecesLeft[pieceType]--; 
4373                         return i;
4374                 }
4375         }
4376         return -1;
4377 }
4378
4379
4380 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4381 // calculate where the next piece goes, (any empty square), and put it there
4382 {
4383         int i;
4384
4385         i = seed % squaresLeft[shade];
4386         nrOfShuffles *= squaresLeft[shade];
4387         seed /= squaresLeft[shade];
4388         put(board, pieceType, rank, i, shade);
4389 }
4390
4391 void AddTwoPieces(Board board, int pieceType, int rank)
4392 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4393 {
4394         int i, n=squaresLeft[ANY], j=n-1, k;
4395
4396         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4397         i = seed % k;  // pick one
4398         nrOfShuffles *= k;
4399         seed /= k;
4400         while(i >= j) i -= j--;
4401         j = n - 1 - j; i += j;
4402         put(board, pieceType, rank, j, ANY);
4403         put(board, pieceType, rank, i, ANY);
4404 }
4405
4406 void SetUpShuffle(Board board, int number)
4407 {
4408         int i, p, first=1;
4409
4410         GetPositionNumber(); nrOfShuffles = 1;
4411
4412         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4413         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4414         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4415
4416         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4417
4418         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4419             p = (int) board[0][i];
4420             if(p < (int) BlackPawn) piecesLeft[p] ++;
4421             board[0][i] = EmptySquare;
4422         }
4423
4424         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4425             // shuffles restricted to allow normal castling put KRR first
4426             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4427                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4428             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4429                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4430             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4431                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4432             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4433                 put(board, WhiteRook, 0, 0, ANY);
4434             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4435         }
4436
4437         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4438             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4439             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4440                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4441                 while(piecesLeft[p] >= 2) {
4442                     AddOnePiece(board, p, 0, LITE);
4443                     AddOnePiece(board, p, 0, DARK);
4444                 }
4445                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4446             }
4447
4448         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4449             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4450             // but we leave King and Rooks for last, to possibly obey FRC restriction
4451             if(p == (int)WhiteRook) continue;
4452             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4453             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4454         }
4455
4456         // now everything is placed, except perhaps King (Unicorn) and Rooks
4457
4458         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4459             // Last King gets castling rights
4460             while(piecesLeft[(int)WhiteUnicorn]) {
4461                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4462                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4463             }
4464
4465             while(piecesLeft[(int)WhiteKing]) {
4466                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4467                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4468             }
4469
4470
4471         } else {
4472             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4473             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4474         }
4475
4476         // Only Rooks can be left; simply place them all
4477         while(piecesLeft[(int)WhiteRook]) {
4478                 i = put(board, WhiteRook, 0, 0, ANY);
4479                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4480                         if(first) {
4481                                 first=0;
4482                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4483                         }
4484                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4485                 }
4486         }
4487         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4488             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4489         }
4490
4491         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4492 }
4493
4494 int SetCharTable( char *table, const char * map )
4495 /* [HGM] moved here from winboard.c because of its general usefulness */
4496 /*       Basically a safe strcpy that uses the last character as King */
4497 {
4498     int result = FALSE; int NrPieces;
4499
4500     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4501                     && NrPieces >= 12 && !(NrPieces&1)) {
4502         int i; /* [HGM] Accept even length from 12 to 34 */
4503
4504         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4505         for( i=0; i<NrPieces/2-1; i++ ) {
4506             table[i] = map[i];
4507             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4508         }
4509         table[(int) WhiteKing]  = map[NrPieces/2-1];
4510         table[(int) BlackKing]  = map[NrPieces-1];
4511
4512         result = TRUE;
4513     }
4514
4515     return result;
4516 }
4517
4518 void Prelude(Board board)
4519 {       // [HGM] superchess: random selection of exo-pieces
4520         int i, j, k; ChessSquare p; 
4521         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4522
4523         GetPositionNumber(); // use FRC position number
4524
4525         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4526             SetCharTable(pieceToChar, appData.pieceToCharTable);
4527             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4528                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4529         }
4530
4531         j = seed%4;                 seed /= 4; 
4532         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4533         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4534         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4535         j = seed%3 + (seed%3 >= j); seed /= 3; 
4536         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4537         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4538         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4539         j = seed%3;                 seed /= 3; 
4540         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4541         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4542         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4543         j = seed%2 + (seed%2 >= j); seed /= 2; 
4544         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4545         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4546         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4547         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4548         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4549         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4550         put(board, exoPieces[0],    0, 0, ANY);
4551         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4552 }
4553
4554 void
4555 InitPosition(redraw)
4556      int redraw;
4557 {
4558     ChessSquare (* pieces)[BOARD_SIZE];
4559     int i, j, pawnRow, overrule,
4560     oldx = gameInfo.boardWidth,
4561     oldy = gameInfo.boardHeight,
4562     oldh = gameInfo.holdingsWidth,
4563     oldv = gameInfo.variant;
4564
4565     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4566
4567     /* [AS] Initialize pv info list [HGM] and game status */
4568     {
4569         for( i=0; i<MAX_MOVES; i++ ) {
4570             pvInfoList[i].depth = 0;
4571             epStatus[i]=EP_NONE;
4572             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4573         }
4574
4575         initialRulePlies = 0; /* 50-move counter start */
4576
4577         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4578         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4579     }
4580
4581     
4582     /* [HGM] logic here is completely changed. In stead of full positions */
4583     /* the initialized data only consist of the two backranks. The switch */
4584     /* selects which one we will use, which is than copied to the Board   */
4585     /* initialPosition, which for the rest is initialized by Pawns and    */
4586     /* empty squares. This initial position is then copied to boards[0],  */
4587     /* possibly after shuffling, so that it remains available.            */
4588
4589     gameInfo.holdingsWidth = 0; /* default board sizes */
4590     gameInfo.boardWidth    = 8;
4591     gameInfo.boardHeight   = 8;
4592     gameInfo.holdingsSize  = 0;
4593     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4594     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4595     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4596
4597     switch (gameInfo.variant) {
4598     case VariantFischeRandom:
4599       shuffleOpenings = TRUE;
4600     default:
4601       pieces = FIDEArray;
4602       break;
4603     case VariantShatranj:
4604       pieces = ShatranjArray;
4605       nrCastlingRights = 0;
4606       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4607       break;
4608     case VariantTwoKings:
4609       pieces = twoKingsArray;
4610       break;
4611     case VariantCapaRandom:
4612       shuffleOpenings = TRUE;
4613     case VariantCapablanca:
4614       pieces = CapablancaArray;
4615       gameInfo.boardWidth = 10;
4616       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4617       break;
4618     case VariantGothic:
4619       pieces = GothicArray;
4620       gameInfo.boardWidth = 10;
4621       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4622       break;
4623     case VariantJanus:
4624       pieces = JanusArray;
4625       gameInfo.boardWidth = 10;
4626       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4627       nrCastlingRights = 6;
4628         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4629         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4630         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4631         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4632         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4633         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4634       break;
4635     case VariantFalcon:
4636       pieces = FalconArray;
4637       gameInfo.boardWidth = 10;
4638       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4639       break;
4640     case VariantXiangqi:
4641       pieces = XiangqiArray;
4642       gameInfo.boardWidth  = 9;
4643       gameInfo.boardHeight = 10;
4644       nrCastlingRights = 0;
4645       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4646       break;
4647     case VariantShogi:
4648       pieces = ShogiArray;
4649       gameInfo.boardWidth  = 9;
4650       gameInfo.boardHeight = 9;
4651       gameInfo.holdingsSize = 7;
4652       nrCastlingRights = 0;
4653       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4654       break;
4655     case VariantCourier:
4656       pieces = CourierArray;
4657       gameInfo.boardWidth  = 12;
4658       nrCastlingRights = 0;
4659       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4660       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4661       break;
4662     case VariantKnightmate:
4663       pieces = KnightmateArray;
4664       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4665       break;
4666     case VariantFairy:
4667       pieces = fairyArray;
4668       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4669       break;
4670     case VariantGreat:
4671       pieces = GreatArray;
4672       gameInfo.boardWidth = 10;
4673       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4674       gameInfo.holdingsSize = 8;
4675       break;
4676     case VariantSuper:
4677       pieces = FIDEArray;
4678       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4679       gameInfo.holdingsSize = 8;
4680       startedFromSetupPosition = TRUE;
4681       break;
4682     case VariantCrazyhouse:
4683     case VariantBughouse:
4684       pieces = FIDEArray;
4685       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4686       gameInfo.holdingsSize = 5;
4687       break;
4688     case VariantWildCastle:
4689       pieces = FIDEArray;
4690       /* !!?shuffle with kings guaranteed to be on d or e file */
4691       shuffleOpenings = 1;
4692       break;
4693     case VariantNoCastle:
4694       pieces = FIDEArray;
4695       nrCastlingRights = 0;
4696       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4697       /* !!?unconstrained back-rank shuffle */
4698       shuffleOpenings = 1;
4699       break;
4700     }
4701
4702     overrule = 0;
4703     if(appData.NrFiles >= 0) {
4704         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4705         gameInfo.boardWidth = appData.NrFiles;
4706     }
4707     if(appData.NrRanks >= 0) {
4708         gameInfo.boardHeight = appData.NrRanks;
4709     }
4710     if(appData.holdingsSize >= 0) {
4711         i = appData.holdingsSize;
4712         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4713         gameInfo.holdingsSize = i;
4714     }
4715     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4716     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4717         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4718
4719     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4720     if(pawnRow < 1) pawnRow = 1;
4721
4722     /* User pieceToChar list overrules defaults */
4723     if(appData.pieceToCharTable != NULL)
4724         SetCharTable(pieceToChar, appData.pieceToCharTable);
4725
4726     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4727
4728         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4729             s = (ChessSquare) 0; /* account holding counts in guard band */
4730         for( i=0; i<BOARD_HEIGHT; i++ )
4731             initialPosition[i][j] = s;
4732
4733         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4734         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4735         initialPosition[pawnRow][j] = WhitePawn;
4736         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4737         if(gameInfo.variant == VariantXiangqi) {
4738             if(j&1) {
4739                 initialPosition[pawnRow][j] = 
4740                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4741                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4742                    initialPosition[2][j] = WhiteCannon;
4743                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4744                 }
4745             }
4746         }
4747         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4748     }
4749     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4750
4751             j=BOARD_LEFT+1;
4752             initialPosition[1][j] = WhiteBishop;
4753             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4754             j=BOARD_RGHT-2;
4755             initialPosition[1][j] = WhiteRook;
4756             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4757     }
4758
4759     if( nrCastlingRights == -1) {
4760         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4761         /*       This sets default castling rights from none to normal corners   */
4762         /* Variants with other castling rights must set them themselves above    */
4763         nrCastlingRights = 6;
4764        
4765         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4766         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4767         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4768         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4769         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4770         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4771      }
4772
4773      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4774      if(gameInfo.variant == VariantGreat) { // promotion commoners
4775         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4776         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4777         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4778         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4779      }
4780   if (appData.debugMode) {
4781     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4782   }
4783     if(shuffleOpenings) {
4784         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4785         startedFromSetupPosition = TRUE;
4786     }
4787     if(startedFromPositionFile) {
4788       /* [HGM] loadPos: use PositionFile for every new game */
4789       CopyBoard(initialPosition, filePosition);
4790       for(i=0; i<nrCastlingRights; i++)
4791           castlingRights[0][i] = initialRights[i] = fileRights[i];
4792       startedFromSetupPosition = TRUE;
4793     }
4794
4795     CopyBoard(boards[0], initialPosition);
4796
4797     if(oldx != gameInfo.boardWidth ||
4798        oldy != gameInfo.boardHeight ||
4799        oldh != gameInfo.holdingsWidth
4800 #ifdef GOTHIC
4801        || oldv == VariantGothic ||        // For licensing popups
4802        gameInfo.variant == VariantGothic
4803 #endif
4804 #ifdef FALCON
4805        || oldv == VariantFalcon ||
4806        gameInfo.variant == VariantFalcon
4807 #endif
4808                                          )
4809             InitDrawingSizes(-2 ,0);
4810
4811     if (redraw)
4812       DrawPosition(TRUE, boards[currentMove]);
4813 }
4814
4815 void
4816 SendBoard(cps, moveNum)
4817      ChessProgramState *cps;
4818      int moveNum;
4819 {
4820     char message[MSG_SIZ];
4821     
4822     if (cps->useSetboard) {
4823       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4824       sprintf(message, "setboard %s\n", fen);
4825       SendToProgram(message, cps);
4826       free(fen);
4827
4828     } else {
4829       ChessSquare *bp;
4830       int i, j;
4831       /* Kludge to set black to move, avoiding the troublesome and now
4832        * deprecated "black" command.
4833        */
4834       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4835
4836       SendToProgram("edit\n", cps);
4837       SendToProgram("#\n", cps);
4838       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4839         bp = &boards[moveNum][i][BOARD_LEFT];
4840         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4841           if ((int) *bp < (int) BlackPawn) {
4842             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4843                     AAA + j, ONE + i);
4844             if(message[0] == '+' || message[0] == '~') {
4845                 sprintf(message, "%c%c%c+\n",
4846                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4847                         AAA + j, ONE + i);
4848             }
4849             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4850                 message[1] = BOARD_RGHT   - 1 - j + '1';
4851                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4852             }
4853             SendToProgram(message, cps);
4854           }
4855         }
4856       }
4857     
4858       SendToProgram("c\n", cps);
4859       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4860         bp = &boards[moveNum][i][BOARD_LEFT];
4861         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4862           if (((int) *bp != (int) EmptySquare)
4863               && ((int) *bp >= (int) BlackPawn)) {
4864             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4865                     AAA + j, ONE + i);
4866             if(message[0] == '+' || message[0] == '~') {
4867                 sprintf(message, "%c%c%c+\n",
4868                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4869                         AAA + j, ONE + i);
4870             }
4871             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4872                 message[1] = BOARD_RGHT   - 1 - j + '1';
4873                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4874             }
4875             SendToProgram(message, cps);
4876           }
4877         }
4878       }
4879     
4880       SendToProgram(".\n", cps);
4881     }
4882     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4883 }
4884
4885 int
4886 IsPromotion(fromX, fromY, toX, toY)
4887      int fromX, fromY, toX, toY;
4888 {
4889     /* [HGM] add Shogi promotions */
4890     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4891     ChessSquare piece;
4892
4893     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4894       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4895    /* [HGM] Note to self: line above also weeds out drops */
4896     piece = boards[currentMove][fromY][fromX];
4897     if(gameInfo.variant == VariantShogi) {
4898         promotionZoneSize = 3;
4899         highestPromotingPiece = (int)WhiteKing;
4900         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4901            and if in normal chess we then allow promotion to King, why not
4902            allow promotion of other piece in Shogi?                         */
4903     }
4904     if((int)piece >= BlackPawn) {
4905         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4906              return FALSE;
4907         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4908     } else {
4909         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4910            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4911     }
4912     return ( (int)piece <= highestPromotingPiece );
4913 }
4914
4915 int
4916 InPalace(row, column)
4917      int row, column;
4918 {   /* [HGM] for Xiangqi */
4919     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4920          column < (BOARD_WIDTH + 4)/2 &&
4921          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4922     return FALSE;
4923 }
4924
4925 int
4926 PieceForSquare (x, y)
4927      int x;
4928      int y;
4929 {
4930   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4931      return -1;
4932   else
4933      return boards[currentMove][y][x];
4934 }
4935
4936 int
4937 OKToStartUserMove(x, y)
4938      int x, y;
4939 {
4940     ChessSquare from_piece;
4941     int white_piece;
4942
4943     if (matchMode) return FALSE;
4944     if (gameMode == EditPosition) return TRUE;
4945
4946     if (x >= 0 && y >= 0)
4947       from_piece = boards[currentMove][y][x];
4948     else
4949       from_piece = EmptySquare;
4950
4951     if (from_piece == EmptySquare) return FALSE;
4952
4953     white_piece = (int)from_piece >= (int)WhitePawn &&
4954       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4955
4956     switch (gameMode) {
4957       case PlayFromGameFile:
4958       case AnalyzeFile:
4959       case TwoMachinesPlay:
4960       case EndOfGame:
4961         return FALSE;
4962
4963       case IcsObserving:
4964       case IcsIdle:
4965         return FALSE;
4966
4967       case MachinePlaysWhite:
4968       case IcsPlayingBlack:
4969         if (appData.zippyPlay) return FALSE;
4970         if (white_piece) {
4971             DisplayMoveError(_("You are playing Black"));
4972             return FALSE;
4973         }
4974         break;
4975
4976       case MachinePlaysBlack:
4977       case IcsPlayingWhite:
4978         if (appData.zippyPlay) return FALSE;
4979         if (!white_piece) {
4980             DisplayMoveError(_("You are playing White"));
4981             return FALSE;
4982         }
4983         break;
4984
4985       case EditGame:
4986         if (!white_piece && WhiteOnMove(currentMove)) {
4987             DisplayMoveError(_("It is White's turn"));
4988             return FALSE;
4989         }           
4990         if (white_piece && !WhiteOnMove(currentMove)) {
4991             DisplayMoveError(_("It is Black's turn"));
4992             return FALSE;
4993         }           
4994         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4995             /* Editing correspondence game history */
4996             /* Could disallow this or prompt for confirmation */
4997             cmailOldMove = -1;
4998         }
4999         if (currentMove < forwardMostMove) {
5000             /* Discarding moves */
5001             /* Could prompt for confirmation here,
5002                but I don't think that's such a good idea */
5003             forwardMostMove = currentMove;
5004         }
5005         break;
5006
5007       case BeginningOfGame:
5008         if (appData.icsActive) return FALSE;
5009         if (!appData.noChessProgram) {
5010             if (!white_piece) {
5011                 DisplayMoveError(_("You are playing White"));
5012                 return FALSE;
5013             }
5014         }
5015         break;
5016         
5017       case Training:
5018         if (!white_piece && WhiteOnMove(currentMove)) {
5019             DisplayMoveError(_("It is White's turn"));
5020             return FALSE;
5021         }           
5022         if (white_piece && !WhiteOnMove(currentMove)) {
5023             DisplayMoveError(_("It is Black's turn"));
5024             return FALSE;
5025         }           
5026         break;
5027
5028       default:
5029       case IcsExamining:
5030         break;
5031     }
5032     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5033         && gameMode != AnalyzeFile && gameMode != Training) {
5034         DisplayMoveError(_("Displayed position is not current"));
5035         return FALSE;
5036     }
5037     return TRUE;
5038 }
5039
5040 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5041 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5042 int lastLoadGameUseList = FALSE;
5043 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5044 ChessMove lastLoadGameStart = (ChessMove) 0;
5045
5046 ChessMove
5047 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5048      int fromX, fromY, toX, toY;
5049      int promoChar;
5050      Boolean captureOwn;
5051 {
5052     ChessMove moveType;
5053     ChessSquare pdown, pup;
5054
5055     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5056
5057     /* [HGM] suppress all moves into holdings area and guard band */
5058     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5059             return ImpossibleMove;
5060
5061     /* [HGM] <sameColor> moved to here from winboard.c */
5062     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5063     pdown = boards[currentMove][fromY][fromX];
5064     pup = boards[currentMove][toY][toX];
5065     if (    gameMode != EditPosition && !captureOwn &&
5066             (WhitePawn <= pdown && pdown < BlackPawn &&
5067              WhitePawn <= pup && pup < BlackPawn  ||
5068              BlackPawn <= pdown && pdown < EmptySquare &&
5069              BlackPawn <= pup && pup < EmptySquare 
5070             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5071                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5072                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5073                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5074                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5075         )           )
5076          return Comment;
5077
5078     /* Check if the user is playing in turn.  This is complicated because we
5079        let the user "pick up" a piece before it is his turn.  So the piece he
5080        tried to pick up may have been captured by the time he puts it down!
5081        Therefore we use the color the user is supposed to be playing in this
5082        test, not the color of the piece that is currently on the starting
5083        square---except in EditGame mode, where the user is playing both
5084        sides; fortunately there the capture race can't happen.  (It can
5085        now happen in IcsExamining mode, but that's just too bad.  The user
5086        will get a somewhat confusing message in that case.)
5087        */
5088
5089     switch (gameMode) {
5090       case PlayFromGameFile:
5091       case AnalyzeFile:
5092       case TwoMachinesPlay:
5093       case EndOfGame:
5094       case IcsObserving:
5095       case IcsIdle:
5096         /* We switched into a game mode where moves are not accepted,
5097            perhaps while the mouse button was down. */
5098         return ImpossibleMove;
5099
5100       case MachinePlaysWhite:
5101         /* User is moving for Black */
5102         if (WhiteOnMove(currentMove)) {
5103             DisplayMoveError(_("It is White's turn"));
5104             return ImpossibleMove;
5105         }
5106         break;
5107
5108       case MachinePlaysBlack:
5109         /* User is moving for White */
5110         if (!WhiteOnMove(currentMove)) {
5111             DisplayMoveError(_("It is Black's turn"));
5112             return ImpossibleMove;
5113         }
5114         break;
5115
5116       case EditGame:
5117       case IcsExamining:
5118       case BeginningOfGame:
5119       case AnalyzeMode:
5120       case Training:
5121         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5122             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5123             /* User is moving for Black */
5124             if (WhiteOnMove(currentMove)) {
5125                 DisplayMoveError(_("It is White's turn"));
5126                 return ImpossibleMove;
5127             }
5128         } else {
5129             /* User is moving for White */
5130             if (!WhiteOnMove(currentMove)) {
5131                 DisplayMoveError(_("It is Black's turn"));
5132                 return ImpossibleMove;
5133             }
5134         }
5135         break;
5136
5137       case IcsPlayingBlack:
5138         /* User is moving for Black */
5139         if (WhiteOnMove(currentMove)) {
5140             if (!appData.premove) {
5141                 DisplayMoveError(_("It is White's turn"));
5142             } else if (toX >= 0 && toY >= 0) {
5143                 premoveToX = toX;
5144                 premoveToY = toY;
5145                 premoveFromX = fromX;
5146                 premoveFromY = fromY;
5147                 premovePromoChar = promoChar;
5148                 gotPremove = 1;
5149                 if (appData.debugMode) 
5150                     fprintf(debugFP, "Got premove: fromX %d,"
5151                             "fromY %d, toX %d, toY %d\n",
5152                             fromX, fromY, toX, toY);
5153             }
5154             return ImpossibleMove;
5155         }
5156         break;
5157
5158       case IcsPlayingWhite:
5159         /* User is moving for White */
5160         if (!WhiteOnMove(currentMove)) {
5161             if (!appData.premove) {
5162                 DisplayMoveError(_("It is Black's turn"));
5163             } else if (toX >= 0 && toY >= 0) {
5164                 premoveToX = toX;
5165                 premoveToY = toY;
5166                 premoveFromX = fromX;
5167                 premoveFromY = fromY;
5168                 premovePromoChar = promoChar;
5169                 gotPremove = 1;
5170                 if (appData.debugMode) 
5171                     fprintf(debugFP, "Got premove: fromX %d,"
5172                             "fromY %d, toX %d, toY %d\n",
5173                             fromX, fromY, toX, toY);
5174             }
5175             return ImpossibleMove;
5176         }
5177         break;
5178
5179       default:
5180         break;
5181
5182       case EditPosition:
5183         /* EditPosition, empty square, or different color piece;
5184            click-click move is possible */
5185         if (toX == -2 || toY == -2) {
5186             boards[0][fromY][fromX] = EmptySquare;
5187             return AmbiguousMove;
5188         } else if (toX >= 0 && toY >= 0) {
5189             boards[0][toY][toX] = boards[0][fromY][fromX];
5190             boards[0][fromY][fromX] = EmptySquare;
5191             return AmbiguousMove;
5192         }
5193         return ImpossibleMove;
5194     }
5195
5196     /* [HGM] If move started in holdings, it means a drop */
5197     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5198          if( pup != EmptySquare ) return ImpossibleMove;
5199          if(appData.testLegality) {
5200              /* it would be more logical if LegalityTest() also figured out
5201               * which drops are legal. For now we forbid pawns on back rank.
5202               * Shogi is on its own here...
5203               */
5204              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5205                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5206                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5207          }
5208          return WhiteDrop; /* Not needed to specify white or black yet */
5209     }
5210
5211     userOfferedDraw = FALSE;
5212         
5213     /* [HGM] always test for legality, to get promotion info */
5214     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5215                           epStatus[currentMove], castlingRights[currentMove],
5216                                          fromY, fromX, toY, toX, promoChar);
5217     /* [HGM] but possibly ignore an IllegalMove result */
5218     if (appData.testLegality) {
5219         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5220             DisplayMoveError(_("Illegal move"));
5221             return ImpossibleMove;
5222         }
5223     }
5224 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5225     return moveType;
5226     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5227        function is made into one that returns an OK move type if FinishMove
5228        should be called. This to give the calling driver routine the
5229        opportunity to finish the userMove input with a promotion popup,
5230        without bothering the user with this for invalid or illegal moves */
5231
5232 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5233 }
5234
5235 /* Common tail of UserMoveEvent and DropMenuEvent */
5236 int
5237 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5238      ChessMove moveType;
5239      int fromX, fromY, toX, toY;
5240      /*char*/int promoChar;
5241 {
5242     char *bookHit = 0;
5243 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5244     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5245         // [HGM] superchess: suppress promotions to non-available piece
5246         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5247         if(WhiteOnMove(currentMove)) {
5248             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5249         } else {
5250             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5251         }
5252     }
5253
5254     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5255        move type in caller when we know the move is a legal promotion */
5256     if(moveType == NormalMove && promoChar)
5257         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5258 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5259     /* [HGM] convert drag-and-drop piece drops to standard form */
5260     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5261          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5262            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5263                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5264 //         fromX = boards[currentMove][fromY][fromX];
5265            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5266            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5267            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5268            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5269          fromY = DROP_RANK;
5270     }
5271
5272     /* [HGM] <popupFix> The following if has been moved here from
5273        UserMoveEvent(). Because it seemed to belon here (why not allow
5274        piece drops in training games?), and because it can only be
5275        performed after it is known to what we promote. */
5276     if (gameMode == Training) {
5277       /* compare the move played on the board to the next move in the
5278        * game. If they match, display the move and the opponent's response. 
5279        * If they don't match, display an error message.
5280        */
5281       int saveAnimate;
5282       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5283       CopyBoard(testBoard, boards[currentMove]);
5284       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5285
5286       if (CompareBoards(testBoard, boards[currentMove+1])) {
5287         ForwardInner(currentMove+1);
5288
5289         /* Autoplay the opponent's response.
5290          * if appData.animate was TRUE when Training mode was entered,
5291          * the response will be animated.
5292          */
5293         saveAnimate = appData.animate;
5294         appData.animate = animateTraining;
5295         ForwardInner(currentMove+1);
5296         appData.animate = saveAnimate;
5297
5298         /* check for the end of the game */
5299         if (currentMove >= forwardMostMove) {
5300           gameMode = PlayFromGameFile;
5301           ModeHighlight();
5302           SetTrainingModeOff();
5303           DisplayInformation(_("End of game"));
5304         }
5305       } else {
5306         DisplayError(_("Incorrect move"), 0);
5307       }
5308       return 1;
5309     }
5310
5311   /* Ok, now we know that the move is good, so we can kill
5312      the previous line in Analysis Mode */
5313   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5314     forwardMostMove = currentMove;
5315   }
5316
5317   /* If we need the chess program but it's dead, restart it */
5318   ResurrectChessProgram();
5319
5320   /* A user move restarts a paused game*/
5321   if (pausing)
5322     PauseEvent();
5323
5324   thinkOutput[0] = NULLCHAR;
5325
5326   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5327
5328   if (gameMode == BeginningOfGame) {
5329     if (appData.noChessProgram) {
5330       gameMode = EditGame;
5331       SetGameInfo();
5332     } else {
5333       char buf[MSG_SIZ];
5334       gameMode = MachinePlaysBlack;
5335       StartClocks();
5336       SetGameInfo();
5337       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5338       DisplayTitle(buf);
5339       if (first.sendName) {
5340         sprintf(buf, "name %s\n", gameInfo.white);
5341         SendToProgram(buf, &first);
5342       }
5343       StartClocks();
5344     }
5345     ModeHighlight();
5346   }
5347 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5348   /* Relay move to ICS or chess engine */
5349   if (appData.icsActive) {
5350     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5351         gameMode == IcsExamining) {
5352       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5353       ics_user_moved = 1;
5354     }
5355   } else {
5356     if (first.sendTime && (gameMode == BeginningOfGame ||
5357                            gameMode == MachinePlaysWhite ||
5358                            gameMode == MachinePlaysBlack)) {
5359       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5360     }
5361     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5362          // [HGM] book: if program might be playing, let it use book
5363         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5364         first.maybeThinking = TRUE;
5365     } else SendMoveToProgram(forwardMostMove-1, &first);
5366     if (currentMove == cmailOldMove + 1) {
5367       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5368     }
5369   }
5370
5371   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5372
5373   switch (gameMode) {
5374   case EditGame:
5375     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5376                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5377     case MT_NONE:
5378     case MT_CHECK:
5379       break;
5380     case MT_CHECKMATE:
5381     case MT_STAINMATE:
5382       if (WhiteOnMove(currentMove)) {
5383         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5384       } else {
5385         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5386       }
5387       break;
5388     case MT_STALEMATE:
5389       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5390       break;
5391     }
5392     break;
5393     
5394   case MachinePlaysBlack:
5395   case MachinePlaysWhite:
5396     /* disable certain menu options while machine is thinking */
5397     SetMachineThinkingEnables();
5398     break;
5399
5400   default:
5401     break;
5402   }
5403
5404   if(bookHit) { // [HGM] book: simulate book reply
5405         static char bookMove[MSG_SIZ]; // a bit generous?
5406
5407         programStats.nodes = programStats.depth = programStats.time = 
5408         programStats.score = programStats.got_only_move = 0;
5409         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5410
5411         strcpy(bookMove, "move ");
5412         strcat(bookMove, bookHit);
5413         HandleMachineMove(bookMove, &first);
5414   }
5415   return 1;
5416 }
5417
5418 void
5419 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5420      int fromX, fromY, toX, toY;
5421      int promoChar;
5422 {
5423     /* [HGM] This routine was added to allow calling of its two logical
5424        parts from other modules in the old way. Before, UserMoveEvent()
5425        automatically called FinishMove() if the move was OK, and returned
5426        otherwise. I separated the two, in order to make it possible to
5427        slip a promotion popup in between. But that it always needs two
5428        calls, to the first part, (now called UserMoveTest() ), and to
5429        FinishMove if the first part succeeded. Calls that do not need
5430        to do anything in between, can call this routine the old way. 
5431     */
5432     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5433 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5434     if(moveType == AmbiguousMove)
5435         DrawPosition(FALSE, boards[currentMove]);
5436     else if(moveType != ImpossibleMove && moveType != Comment)
5437         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5438 }
5439
5440 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5441 {
5442 //    char * hint = lastHint;
5443     FrontEndProgramStats stats;
5444
5445     stats.which = cps == &first ? 0 : 1;
5446     stats.depth = cpstats->depth;
5447     stats.nodes = cpstats->nodes;
5448     stats.score = cpstats->score;
5449     stats.time = cpstats->time;
5450     stats.pv = cpstats->movelist;
5451     stats.hint = lastHint;
5452     stats.an_move_index = 0;
5453     stats.an_move_count = 0;
5454
5455     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5456         stats.hint = cpstats->move_name;
5457         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5458         stats.an_move_count = cpstats->nr_moves;
5459     }
5460
5461     SetProgramStats( &stats );
5462 }
5463
5464 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5465 {   // [HGM] book: this routine intercepts moves to simulate book replies
5466     char *bookHit = NULL;
5467
5468     //first determine if the incoming move brings opponent into his book
5469     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5470         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5471     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5472     if(bookHit != NULL && !cps->bookSuspend) {
5473         // make sure opponent is not going to reply after receiving move to book position
5474         SendToProgram("force\n", cps);
5475         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5476     }
5477     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5478     // now arrange restart after book miss
5479     if(bookHit) {
5480         // after a book hit we never send 'go', and the code after the call to this routine
5481         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5482         char buf[MSG_SIZ];
5483         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5484         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5485         SendToProgram(buf, cps);
5486         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5487     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5488         SendToProgram("go\n", cps);
5489         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5490     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5491         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5492             SendToProgram("go\n", cps); 
5493         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5494     }
5495     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5496 }
5497
5498 char *savedMessage;
5499 ChessProgramState *savedState;
5500 void DeferredBookMove(void)
5501 {
5502         if(savedState->lastPing != savedState->lastPong)
5503                     ScheduleDelayedEvent(DeferredBookMove, 10);
5504         else
5505         HandleMachineMove(savedMessage, savedState);
5506 }
5507
5508 void
5509 HandleMachineMove(message, cps)
5510      char *message;
5511      ChessProgramState *cps;
5512 {
5513     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5514     char realname[MSG_SIZ];
5515     int fromX, fromY, toX, toY;
5516     ChessMove moveType;
5517     char promoChar;
5518     char *p;
5519     int machineWhite;
5520     char *bookHit;
5521
5522 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5523     /*
5524      * Kludge to ignore BEL characters
5525      */
5526     while (*message == '\007') message++;
5527
5528     /*
5529      * [HGM] engine debug message: ignore lines starting with '#' character
5530      */
5531     if(cps->debug && *message == '#') return;
5532
5533     /*
5534      * Look for book output
5535      */
5536     if (cps == &first && bookRequested) {
5537         if (message[0] == '\t' || message[0] == ' ') {
5538             /* Part of the book output is here; append it */
5539             strcat(bookOutput, message);
5540             strcat(bookOutput, "  \n");
5541             return;
5542         } else if (bookOutput[0] != NULLCHAR) {
5543             /* All of book output has arrived; display it */
5544             char *p = bookOutput;
5545             while (*p != NULLCHAR) {
5546                 if (*p == '\t') *p = ' ';
5547                 p++;
5548             }
5549             DisplayInformation(bookOutput);
5550             bookRequested = FALSE;
5551             /* Fall through to parse the current output */
5552         }
5553     }
5554
5555     /*
5556      * Look for machine move.
5557      */
5558     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5559         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5560     {
5561         /* This method is only useful on engines that support ping */
5562         if (cps->lastPing != cps->lastPong) {
5563           if (gameMode == BeginningOfGame) {
5564             /* Extra move from before last new; ignore */
5565             if (appData.debugMode) {
5566                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5567             }
5568           } else {
5569             if (appData.debugMode) {
5570                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5571                         cps->which, gameMode);
5572             }
5573
5574             SendToProgram("undo\n", cps);
5575           }
5576           return;
5577         }
5578
5579         switch (gameMode) {
5580           case BeginningOfGame:
5581             /* Extra move from before last reset; ignore */
5582             if (appData.debugMode) {
5583                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5584             }
5585             return;
5586
5587           case EndOfGame:
5588           case IcsIdle:
5589           default:
5590             /* Extra move after we tried to stop.  The mode test is
5591                not a reliable way of detecting this problem, but it's
5592                the best we can do on engines that don't support ping.
5593             */
5594             if (appData.debugMode) {
5595                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5596                         cps->which, gameMode);
5597             }
5598             SendToProgram("undo\n", cps);
5599             return;
5600
5601           case MachinePlaysWhite:
5602           case IcsPlayingWhite:
5603             machineWhite = TRUE;
5604             break;
5605
5606           case MachinePlaysBlack:
5607           case IcsPlayingBlack:
5608             machineWhite = FALSE;
5609             break;
5610
5611           case TwoMachinesPlay:
5612             machineWhite = (cps->twoMachinesColor[0] == 'w');
5613             break;
5614         }
5615         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5616             if (appData.debugMode) {
5617                 fprintf(debugFP,
5618                         "Ignoring move out of turn by %s, gameMode %d"
5619                         ", forwardMost %d\n",
5620                         cps->which, gameMode, forwardMostMove);
5621             }
5622             return;
5623         }
5624
5625     if (appData.debugMode) { int f = forwardMostMove;
5626         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5627                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5628     }
5629         if(cps->alphaRank) AlphaRank(machineMove, 4);
5630         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5631                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5632             /* Machine move could not be parsed; ignore it. */
5633             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5634                     machineMove, cps->which);
5635             DisplayError(buf1, 0);
5636             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5637                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5638             if (gameMode == TwoMachinesPlay) {
5639               GameEnds(machineWhite ? BlackWins : WhiteWins,
5640                        buf1, GE_XBOARD);
5641             }
5642             return;
5643         }
5644
5645         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5646         /* So we have to redo legality test with true e.p. status here,  */
5647         /* to make sure an illegal e.p. capture does not slip through,   */
5648         /* to cause a forfeit on a justified illegal-move complaint      */
5649         /* of the opponent.                                              */
5650         if( gameMode==TwoMachinesPlay && appData.testLegality
5651             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5652                                                               ) {
5653            ChessMove moveType;
5654            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5655                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5656                              fromY, fromX, toY, toX, promoChar);
5657             if (appData.debugMode) {
5658                 int i;
5659                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5660                     castlingRights[forwardMostMove][i], castlingRank[i]);
5661                 fprintf(debugFP, "castling rights\n");
5662             }
5663             if(moveType == IllegalMove) {
5664                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5665                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5666                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5667                            buf1, GE_XBOARD);
5668                 return;
5669            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5670            /* [HGM] Kludge to handle engines that send FRC-style castling
5671               when they shouldn't (like TSCP-Gothic) */
5672            switch(moveType) {
5673              case WhiteASideCastleFR:
5674              case BlackASideCastleFR:
5675                toX+=2;
5676                currentMoveString[2]++;
5677                break;
5678              case WhiteHSideCastleFR:
5679              case BlackHSideCastleFR:
5680                toX--;
5681                currentMoveString[2]--;
5682                break;
5683              default: ; // nothing to do, but suppresses warning of pedantic compilers
5684            }
5685         }
5686         hintRequested = FALSE;
5687         lastHint[0] = NULLCHAR;
5688         bookRequested = FALSE;
5689         /* Program may be pondering now */
5690         cps->maybeThinking = TRUE;
5691         if (cps->sendTime == 2) cps->sendTime = 1;
5692         if (cps->offeredDraw) cps->offeredDraw--;
5693
5694 #if ZIPPY
5695         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5696             first.initDone) {
5697           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5698           ics_user_moved = 1;
5699           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5700                 char buf[3*MSG_SIZ];
5701
5702                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5703                         programStats.score / 100.,
5704                         programStats.depth,
5705                         programStats.time / 100.,
5706                         (unsigned int)programStats.nodes,
5707                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5708                         programStats.movelist);
5709                 SendToICS(buf);
5710 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5711           }
5712         }
5713 #endif
5714         /* currentMoveString is set as a side-effect of ParseOneMove */
5715         strcpy(machineMove, currentMoveString);
5716         strcat(machineMove, "\n");
5717         strcpy(moveList[forwardMostMove], machineMove);
5718
5719         /* [AS] Save move info and clear stats for next move */
5720         pvInfoList[ forwardMostMove ].score = programStats.score;
5721         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5722         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5723         ClearProgramStats();
5724         thinkOutput[0] = NULLCHAR;
5725         hiddenThinkOutputState = 0;
5726
5727         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5728
5729         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5730         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5731             int count = 0;
5732
5733             while( count < adjudicateLossPlies ) {
5734                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5735
5736                 if( count & 1 ) {
5737                     score = -score; /* Flip score for winning side */
5738                 }
5739
5740                 if( score > adjudicateLossThreshold ) {
5741                     break;
5742                 }
5743
5744                 count++;
5745             }
5746
5747             if( count >= adjudicateLossPlies ) {
5748                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5749
5750                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5751                     "Xboard adjudication", 
5752                     GE_XBOARD );
5753
5754                 return;
5755             }
5756         }
5757
5758         if( gameMode == TwoMachinesPlay ) {
5759           // [HGM] some adjudications useful with buggy engines
5760             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5761           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5762
5763
5764             if( appData.testLegality )
5765             {   /* [HGM] Some more adjudications for obstinate engines */
5766                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5767                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5768                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5769                 static int moveCount = 6;
5770                 ChessMove result;
5771                 char *reason = NULL;
5772
5773                 /* Count what is on board. */
5774                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5775                 {   ChessSquare p = boards[forwardMostMove][i][j];
5776                     int m=i;
5777
5778                     switch((int) p)
5779                     {   /* count B,N,R and other of each side */
5780                         case WhiteKing:
5781                         case BlackKing:
5782                              NrK++; break; // [HGM] atomic: count Kings
5783                         case WhiteKnight:
5784                              NrWN++; break;
5785                         case WhiteBishop:
5786                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5787                              bishopsColor |= 1 << ((i^j)&1);
5788                              NrWB++; break;
5789                         case BlackKnight:
5790                              NrBN++; break;
5791                         case BlackBishop:
5792                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5793                              bishopsColor |= 1 << ((i^j)&1);
5794                              NrBB++; break;
5795                         case WhiteRook:
5796                              NrWR++; break;
5797                         case BlackRook:
5798                              NrBR++; break;
5799                         case WhiteQueen:
5800                              NrWQ++; break;
5801                         case BlackQueen:
5802                              NrBQ++; break;
5803                         case EmptySquare: 
5804                              break;
5805                         case BlackPawn:
5806                              m = 7-i;
5807                         case WhitePawn:
5808                              PawnAdvance += m; NrPawns++;
5809                     }
5810                     NrPieces += (p != EmptySquare);
5811                     NrW += ((int)p < (int)BlackPawn);
5812                     if(gameInfo.variant == VariantXiangqi && 
5813                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5814                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5815                         NrW -= ((int)p < (int)BlackPawn);
5816                     }
5817                 }
5818
5819                 /* Some material-based adjudications that have to be made before stalemate test */
5820                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5821                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5822                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5823                      if(appData.checkMates) {
5824                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5825                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5826                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5827                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5828                          return;
5829                      }
5830                 }
5831
5832                 /* Bare King in Shatranj (loses) or Losers (wins) */
5833                 if( NrW == 1 || NrPieces - NrW == 1) {
5834                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5835                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5836                      if(appData.checkMates) {
5837                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5838                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5839                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5840                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5841                          return;
5842                      }
5843                   } else
5844                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5845                   {    /* bare King */
5846                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5847                         if(appData.checkMates) {
5848                             /* but only adjudicate if adjudication enabled */
5849                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5850                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5851                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5852                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5853                             return;
5854                         }
5855                   }
5856                 } else bare = 1;
5857
5858
5859             // don't wait for engine to announce game end if we can judge ourselves
5860             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5861                                        castlingRights[forwardMostMove]) ) {
5862               case MT_CHECK:
5863                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5864                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5865                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5866                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5867                             checkCnt++;
5868                         if(checkCnt >= 2) {
5869                             reason = "Xboard adjudication: 3rd check";
5870                             epStatus[forwardMostMove] = EP_CHECKMATE;
5871                             break;
5872                         }
5873                     }
5874                 }
5875               case MT_NONE:
5876               default:
5877                 break;
5878               case MT_STALEMATE:
5879               case MT_STAINMATE:
5880                 reason = "Xboard adjudication: Stalemate";
5881                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5882                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5883                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5884                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5885                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5886                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5887                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5888                                                                         EP_CHECKMATE : EP_WINS);
5889                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5890                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5891                 }
5892                 break;
5893               case MT_CHECKMATE:
5894                 reason = "Xboard adjudication: Checkmate";
5895                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5896                 break;
5897             }
5898
5899                 switch(i = epStatus[forwardMostMove]) {
5900                     case EP_STALEMATE:
5901                         result = GameIsDrawn; break;
5902                     case EP_CHECKMATE:
5903                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5904                     case EP_WINS:
5905                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5906                     default:
5907                         result = (ChessMove) 0;
5908                 }
5909                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5910                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5911                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5912                     GameEnds( result, reason, GE_XBOARD );
5913                     return;
5914                 }
5915
5916                 /* Next absolutely insufficient mating material. */
5917                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5918                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5919                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5920                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5921                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5922
5923                      /* always flag draws, for judging claims */
5924                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5925
5926                      if(appData.materialDraws) {
5927                          /* but only adjudicate them if adjudication enabled */
5928                          SendToProgram("force\n", cps->other); // suppress reply
5929                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5930                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5931                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5932                          return;
5933                      }
5934                 }
5935
5936                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5937                 if(NrPieces == 4 && 
5938                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5939                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5940                    || NrWN==2 || NrBN==2     /* KNNK */
5941                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5942                   ) ) {
5943                      if(--moveCount < 0 && appData.trivialDraws)
5944                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5945                           SendToProgram("force\n", cps->other); // suppress reply
5946                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5947                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5948                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5949                           return;
5950                      }
5951                 } else moveCount = 6;
5952             }
5953           }
5954           
5955           if (appData.debugMode) { int i;
5956             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5957                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5958                     appData.drawRepeats);
5959             for( i=forwardMostMove; i>=backwardMostMove; i-- )
5960               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5961             
5962           }
5963
5964                 /* Check for rep-draws */
5965                 count = 0;
5966                 for(k = forwardMostMove-2;
5967                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5968                         epStatus[k] < EP_UNKNOWN &&
5969                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5970                     k-=2)
5971                 {   int rights=0;
5972                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5973                         /* compare castling rights */
5974                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5975                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5976                                 rights++; /* King lost rights, while rook still had them */
5977                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5978                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5979                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5980                                    rights++; /* but at least one rook lost them */
5981                         }
5982                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5983                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5984                                 rights++; 
5985                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5986                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5987                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5988                                    rights++;
5989                         }
5990                         if( rights == 0 && ++count > appData.drawRepeats-2
5991                             && appData.drawRepeats > 1) {
5992                              /* adjudicate after user-specified nr of repeats */
5993                              SendToProgram("force\n", cps->other); // suppress reply
5994                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5995                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5996                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5997                                 // [HGM] xiangqi: check for forbidden perpetuals
5998                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5999                                 for(m=forwardMostMove; m>k; m-=2) {
6000                                     if(MateTest(boards[m], PosFlags(m), 
6001                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6002                                         ourPerpetual = 0; // the current mover did not always check
6003                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6004                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6005                                         hisPerpetual = 0; // the opponent did not always check
6006                                 }
6007                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6008                                                                         ourPerpetual, hisPerpetual);
6009                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6010                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6011                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6012                                     return;
6013                                 }
6014                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6015                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6016                                 // Now check for perpetual chases
6017                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6018                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6019                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6020                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6021                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6022                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6023                                         return;
6024                                     }
6025                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6026                                         break; // Abort repetition-checking loop.
6027                                 }
6028                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6029                              }
6030                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6031                              return;
6032                         }
6033                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6034                              epStatus[forwardMostMove] = EP_REP_DRAW;
6035                     }
6036                 }
6037
6038                 /* Now we test for 50-move draws. Determine ply count */
6039                 count = forwardMostMove;
6040                 /* look for last irreversble move */
6041                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6042                     count--;
6043                 /* if we hit starting position, add initial plies */
6044                 if( count == backwardMostMove )
6045                     count -= initialRulePlies;
6046                 count = forwardMostMove - count; 
6047                 if( count >= 100)
6048                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6049                          /* this is used to judge if draw claims are legal */
6050                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6051                          SendToProgram("force\n", cps->other); // suppress reply
6052                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6053                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6054                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6055                          return;
6056                 }
6057
6058                 /* if draw offer is pending, treat it as a draw claim
6059                  * when draw condition present, to allow engines a way to
6060                  * claim draws before making their move to avoid a race
6061                  * condition occurring after their move
6062                  */
6063                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6064                          char *p = NULL;
6065                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6066                              p = "Draw claim: 50-move rule";
6067                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6068                              p = "Draw claim: 3-fold repetition";
6069                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6070                              p = "Draw claim: insufficient mating material";
6071                          if( p != NULL ) {
6072                              SendToProgram("force\n", cps->other); // suppress reply
6073                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6074                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6075                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6076                              return;
6077                          }
6078                 }
6079
6080
6081                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6082                     SendToProgram("force\n", cps->other); // suppress reply
6083                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6084                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6085
6086                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6087
6088                     return;
6089                 }
6090         }
6091
6092         bookHit = NULL;
6093         if (gameMode == TwoMachinesPlay) {
6094             /* [HGM] relaying draw offers moved to after reception of move */
6095             /* and interpreting offer as claim if it brings draw condition */
6096             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6097                 SendToProgram("draw\n", cps->other);
6098             }
6099             if (cps->other->sendTime) {
6100                 SendTimeRemaining(cps->other,
6101                                   cps->other->twoMachinesColor[0] == 'w');
6102             }
6103             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6104             if (firstMove && !bookHit) {
6105                 firstMove = FALSE;
6106                 if (cps->other->useColors) {
6107                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6108                 }
6109                 SendToProgram("go\n", cps->other);
6110             }
6111             cps->other->maybeThinking = TRUE;
6112         }
6113
6114         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6115         
6116         if (!pausing && appData.ringBellAfterMoves) {
6117             RingBell();
6118         }
6119
6120         /* 
6121          * Reenable menu items that were disabled while
6122          * machine was thinking
6123          */
6124         if (gameMode != TwoMachinesPlay)
6125             SetUserThinkingEnables();
6126
6127         // [HGM] book: after book hit opponent has received move and is now in force mode
6128         // force the book reply into it, and then fake that it outputted this move by jumping
6129         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6130         if(bookHit) {
6131                 static char bookMove[MSG_SIZ]; // a bit generous?
6132
6133                 strcpy(bookMove, "move ");
6134                 strcat(bookMove, bookHit);
6135                 message = bookMove;
6136                 cps = cps->other;
6137                 programStats.nodes = programStats.depth = programStats.time = 
6138                 programStats.score = programStats.got_only_move = 0;
6139                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6140
6141                 if(cps->lastPing != cps->lastPong) {
6142                     savedMessage = message; // args for deferred call
6143                     savedState = cps;
6144                     ScheduleDelayedEvent(DeferredBookMove, 10);
6145                     return;
6146                 }
6147                 goto FakeBookMove;
6148         }
6149
6150         return;
6151     }
6152
6153     /* Set special modes for chess engines.  Later something general
6154      *  could be added here; for now there is just one kludge feature,
6155      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6156      *  when "xboard" is given as an interactive command.
6157      */
6158     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6159         cps->useSigint = FALSE;
6160         cps->useSigterm = FALSE;
6161     }
6162     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6163       ParseFeatures(message+8, cps);
6164       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6165     }
6166
6167     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6168      * want this, I was asked to put it in, and obliged.
6169      */
6170     if (!strncmp(message, "setboard ", 9)) {
6171         Board initial_position; int i;
6172
6173         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6174
6175         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6176             DisplayError(_("Bad FEN received from engine"), 0);
6177             return ;
6178         } else {
6179            Reset(FALSE, FALSE);
6180            CopyBoard(boards[0], initial_position);
6181            initialRulePlies = FENrulePlies;
6182            epStatus[0] = FENepStatus;
6183            for( i=0; i<nrCastlingRights; i++ )
6184                 castlingRights[0][i] = FENcastlingRights[i];
6185            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6186            else gameMode = MachinePlaysBlack;                 
6187            DrawPosition(FALSE, boards[currentMove]);
6188         }
6189         return;
6190     }
6191
6192     /*
6193      * Look for communication commands
6194      */
6195     if (!strncmp(message, "telluser ", 9)) {
6196         DisplayNote(message + 9);
6197         return;
6198     }
6199     if (!strncmp(message, "tellusererror ", 14)) {
6200         DisplayError(message + 14, 0);
6201         return;
6202     }
6203     if (!strncmp(message, "tellopponent ", 13)) {
6204       if (appData.icsActive) {
6205         if (loggedOn) {
6206           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6207           SendToICS(buf1);
6208         }
6209       } else {
6210         DisplayNote(message + 13);
6211       }
6212       return;
6213     }
6214     if (!strncmp(message, "tellothers ", 11)) {
6215       if (appData.icsActive) {
6216         if (loggedOn) {
6217           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6218           SendToICS(buf1);
6219         }
6220       }
6221       return;
6222     }
6223     if (!strncmp(message, "tellall ", 8)) {
6224       if (appData.icsActive) {
6225         if (loggedOn) {
6226           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6227           SendToICS(buf1);
6228         }
6229       } else {
6230         DisplayNote(message + 8);
6231       }
6232       return;
6233     }
6234     if (strncmp(message, "warning", 7) == 0) {
6235         /* Undocumented feature, use tellusererror in new code */
6236         DisplayError(message, 0);
6237         return;
6238     }
6239     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6240         strcpy(realname, cps->tidy);
6241         strcat(realname, " query");
6242         AskQuestion(realname, buf2, buf1, cps->pr);
6243         return;
6244     }
6245     /* Commands from the engine directly to ICS.  We don't allow these to be 
6246      *  sent until we are logged on. Crafty kibitzes have been known to 
6247      *  interfere with the login process.
6248      */
6249     if (loggedOn) {
6250         if (!strncmp(message, "tellics ", 8)) {
6251             SendToICS(message + 8);
6252             SendToICS("\n");
6253             return;
6254         }
6255         if (!strncmp(message, "tellicsnoalias ", 15)) {
6256             SendToICS(ics_prefix);
6257             SendToICS(message + 15);
6258             SendToICS("\n");
6259             return;
6260         }
6261         /* The following are for backward compatibility only */
6262         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6263             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6264             SendToICS(ics_prefix);
6265             SendToICS(message);
6266             SendToICS("\n");
6267             return;
6268         }
6269     }
6270     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6271         return;
6272     }
6273     /*
6274      * If the move is illegal, cancel it and redraw the board.
6275      * Also deal with other error cases.  Matching is rather loose
6276      * here to accommodate engines written before the spec.
6277      */
6278     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6279         strncmp(message, "Error", 5) == 0) {
6280         if (StrStr(message, "name") || 
6281             StrStr(message, "rating") || StrStr(message, "?") ||
6282             StrStr(message, "result") || StrStr(message, "board") ||
6283             StrStr(message, "bk") || StrStr(message, "computer") ||
6284             StrStr(message, "variant") || StrStr(message, "hint") ||
6285             StrStr(message, "random") || StrStr(message, "depth") ||
6286             StrStr(message, "accepted")) {
6287             return;
6288         }
6289         if (StrStr(message, "protover")) {
6290           /* Program is responding to input, so it's apparently done
6291              initializing, and this error message indicates it is
6292              protocol version 1.  So we don't need to wait any longer
6293              for it to initialize and send feature commands. */
6294           FeatureDone(cps, 1);
6295           cps->protocolVersion = 1;
6296           return;
6297         }
6298         cps->maybeThinking = FALSE;
6299
6300         if (StrStr(message, "draw")) {
6301             /* Program doesn't have "draw" command */
6302             cps->sendDrawOffers = 0;
6303             return;
6304         }
6305         if (cps->sendTime != 1 &&
6306             (StrStr(message, "time") || StrStr(message, "otim"))) {
6307           /* Program apparently doesn't have "time" or "otim" command */
6308           cps->sendTime = 0;
6309           return;
6310         }
6311         if (StrStr(message, "analyze")) {
6312             cps->analysisSupport = FALSE;
6313             cps->analyzing = FALSE;
6314             Reset(FALSE, TRUE);
6315             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6316             DisplayError(buf2, 0);
6317             return;
6318         }
6319         if (StrStr(message, "(no matching move)st")) {
6320           /* Special kludge for GNU Chess 4 only */
6321           cps->stKludge = TRUE;
6322           SendTimeControl(cps, movesPerSession, timeControl,
6323                           timeIncrement, appData.searchDepth,
6324                           searchTime);
6325           return;
6326         }
6327         if (StrStr(message, "(no matching move)sd")) {
6328           /* Special kludge for GNU Chess 4 only */
6329           cps->sdKludge = TRUE;
6330           SendTimeControl(cps, movesPerSession, timeControl,
6331                           timeIncrement, appData.searchDepth,
6332                           searchTime);
6333           return;
6334         }
6335         if (!StrStr(message, "llegal")) {
6336             return;
6337         }
6338         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6339             gameMode == IcsIdle) return;
6340         if (forwardMostMove <= backwardMostMove) return;
6341         if (pausing) PauseEvent();
6342       if(appData.forceIllegal) {
6343             // [HGM] illegal: machine refused move; force position after move into it
6344           SendToProgram("force\n", cps);
6345           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6346                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6347                 // when black is to move, while there might be nothing on a2 or black
6348                 // might already have the move. So send the board as if white has the move.
6349                 // But first we must change the stm of the engine, as it refused the last move
6350                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6351                 if(WhiteOnMove(forwardMostMove)) {
6352                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6353                     SendBoard(cps, forwardMostMove); // kludgeless board
6354                 } else {
6355                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6356                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6357                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6358                 }
6359           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6360             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6361                  gameMode == TwoMachinesPlay)
6362               SendToProgram("go\n", cps);
6363             return;
6364       } else
6365         if (gameMode == PlayFromGameFile) {
6366             /* Stop reading this game file */
6367             gameMode = EditGame;
6368             ModeHighlight();
6369         }
6370         currentMove = --forwardMostMove;
6371         DisplayMove(currentMove-1); /* before DisplayMoveError */
6372         SwitchClocks();
6373         DisplayBothClocks();
6374         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6375                 parseList[currentMove], cps->which);
6376         DisplayMoveError(buf1);
6377         DrawPosition(FALSE, boards[currentMove]);
6378
6379         /* [HGM] illegal-move claim should forfeit game when Xboard */
6380         /* only passes fully legal moves                            */
6381         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6382             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6383                                 "False illegal-move claim", GE_XBOARD );
6384         }
6385         return;
6386     }
6387     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6388         /* Program has a broken "time" command that
6389            outputs a string not ending in newline.
6390            Don't use it. */
6391         cps->sendTime = 0;
6392     }
6393     
6394     /*
6395      * If chess program startup fails, exit with an error message.
6396      * Attempts to recover here are futile.
6397      */
6398     if ((StrStr(message, "unknown host") != NULL)
6399         || (StrStr(message, "No remote directory") != NULL)
6400         || (StrStr(message, "not found") != NULL)
6401         || (StrStr(message, "No such file") != NULL)
6402         || (StrStr(message, "can't alloc") != NULL)
6403         || (StrStr(message, "Permission denied") != NULL)) {
6404
6405         cps->maybeThinking = FALSE;
6406         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6407                 cps->which, cps->program, cps->host, message);
6408         RemoveInputSource(cps->isr);
6409         DisplayFatalError(buf1, 0, 1);
6410         return;
6411     }
6412     
6413     /* 
6414      * Look for hint output
6415      */
6416     if (sscanf(message, "Hint: %s", buf1) == 1) {
6417         if (cps == &first && hintRequested) {
6418             hintRequested = FALSE;
6419             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6420                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6421                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6422                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6423                                     fromY, fromX, toY, toX, promoChar, buf1);
6424                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6425                 DisplayInformation(buf2);
6426             } else {
6427                 /* Hint move could not be parsed!? */
6428               snprintf(buf2, sizeof(buf2),
6429                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6430                         buf1, cps->which);
6431                 DisplayError(buf2, 0);
6432             }
6433         } else {
6434             strcpy(lastHint, buf1);
6435         }
6436         return;
6437     }
6438
6439     /*
6440      * Ignore other messages if game is not in progress
6441      */
6442     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6443         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6444
6445     /*
6446      * look for win, lose, draw, or draw offer
6447      */
6448     if (strncmp(message, "1-0", 3) == 0) {
6449         char *p, *q, *r = "";
6450         p = strchr(message, '{');
6451         if (p) {
6452             q = strchr(p, '}');
6453             if (q) {
6454                 *q = NULLCHAR;
6455                 r = p + 1;
6456             }
6457         }
6458         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6459         return;
6460     } else if (strncmp(message, "0-1", 3) == 0) {
6461         char *p, *q, *r = "";
6462         p = strchr(message, '{');
6463         if (p) {
6464             q = strchr(p, '}');
6465             if (q) {
6466                 *q = NULLCHAR;
6467                 r = p + 1;
6468             }
6469         }
6470         /* Kludge for Arasan 4.1 bug */
6471         if (strcmp(r, "Black resigns") == 0) {
6472             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6473             return;
6474         }
6475         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6476         return;
6477     } else if (strncmp(message, "1/2", 3) == 0) {
6478         char *p, *q, *r = "";
6479         p = strchr(message, '{');
6480         if (p) {
6481             q = strchr(p, '}');
6482             if (q) {
6483                 *q = NULLCHAR;
6484                 r = p + 1;
6485             }
6486         }
6487             
6488         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6489         return;
6490
6491     } else if (strncmp(message, "White resign", 12) == 0) {
6492         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6493         return;
6494     } else if (strncmp(message, "Black resign", 12) == 0) {
6495         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6496         return;
6497     } else if (strncmp(message, "White matches", 13) == 0 ||
6498                strncmp(message, "Black matches", 13) == 0   ) {
6499         /* [HGM] ignore GNUShogi noises */
6500         return;
6501     } else if (strncmp(message, "White", 5) == 0 &&
6502                message[5] != '(' &&
6503                StrStr(message, "Black") == NULL) {
6504         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6505         return;
6506     } else if (strncmp(message, "Black", 5) == 0 &&
6507                message[5] != '(') {
6508         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6509         return;
6510     } else if (strcmp(message, "resign") == 0 ||
6511                strcmp(message, "computer resigns") == 0) {
6512         switch (gameMode) {
6513           case MachinePlaysBlack:
6514           case IcsPlayingBlack:
6515             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6516             break;
6517           case MachinePlaysWhite:
6518           case IcsPlayingWhite:
6519             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6520             break;
6521           case TwoMachinesPlay:
6522             if (cps->twoMachinesColor[0] == 'w')
6523               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6524             else
6525               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6526             break;
6527           default:
6528             /* can't happen */
6529             break;
6530         }
6531         return;
6532     } else if (strncmp(message, "opponent mates", 14) == 0) {
6533         switch (gameMode) {
6534           case MachinePlaysBlack:
6535           case IcsPlayingBlack:
6536             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6537             break;
6538           case MachinePlaysWhite:
6539           case IcsPlayingWhite:
6540             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6541             break;
6542           case TwoMachinesPlay:
6543             if (cps->twoMachinesColor[0] == 'w')
6544               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6545             else
6546               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6547             break;
6548           default:
6549             /* can't happen */
6550             break;
6551         }
6552         return;
6553     } else if (strncmp(message, "computer mates", 14) == 0) {
6554         switch (gameMode) {
6555           case MachinePlaysBlack:
6556           case IcsPlayingBlack:
6557             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6558             break;
6559           case MachinePlaysWhite:
6560           case IcsPlayingWhite:
6561             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6562             break;
6563           case TwoMachinesPlay:
6564             if (cps->twoMachinesColor[0] == 'w')
6565               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6566             else
6567               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6568             break;
6569           default:
6570             /* can't happen */
6571             break;
6572         }
6573         return;
6574     } else if (strncmp(message, "checkmate", 9) == 0) {
6575         if (WhiteOnMove(forwardMostMove)) {
6576             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6577         } else {
6578             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6579         }
6580         return;
6581     } else if (strstr(message, "Draw") != NULL ||
6582                strstr(message, "game is a draw") != NULL) {
6583         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6584         return;
6585     } else if (strstr(message, "offer") != NULL &&
6586                strstr(message, "draw") != NULL) {
6587 #if ZIPPY
6588         if (appData.zippyPlay && first.initDone) {
6589             /* Relay offer to ICS */
6590             SendToICS(ics_prefix);
6591             SendToICS("draw\n");
6592         }
6593 #endif
6594         cps->offeredDraw = 2; /* valid until this engine moves twice */
6595         if (gameMode == TwoMachinesPlay) {
6596             if (cps->other->offeredDraw) {
6597                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6598             /* [HGM] in two-machine mode we delay relaying draw offer      */
6599             /* until after we also have move, to see if it is really claim */
6600             }
6601         } else if (gameMode == MachinePlaysWhite ||
6602                    gameMode == MachinePlaysBlack) {
6603           if (userOfferedDraw) {
6604             DisplayInformation(_("Machine accepts your draw offer"));
6605             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6606           } else {
6607             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6608           }
6609         }
6610     }
6611
6612     
6613     /*
6614      * Look for thinking output
6615      */
6616     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6617           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6618                                 ) {
6619         int plylev, mvleft, mvtot, curscore, time;
6620         char mvname[MOVE_LEN];
6621         u64 nodes; // [DM]
6622         char plyext;
6623         int ignore = FALSE;
6624         int prefixHint = FALSE;
6625         mvname[0] = NULLCHAR;
6626
6627         switch (gameMode) {
6628           case MachinePlaysBlack:
6629           case IcsPlayingBlack:
6630             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6631             break;
6632           case MachinePlaysWhite:
6633           case IcsPlayingWhite:
6634             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6635             break;
6636           case AnalyzeMode:
6637           case AnalyzeFile:
6638             break;
6639           case IcsObserving: /* [DM] icsEngineAnalyze */
6640             if (!appData.icsEngineAnalyze) ignore = TRUE;
6641             break;
6642           case TwoMachinesPlay:
6643             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6644                 ignore = TRUE;
6645             }
6646             break;
6647           default:
6648             ignore = TRUE;
6649             break;
6650         }
6651
6652         if (!ignore) {
6653             buf1[0] = NULLCHAR;
6654             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6655                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6656
6657                 if (plyext != ' ' && plyext != '\t') {
6658                     time *= 100;
6659                 }
6660
6661                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6662                 if( cps->scoreIsAbsolute && 
6663                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6664                 {
6665                     curscore = -curscore;
6666                 }
6667
6668
6669                 programStats.depth = plylev;
6670                 programStats.nodes = nodes;
6671                 programStats.time = time;
6672                 programStats.score = curscore;
6673                 programStats.got_only_move = 0;
6674
6675                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6676                         int ticklen;
6677
6678                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6679                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6680                         if(WhiteOnMove(forwardMostMove)) 
6681                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6682                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6683                 }
6684
6685                 /* Buffer overflow protection */
6686                 if (buf1[0] != NULLCHAR) {
6687                     if (strlen(buf1) >= sizeof(programStats.movelist)
6688                         && appData.debugMode) {
6689                         fprintf(debugFP,
6690                                 "PV is too long; using the first %d bytes.\n",
6691                                 sizeof(programStats.movelist) - 1);
6692                     }
6693
6694                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6695                 } else {
6696                     sprintf(programStats.movelist, " no PV\n");
6697                 }
6698
6699                 if (programStats.seen_stat) {
6700                     programStats.ok_to_send = 1;
6701                 }
6702
6703                 if (strchr(programStats.movelist, '(') != NULL) {
6704                     programStats.line_is_book = 1;
6705                     programStats.nr_moves = 0;
6706                     programStats.moves_left = 0;
6707                 } else {
6708                     programStats.line_is_book = 0;
6709                 }
6710
6711                 SendProgramStatsToFrontend( cps, &programStats );
6712
6713                 /* 
6714                     [AS] Protect the thinkOutput buffer from overflow... this
6715                     is only useful if buf1 hasn't overflowed first!
6716                 */
6717                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6718                         plylev, 
6719                         (gameMode == TwoMachinesPlay ?
6720                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6721                         ((double) curscore) / 100.0,
6722                         prefixHint ? lastHint : "",
6723                         prefixHint ? " " : "" );
6724
6725                 if( buf1[0] != NULLCHAR ) {
6726                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6727
6728                     if( strlen(buf1) > max_len ) {
6729                         if( appData.debugMode) {
6730                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6731                         }
6732                         buf1[max_len+1] = '\0';
6733                     }
6734
6735                     strcat( thinkOutput, buf1 );
6736                 }
6737
6738                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6739                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6740                     DisplayMove(currentMove - 1);
6741                 }
6742                 return;
6743
6744             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6745                 /* crafty (9.25+) says "(only move) <move>"
6746                  * if there is only 1 legal move
6747                  */
6748                 sscanf(p, "(only move) %s", buf1);
6749                 sprintf(thinkOutput, "%s (only move)", buf1);
6750                 sprintf(programStats.movelist, "%s (only move)", buf1);
6751                 programStats.depth = 1;
6752                 programStats.nr_moves = 1;
6753                 programStats.moves_left = 1;
6754                 programStats.nodes = 1;
6755                 programStats.time = 1;
6756                 programStats.got_only_move = 1;
6757
6758                 /* Not really, but we also use this member to
6759                    mean "line isn't going to change" (Crafty
6760                    isn't searching, so stats won't change) */
6761                 programStats.line_is_book = 1;
6762
6763                 SendProgramStatsToFrontend( cps, &programStats );
6764                 
6765                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6766                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6767                     DisplayMove(currentMove - 1);
6768                 }
6769                 return;
6770             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6771                               &time, &nodes, &plylev, &mvleft,
6772                               &mvtot, mvname) >= 5) {
6773                 /* The stat01: line is from Crafty (9.29+) in response
6774                    to the "." command */
6775                 programStats.seen_stat = 1;
6776                 cps->maybeThinking = TRUE;
6777
6778                 if (programStats.got_only_move || !appData.periodicUpdates)
6779                   return;
6780
6781                 programStats.depth = plylev;
6782                 programStats.time = time;
6783                 programStats.nodes = nodes;
6784                 programStats.moves_left = mvleft;
6785                 programStats.nr_moves = mvtot;
6786                 strcpy(programStats.move_name, mvname);
6787                 programStats.ok_to_send = 1;
6788                 programStats.movelist[0] = '\0';
6789
6790                 SendProgramStatsToFrontend( cps, &programStats );
6791
6792                 return;
6793
6794             } else if (strncmp(message,"++",2) == 0) {
6795                 /* Crafty 9.29+ outputs this */
6796                 programStats.got_fail = 2;
6797                 return;
6798
6799             } else if (strncmp(message,"--",2) == 0) {
6800                 /* Crafty 9.29+ outputs this */
6801                 programStats.got_fail = 1;
6802                 return;
6803
6804             } else if (thinkOutput[0] != NULLCHAR &&
6805                        strncmp(message, "    ", 4) == 0) {
6806                 unsigned message_len;
6807
6808                 p = message;
6809                 while (*p && *p == ' ') p++;
6810
6811                 message_len = strlen( p );
6812
6813                 /* [AS] Avoid buffer overflow */
6814                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6815                     strcat(thinkOutput, " ");
6816                     strcat(thinkOutput, p);
6817                 }
6818
6819                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6820                     strcat(programStats.movelist, " ");
6821                     strcat(programStats.movelist, p);
6822                 }
6823
6824                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6825                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6826                     DisplayMove(currentMove - 1);
6827                 }
6828                 return;
6829             }
6830         }
6831         else {
6832             buf1[0] = NULLCHAR;
6833
6834             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6835                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6836             {
6837                 ChessProgramStats cpstats;
6838
6839                 if (plyext != ' ' && plyext != '\t') {
6840                     time *= 100;
6841                 }
6842
6843                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6844                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6845                     curscore = -curscore;
6846                 }
6847
6848                 cpstats.depth = plylev;
6849                 cpstats.nodes = nodes;
6850                 cpstats.time = time;
6851                 cpstats.score = curscore;
6852                 cpstats.got_only_move = 0;
6853                 cpstats.movelist[0] = '\0';
6854
6855                 if (buf1[0] != NULLCHAR) {
6856                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6857                 }
6858
6859                 cpstats.ok_to_send = 0;
6860                 cpstats.line_is_book = 0;
6861                 cpstats.nr_moves = 0;
6862                 cpstats.moves_left = 0;
6863
6864                 SendProgramStatsToFrontend( cps, &cpstats );
6865             }
6866         }
6867     }
6868 }
6869
6870
6871 /* Parse a game score from the character string "game", and
6872    record it as the history of the current game.  The game
6873    score is NOT assumed to start from the standard position. 
6874    The display is not updated in any way.
6875    */
6876 void
6877 ParseGameHistory(game)
6878      char *game;
6879 {
6880     ChessMove moveType;
6881     int fromX, fromY, toX, toY, boardIndex;
6882     char promoChar;
6883     char *p, *q;
6884     char buf[MSG_SIZ];
6885
6886     if (appData.debugMode)
6887       fprintf(debugFP, "Parsing game history: %s\n", game);
6888
6889     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6890     gameInfo.site = StrSave(appData.icsHost);
6891     gameInfo.date = PGNDate();
6892     gameInfo.round = StrSave("-");
6893
6894     /* Parse out names of players */
6895     while (*game == ' ') game++;
6896     p = buf;
6897     while (*game != ' ') *p++ = *game++;
6898     *p = NULLCHAR;
6899     gameInfo.white = StrSave(buf);
6900     while (*game == ' ') game++;
6901     p = buf;
6902     while (*game != ' ' && *game != '\n') *p++ = *game++;
6903     *p = NULLCHAR;
6904     gameInfo.black = StrSave(buf);
6905
6906     /* Parse moves */
6907     boardIndex = blackPlaysFirst ? 1 : 0;
6908     yynewstr(game);
6909     for (;;) {
6910         yyboardindex = boardIndex;
6911         moveType = (ChessMove) yylex();
6912         switch (moveType) {
6913           case IllegalMove:             /* maybe suicide chess, etc. */
6914   if (appData.debugMode) {
6915     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6916     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6917     setbuf(debugFP, NULL);
6918   }
6919           case WhitePromotionChancellor:
6920           case BlackPromotionChancellor:
6921           case WhitePromotionArchbishop:
6922           case BlackPromotionArchbishop:
6923           case WhitePromotionQueen:
6924           case BlackPromotionQueen:
6925           case WhitePromotionRook:
6926           case BlackPromotionRook:
6927           case WhitePromotionBishop:
6928           case BlackPromotionBishop:
6929           case WhitePromotionKnight:
6930           case BlackPromotionKnight:
6931           case WhitePromotionKing:
6932           case BlackPromotionKing:
6933           case NormalMove:
6934           case WhiteCapturesEnPassant:
6935           case BlackCapturesEnPassant:
6936           case WhiteKingSideCastle:
6937           case WhiteQueenSideCastle:
6938           case BlackKingSideCastle:
6939           case BlackQueenSideCastle:
6940           case WhiteKingSideCastleWild:
6941           case WhiteQueenSideCastleWild:
6942           case BlackKingSideCastleWild:
6943           case BlackQueenSideCastleWild:
6944           /* PUSH Fabien */
6945           case WhiteHSideCastleFR:
6946           case WhiteASideCastleFR:
6947           case BlackHSideCastleFR:
6948           case BlackASideCastleFR:
6949           /* POP Fabien */
6950             fromX = currentMoveString[0] - AAA;
6951             fromY = currentMoveString[1] - ONE;
6952             toX = currentMoveString[2] - AAA;
6953             toY = currentMoveString[3] - ONE;
6954             promoChar = currentMoveString[4];
6955             break;
6956           case WhiteDrop:
6957           case BlackDrop:
6958             fromX = moveType == WhiteDrop ?
6959               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6960             (int) CharToPiece(ToLower(currentMoveString[0]));
6961             fromY = DROP_RANK;
6962             toX = currentMoveString[2] - AAA;
6963             toY = currentMoveString[3] - ONE;
6964             promoChar = NULLCHAR;
6965             break;
6966           case AmbiguousMove:
6967             /* bug? */
6968             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6969   if (appData.debugMode) {
6970     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6971     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6972     setbuf(debugFP, NULL);
6973   }
6974             DisplayError(buf, 0);
6975             return;
6976           case ImpossibleMove:
6977             /* bug? */
6978             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6979   if (appData.debugMode) {
6980     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6981     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6982     setbuf(debugFP, NULL);
6983   }
6984             DisplayError(buf, 0);
6985             return;
6986           case (ChessMove) 0:   /* end of file */
6987             if (boardIndex < backwardMostMove) {
6988                 /* Oops, gap.  How did that happen? */
6989                 DisplayError(_("Gap in move list"), 0);
6990                 return;
6991             }
6992             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6993             if (boardIndex > forwardMostMove) {
6994                 forwardMostMove = boardIndex;
6995             }
6996             return;
6997           case ElapsedTime:
6998             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6999                 strcat(parseList[boardIndex-1], " ");
7000                 strcat(parseList[boardIndex-1], yy_text);
7001             }
7002             continue;
7003           case Comment:
7004           case PGNTag:
7005           case NAG:
7006           default:
7007             /* ignore */
7008             continue;
7009           case WhiteWins:
7010           case BlackWins:
7011           case GameIsDrawn:
7012           case GameUnfinished:
7013             if (gameMode == IcsExamining) {
7014                 if (boardIndex < backwardMostMove) {
7015                     /* Oops, gap.  How did that happen? */
7016                     return;
7017                 }
7018                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7019                 return;
7020             }
7021             gameInfo.result = moveType;
7022             p = strchr(yy_text, '{');
7023             if (p == NULL) p = strchr(yy_text, '(');
7024             if (p == NULL) {
7025                 p = yy_text;
7026                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7027             } else {
7028                 q = strchr(p, *p == '{' ? '}' : ')');
7029                 if (q != NULL) *q = NULLCHAR;
7030                 p++;
7031             }
7032             gameInfo.resultDetails = StrSave(p);
7033             continue;
7034         }
7035         if (boardIndex >= forwardMostMove &&
7036             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7037             backwardMostMove = blackPlaysFirst ? 1 : 0;
7038             return;
7039         }
7040         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7041                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7042                                  parseList[boardIndex]);
7043         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7044         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7045         /* currentMoveString is set as a side-effect of yylex */
7046         strcpy(moveList[boardIndex], currentMoveString);
7047         strcat(moveList[boardIndex], "\n");
7048         boardIndex++;
7049         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7050                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7051         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7052                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7053           case MT_NONE:
7054           case MT_STALEMATE:
7055           default:
7056             break;
7057           case MT_CHECK:
7058             if(gameInfo.variant != VariantShogi)
7059                 strcat(parseList[boardIndex - 1], "+");
7060             break;
7061           case MT_CHECKMATE:
7062           case MT_STAINMATE:
7063             strcat(parseList[boardIndex - 1], "#");
7064             break;
7065         }
7066     }
7067 }
7068
7069
7070 /* Apply a move to the given board  */
7071 void
7072 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7073      int fromX, fromY, toX, toY;
7074      int promoChar;
7075      Board board;
7076      char *castling;
7077      char *ep;
7078 {
7079   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7080
7081     /* [HGM] compute & store e.p. status and castling rights for new position */
7082     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7083     { int i;
7084
7085       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7086       oldEP = *ep;
7087       *ep = EP_NONE;
7088
7089       if( board[toY][toX] != EmptySquare ) 
7090            *ep = EP_CAPTURE;  
7091
7092       if( board[fromY][fromX] == WhitePawn ) {
7093            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7094                *ep = EP_PAWN_MOVE;
7095            if( toY-fromY==2) {
7096                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7097                         gameInfo.variant != VariantBerolina || toX < fromX)
7098                       *ep = toX | berolina;
7099                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7100                         gameInfo.variant != VariantBerolina || toX > fromX) 
7101                       *ep = toX;
7102            }
7103       } else 
7104       if( board[fromY][fromX] == BlackPawn ) {
7105            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7106                *ep = EP_PAWN_MOVE; 
7107            if( toY-fromY== -2) {
7108                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7109                         gameInfo.variant != VariantBerolina || toX < fromX)
7110                       *ep = toX | berolina;
7111                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7112                         gameInfo.variant != VariantBerolina || toX > fromX) 
7113                       *ep = toX;
7114            }
7115        }
7116
7117        for(i=0; i<nrCastlingRights; i++) {
7118            if(castling[i] == fromX && castlingRank[i] == fromY ||
7119               castling[i] == toX   && castlingRank[i] == toY   
7120              ) castling[i] = -1; // revoke for moved or captured piece
7121        }
7122
7123     }
7124
7125   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7126   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7127        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7128          
7129   if (fromX == toX && fromY == toY) return;
7130
7131   if (fromY == DROP_RANK) {
7132         /* must be first */
7133         piece = board[toY][toX] = (ChessSquare) fromX;
7134   } else {
7135      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7136      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7137      if(gameInfo.variant == VariantKnightmate)
7138          king += (int) WhiteUnicorn - (int) WhiteKing;
7139
7140     /* Code added by Tord: */
7141     /* FRC castling assumed when king captures friendly rook. */
7142     if (board[fromY][fromX] == WhiteKing &&
7143              board[toY][toX] == WhiteRook) {
7144       board[fromY][fromX] = EmptySquare;
7145       board[toY][toX] = EmptySquare;
7146       if(toX > fromX) {
7147         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7148       } else {
7149         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7150       }
7151     } else if (board[fromY][fromX] == BlackKing &&
7152                board[toY][toX] == BlackRook) {
7153       board[fromY][fromX] = EmptySquare;
7154       board[toY][toX] = EmptySquare;
7155       if(toX > fromX) {
7156         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7157       } else {
7158         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7159       }
7160     /* End of code added by Tord */
7161
7162     } else if (board[fromY][fromX] == king
7163         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7164         && toY == fromY && toX > fromX+1) {
7165         board[fromY][fromX] = EmptySquare;
7166         board[toY][toX] = king;
7167         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7168         board[fromY][BOARD_RGHT-1] = EmptySquare;
7169     } else if (board[fromY][fromX] == king
7170         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7171                && toY == fromY && toX < fromX-1) {
7172         board[fromY][fromX] = EmptySquare;
7173         board[toY][toX] = king;
7174         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7175         board[fromY][BOARD_LEFT] = EmptySquare;
7176     } else if (board[fromY][fromX] == WhitePawn
7177                && toY == BOARD_HEIGHT-1
7178                && gameInfo.variant != VariantXiangqi
7179                ) {
7180         /* white pawn promotion */
7181         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7182         if (board[toY][toX] == EmptySquare) {
7183             board[toY][toX] = WhiteQueen;
7184         }
7185         if(gameInfo.variant==VariantBughouse ||
7186            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7187             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7188         board[fromY][fromX] = EmptySquare;
7189     } else if ((fromY == BOARD_HEIGHT-4)
7190                && (toX != fromX)
7191                && gameInfo.variant != VariantXiangqi
7192                && gameInfo.variant != VariantBerolina
7193                && (board[fromY][fromX] == WhitePawn)
7194                && (board[toY][toX] == EmptySquare)) {
7195         board[fromY][fromX] = EmptySquare;
7196         board[toY][toX] = WhitePawn;
7197         captured = board[toY - 1][toX];
7198         board[toY - 1][toX] = EmptySquare;
7199     } else if ((fromY == BOARD_HEIGHT-4)
7200                && (toX == fromX)
7201                && gameInfo.variant == VariantBerolina
7202                && (board[fromY][fromX] == WhitePawn)
7203                && (board[toY][toX] == EmptySquare)) {
7204         board[fromY][fromX] = EmptySquare;
7205         board[toY][toX] = WhitePawn;
7206         if(oldEP & EP_BEROLIN_A) {
7207                 captured = board[fromY][fromX-1];
7208                 board[fromY][fromX-1] = EmptySquare;
7209         }else{  captured = board[fromY][fromX+1];
7210                 board[fromY][fromX+1] = EmptySquare;
7211         }
7212     } else if (board[fromY][fromX] == king
7213         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7214                && toY == fromY && toX > fromX+1) {
7215         board[fromY][fromX] = EmptySquare;
7216         board[toY][toX] = king;
7217         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7218         board[fromY][BOARD_RGHT-1] = EmptySquare;
7219     } else if (board[fromY][fromX] == king
7220         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7221                && toY == fromY && toX < fromX-1) {
7222         board[fromY][fromX] = EmptySquare;
7223         board[toY][toX] = king;
7224         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7225         board[fromY][BOARD_LEFT] = EmptySquare;
7226     } else if (fromY == 7 && fromX == 3
7227                && board[fromY][fromX] == BlackKing
7228                && toY == 7 && toX == 5) {
7229         board[fromY][fromX] = EmptySquare;
7230         board[toY][toX] = BlackKing;
7231         board[fromY][7] = EmptySquare;
7232         board[toY][4] = BlackRook;
7233     } else if (fromY == 7 && fromX == 3
7234                && board[fromY][fromX] == BlackKing
7235                && toY == 7 && toX == 1) {
7236         board[fromY][fromX] = EmptySquare;
7237         board[toY][toX] = BlackKing;
7238         board[fromY][0] = EmptySquare;
7239         board[toY][2] = BlackRook;
7240     } else if (board[fromY][fromX] == BlackPawn
7241                && toY == 0
7242                && gameInfo.variant != VariantXiangqi
7243                ) {
7244         /* black pawn promotion */
7245         board[0][toX] = CharToPiece(ToLower(promoChar));
7246         if (board[0][toX] == EmptySquare) {
7247             board[0][toX] = BlackQueen;
7248         }
7249         if(gameInfo.variant==VariantBughouse ||
7250            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7251             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7252         board[fromY][fromX] = EmptySquare;
7253     } else if ((fromY == 3)
7254                && (toX != fromX)
7255                && gameInfo.variant != VariantXiangqi
7256                && gameInfo.variant != VariantBerolina
7257                && (board[fromY][fromX] == BlackPawn)
7258                && (board[toY][toX] == EmptySquare)) {
7259         board[fromY][fromX] = EmptySquare;
7260         board[toY][toX] = BlackPawn;
7261         captured = board[toY + 1][toX];
7262         board[toY + 1][toX] = EmptySquare;
7263     } else if ((fromY == 3)
7264                && (toX == fromX)
7265                && gameInfo.variant == VariantBerolina
7266                && (board[fromY][fromX] == BlackPawn)
7267                && (board[toY][toX] == EmptySquare)) {
7268         board[fromY][fromX] = EmptySquare;
7269         board[toY][toX] = BlackPawn;
7270         if(oldEP & EP_BEROLIN_A) {
7271                 captured = board[fromY][fromX-1];
7272                 board[fromY][fromX-1] = EmptySquare;
7273         }else{  captured = board[fromY][fromX+1];
7274                 board[fromY][fromX+1] = EmptySquare;
7275         }
7276     } else {
7277         board[toY][toX] = board[fromY][fromX];
7278         board[fromY][fromX] = EmptySquare;
7279     }
7280
7281     /* [HGM] now we promote for Shogi, if needed */
7282     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7283         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7284   }
7285
7286     if (gameInfo.holdingsWidth != 0) {
7287
7288       /* !!A lot more code needs to be written to support holdings  */
7289       /* [HGM] OK, so I have written it. Holdings are stored in the */
7290       /* penultimate board files, so they are automaticlly stored   */
7291       /* in the game history.                                       */
7292       if (fromY == DROP_RANK) {
7293         /* Delete from holdings, by decreasing count */
7294         /* and erasing image if necessary            */
7295         p = (int) fromX;
7296         if(p < (int) BlackPawn) { /* white drop */
7297              p -= (int)WhitePawn;
7298              if(p >= gameInfo.holdingsSize) p = 0;
7299              if(--board[p][BOARD_WIDTH-2] == 0)
7300                   board[p][BOARD_WIDTH-1] = EmptySquare;
7301         } else {                  /* black drop */
7302              p -= (int)BlackPawn;
7303              if(p >= gameInfo.holdingsSize) p = 0;
7304              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7305                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7306         }
7307       }
7308       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7309           && gameInfo.variant != VariantBughouse        ) {
7310         /* [HGM] holdings: Add to holdings, if holdings exist */
7311         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7312                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7313                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7314         }
7315         p = (int) captured;
7316         if (p >= (int) BlackPawn) {
7317           p -= (int)BlackPawn;
7318           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7319                   /* in Shogi restore piece to its original  first */
7320                   captured = (ChessSquare) (DEMOTED captured);
7321                   p = DEMOTED p;
7322           }
7323           p = PieceToNumber((ChessSquare)p);
7324           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7325           board[p][BOARD_WIDTH-2]++;
7326           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7327         } else {
7328           p -= (int)WhitePawn;
7329           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7330                   captured = (ChessSquare) (DEMOTED captured);
7331                   p = DEMOTED p;
7332           }
7333           p = PieceToNumber((ChessSquare)p);
7334           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7335           board[BOARD_HEIGHT-1-p][1]++;
7336           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7337         }
7338       }
7339
7340     } else if (gameInfo.variant == VariantAtomic) {
7341       if (captured != EmptySquare) {
7342         int y, x;
7343         for (y = toY-1; y <= toY+1; y++) {
7344           for (x = toX-1; x <= toX+1; x++) {
7345             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7346                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7347               board[y][x] = EmptySquare;
7348             }
7349           }
7350         }
7351         board[toY][toX] = EmptySquare;
7352       }
7353     }
7354     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7355         /* [HGM] Shogi promotions */
7356         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7357     }
7358
7359     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7360                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7361         // [HGM] superchess: take promotion piece out of holdings
7362         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7363         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7364             if(!--board[k][BOARD_WIDTH-2])
7365                 board[k][BOARD_WIDTH-1] = EmptySquare;
7366         } else {
7367             if(!--board[BOARD_HEIGHT-1-k][1])
7368                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7369         }
7370     }
7371
7372 }
7373
7374 /* Updates forwardMostMove */
7375 void
7376 MakeMove(fromX, fromY, toX, toY, promoChar)
7377      int fromX, fromY, toX, toY;
7378      int promoChar;
7379 {
7380 //    forwardMostMove++; // [HGM] bare: moved downstream
7381
7382     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7383         int timeLeft; static int lastLoadFlag=0; int king, piece;
7384         piece = boards[forwardMostMove][fromY][fromX];
7385         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7386         if(gameInfo.variant == VariantKnightmate)
7387             king += (int) WhiteUnicorn - (int) WhiteKing;
7388         if(forwardMostMove == 0) {
7389             if(blackPlaysFirst) 
7390                 fprintf(serverMoves, "%s;", second.tidy);
7391             fprintf(serverMoves, "%s;", first.tidy);
7392             if(!blackPlaysFirst) 
7393                 fprintf(serverMoves, "%s;", second.tidy);
7394         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7395         lastLoadFlag = loadFlag;
7396         // print base move
7397         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7398         // print castling suffix
7399         if( toY == fromY && piece == king ) {
7400             if(toX-fromX > 1)
7401                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7402             if(fromX-toX >1)
7403                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7404         }
7405         // e.p. suffix
7406         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7407              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7408              boards[forwardMostMove][toY][toX] == EmptySquare
7409              && fromX != toX )
7410                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7411         // promotion suffix
7412         if(promoChar != NULLCHAR)
7413                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7414         if(!loadFlag) {
7415             fprintf(serverMoves, "/%d/%d",
7416                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7417             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7418             else                      timeLeft = blackTimeRemaining/1000;
7419             fprintf(serverMoves, "/%d", timeLeft);
7420         }
7421         fflush(serverMoves);
7422     }
7423
7424     if (forwardMostMove+1 >= MAX_MOVES) {
7425       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7426                         0, 1);
7427       return;
7428     }
7429     if (commentList[forwardMostMove+1] != NULL) {
7430         free(commentList[forwardMostMove+1]);
7431         commentList[forwardMostMove+1] = NULL;
7432     }
7433     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7434     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7435     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7436                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7437     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7438     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7439     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7440     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7441     gameInfo.result = GameUnfinished;
7442     if (gameInfo.resultDetails != NULL) {
7443         free(gameInfo.resultDetails);
7444         gameInfo.resultDetails = NULL;
7445     }
7446     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7447                               moveList[forwardMostMove - 1]);
7448     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7449                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7450                              fromY, fromX, toY, toX, promoChar,
7451                              parseList[forwardMostMove - 1]);
7452     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7453                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7454                             castlingRights[forwardMostMove]) ) {
7455       case MT_NONE:
7456       case MT_STALEMATE:
7457       default:
7458         break;
7459       case MT_CHECK:
7460         if(gameInfo.variant != VariantShogi)
7461             strcat(parseList[forwardMostMove - 1], "+");
7462         break;
7463       case MT_CHECKMATE:
7464       case MT_STAINMATE:
7465         strcat(parseList[forwardMostMove - 1], "#");
7466         break;
7467     }
7468     if (appData.debugMode) {
7469         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7470     }
7471
7472 }
7473
7474 /* Updates currentMove if not pausing */
7475 void
7476 ShowMove(fromX, fromY, toX, toY)
7477 {
7478     int instant = (gameMode == PlayFromGameFile) ?
7479         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7480     if(appData.noGUI) return;
7481     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7482         if (!instant) {
7483             if (forwardMostMove == currentMove + 1) {
7484                 AnimateMove(boards[forwardMostMove - 1],
7485                             fromX, fromY, toX, toY);
7486             }
7487             if (appData.highlightLastMove) {
7488                 SetHighlights(fromX, fromY, toX, toY);
7489             }
7490         }
7491         currentMove = forwardMostMove;
7492     }
7493
7494     if (instant) return;
7495
7496     DisplayMove(currentMove - 1);
7497     DrawPosition(FALSE, boards[currentMove]);
7498     DisplayBothClocks();
7499     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7500 }
7501
7502 void SendEgtPath(ChessProgramState *cps)
7503 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7504         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7505
7506         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7507
7508         while(*p) {
7509             char c, *q = name+1, *r, *s;
7510
7511             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7512             while(*p && *p != ',') *q++ = *p++;
7513             *q++ = ':'; *q = 0;
7514             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7515                 strcmp(name, ",nalimov:") == 0 ) {
7516                 // take nalimov path from the menu-changeable option first, if it is defined
7517                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7518                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7519             } else
7520             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7521                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7522                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7523                 s = r = StrStr(s, ":") + 1; // beginning of path info
7524                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7525                 c = *r; *r = 0;             // temporarily null-terminate path info
7526                     *--q = 0;               // strip of trailig ':' from name
7527                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7528                 *r = c;
7529                 SendToProgram(buf,cps);     // send egtbpath command for this format
7530             }
7531             if(*p == ',') p++; // read away comma to position for next format name
7532         }
7533 }
7534
7535 void
7536 InitChessProgram(cps, setup)
7537      ChessProgramState *cps;
7538      int setup; /* [HGM] needed to setup FRC opening position */
7539 {
7540     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7541     if (appData.noChessProgram) return;
7542     hintRequested = FALSE;
7543     bookRequested = FALSE;
7544
7545     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7546     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7547     if(cps->memSize) { /* [HGM] memory */
7548         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7549         SendToProgram(buf, cps);
7550     }
7551     SendEgtPath(cps); /* [HGM] EGT */
7552     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7553         sprintf(buf, "cores %d\n", appData.smpCores);
7554         SendToProgram(buf, cps);
7555     }
7556
7557     SendToProgram(cps->initString, cps);
7558     if (gameInfo.variant != VariantNormal &&
7559         gameInfo.variant != VariantLoadable
7560         /* [HGM] also send variant if board size non-standard */
7561         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7562                                             ) {
7563       char *v = VariantName(gameInfo.variant);
7564       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7565         /* [HGM] in protocol 1 we have to assume all variants valid */
7566         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7567         DisplayFatalError(buf, 0, 1);
7568         return;
7569       }
7570
7571       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7572       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7573       if( gameInfo.variant == VariantXiangqi )
7574            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7575       if( gameInfo.variant == VariantShogi )
7576            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7577       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7578            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7579       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7580                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7581            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7582       if( gameInfo.variant == VariantCourier )
7583            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7584       if( gameInfo.variant == VariantSuper )
7585            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7586       if( gameInfo.variant == VariantGreat )
7587            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7588
7589       if(overruled) {
7590            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7591                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7592            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7593            if(StrStr(cps->variants, b) == NULL) { 
7594                // specific sized variant not known, check if general sizing allowed
7595                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7596                    if(StrStr(cps->variants, "boardsize") == NULL) {
7597                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7598                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7599                        DisplayFatalError(buf, 0, 1);
7600                        return;
7601                    }
7602                    /* [HGM] here we really should compare with the maximum supported board size */
7603                }
7604            }
7605       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7606       sprintf(buf, "variant %s\n", b);
7607       SendToProgram(buf, cps);
7608     }
7609     currentlyInitializedVariant = gameInfo.variant;
7610
7611     /* [HGM] send opening position in FRC to first engine */
7612     if(setup) {
7613           SendToProgram("force\n", cps);
7614           SendBoard(cps, 0);
7615           /* engine is now in force mode! Set flag to wake it up after first move. */
7616           setboardSpoiledMachineBlack = 1;
7617     }
7618
7619     if (cps->sendICS) {
7620       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7621       SendToProgram(buf, cps);
7622     }
7623     cps->maybeThinking = FALSE;
7624     cps->offeredDraw = 0;
7625     if (!appData.icsActive) {
7626         SendTimeControl(cps, movesPerSession, timeControl,
7627                         timeIncrement, appData.searchDepth,
7628                         searchTime);
7629     }
7630     if (appData.showThinking 
7631         // [HGM] thinking: four options require thinking output to be sent
7632         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7633                                 ) {
7634         SendToProgram("post\n", cps);
7635     }
7636     SendToProgram("hard\n", cps);
7637     if (!appData.ponderNextMove) {
7638         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7639            it without being sure what state we are in first.  "hard"
7640            is not a toggle, so that one is OK.
7641          */
7642         SendToProgram("easy\n", cps);
7643     }
7644     if (cps->usePing) {
7645       sprintf(buf, "ping %d\n", ++cps->lastPing);
7646       SendToProgram(buf, cps);
7647     }
7648     cps->initDone = TRUE;
7649 }   
7650
7651
7652 void
7653 StartChessProgram(cps)
7654      ChessProgramState *cps;
7655 {
7656     char buf[MSG_SIZ];
7657     int err;
7658
7659     if (appData.noChessProgram) return;
7660     cps->initDone = FALSE;
7661
7662     if (strcmp(cps->host, "localhost") == 0) {
7663         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7664     } else if (*appData.remoteShell == NULLCHAR) {
7665         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7666     } else {
7667         if (*appData.remoteUser == NULLCHAR) {
7668           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7669                     cps->program);
7670         } else {
7671           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7672                     cps->host, appData.remoteUser, cps->program);
7673         }
7674         err = StartChildProcess(buf, "", &cps->pr);
7675     }
7676     
7677     if (err != 0) {
7678         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7679         DisplayFatalError(buf, err, 1);
7680         cps->pr = NoProc;
7681         cps->isr = NULL;
7682         return;
7683     }
7684     
7685     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7686     if (cps->protocolVersion > 1) {
7687       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7688       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7689       cps->comboCnt = 0;  //                and values of combo boxes
7690       SendToProgram(buf, cps);
7691     } else {
7692       SendToProgram("xboard\n", cps);
7693     }
7694 }
7695
7696
7697 void
7698 TwoMachinesEventIfReady P((void))
7699 {
7700   if (first.lastPing != first.lastPong) {
7701     DisplayMessage("", _("Waiting for first chess program"));
7702     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7703     return;
7704   }
7705   if (second.lastPing != second.lastPong) {
7706     DisplayMessage("", _("Waiting for second chess program"));
7707     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7708     return;
7709   }
7710   ThawUI();
7711   TwoMachinesEvent();
7712 }
7713
7714 void
7715 NextMatchGame P((void))
7716 {
7717     int index; /* [HGM] autoinc: step lod index during match */
7718     Reset(FALSE, TRUE);
7719     if (*appData.loadGameFile != NULLCHAR) {
7720         index = appData.loadGameIndex;
7721         if(index < 0) { // [HGM] autoinc
7722             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7723             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7724         } 
7725         LoadGameFromFile(appData.loadGameFile,
7726                          index,
7727                          appData.loadGameFile, FALSE);
7728     } else if (*appData.loadPositionFile != NULLCHAR) {
7729         index = appData.loadPositionIndex;
7730         if(index < 0) { // [HGM] autoinc
7731             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7732             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7733         } 
7734         LoadPositionFromFile(appData.loadPositionFile,
7735                              index,
7736                              appData.loadPositionFile);
7737     }
7738     TwoMachinesEventIfReady();
7739 }
7740
7741 void UserAdjudicationEvent( int result )
7742 {
7743     ChessMove gameResult = GameIsDrawn;
7744
7745     if( result > 0 ) {
7746         gameResult = WhiteWins;
7747     }
7748     else if( result < 0 ) {
7749         gameResult = BlackWins;
7750     }
7751
7752     if( gameMode == TwoMachinesPlay ) {
7753         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7754     }
7755 }
7756
7757
7758 // [HGM] save: calculate checksum of game to make games easily identifiable
7759 int StringCheckSum(char *s)
7760 {
7761         int i = 0;
7762         if(s==NULL) return 0;
7763         while(*s) i = i*259 + *s++;
7764         return i;
7765 }
7766
7767 int GameCheckSum()
7768 {
7769         int i, sum=0;
7770         for(i=backwardMostMove; i<forwardMostMove; i++) {
7771                 sum += pvInfoList[i].depth;
7772                 sum += StringCheckSum(parseList[i]);
7773                 sum += StringCheckSum(commentList[i]);
7774                 sum *= 261;
7775         }
7776         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7777         return sum + StringCheckSum(commentList[i]);
7778 } // end of save patch
7779
7780 void
7781 GameEnds(result, resultDetails, whosays)
7782      ChessMove result;
7783      char *resultDetails;
7784      int whosays;
7785 {
7786     GameMode nextGameMode;
7787     int isIcsGame;
7788     char buf[MSG_SIZ];
7789
7790     if(endingGame) return; /* [HGM] crash: forbid recursion */
7791     endingGame = 1;
7792
7793     if (appData.debugMode) {
7794       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7795               result, resultDetails ? resultDetails : "(null)", whosays);
7796     }
7797
7798     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7799         /* If we are playing on ICS, the server decides when the
7800            game is over, but the engine can offer to draw, claim 
7801            a draw, or resign. 
7802          */
7803 #if ZIPPY
7804         if (appData.zippyPlay && first.initDone) {
7805             if (result == GameIsDrawn) {
7806                 /* In case draw still needs to be claimed */
7807                 SendToICS(ics_prefix);
7808                 SendToICS("draw\n");
7809             } else if (StrCaseStr(resultDetails, "resign")) {
7810                 SendToICS(ics_prefix);
7811                 SendToICS("resign\n");
7812             }
7813         }
7814 #endif
7815         endingGame = 0; /* [HGM] crash */
7816         return;
7817     }
7818
7819     /* If we're loading the game from a file, stop */
7820     if (whosays == GE_FILE) {
7821       (void) StopLoadGameTimer();
7822       gameFileFP = NULL;
7823     }
7824
7825     /* Cancel draw offers */
7826     first.offeredDraw = second.offeredDraw = 0;
7827
7828     /* If this is an ICS game, only ICS can really say it's done;
7829        if not, anyone can. */
7830     isIcsGame = (gameMode == IcsPlayingWhite || 
7831                  gameMode == IcsPlayingBlack || 
7832                  gameMode == IcsObserving    || 
7833                  gameMode == IcsExamining);
7834
7835     if (!isIcsGame || whosays == GE_ICS) {
7836         /* OK -- not an ICS game, or ICS said it was done */
7837         StopClocks();
7838         if (!isIcsGame && !appData.noChessProgram) 
7839           SetUserThinkingEnables();
7840     
7841         /* [HGM] if a machine claims the game end we verify this claim */
7842         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7843             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7844                 char claimer;
7845                 ChessMove trueResult = (ChessMove) -1;
7846
7847                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7848                                             first.twoMachinesColor[0] :
7849                                             second.twoMachinesColor[0] ;
7850
7851                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7852                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7853                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7854                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7855                 } else
7856                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7857                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7858                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7859                 } else
7860                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7861                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7862                 }
7863
7864                 // now verify win claims, but not in drop games, as we don't understand those yet
7865                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7866                                                  || gameInfo.variant == VariantGreat) &&
7867                     (result == WhiteWins && claimer == 'w' ||
7868                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7869                       if (appData.debugMode) {
7870                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7871                                 result, epStatus[forwardMostMove], forwardMostMove);
7872                       }
7873                       if(result != trueResult) {
7874                               sprintf(buf, "False win claim: '%s'", resultDetails);
7875                               result = claimer == 'w' ? BlackWins : WhiteWins;
7876                               resultDetails = buf;
7877                       }
7878                 } else
7879                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7880                     && (forwardMostMove <= backwardMostMove ||
7881                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7882                         (claimer=='b')==(forwardMostMove&1))
7883                                                                                   ) {
7884                       /* [HGM] verify: draws that were not flagged are false claims */
7885                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7886                       result = claimer == 'w' ? BlackWins : WhiteWins;
7887                       resultDetails = buf;
7888                 }
7889                 /* (Claiming a loss is accepted no questions asked!) */
7890             }
7891             /* [HGM] bare: don't allow bare King to win */
7892             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7893                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7894                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7895                && result != GameIsDrawn)
7896             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7897                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7898                         int p = (int)boards[forwardMostMove][i][j] - color;
7899                         if(p >= 0 && p <= (int)WhiteKing) k++;
7900                 }
7901                 if (appData.debugMode) {
7902                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7903                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7904                 }
7905                 if(k <= 1) {
7906                         result = GameIsDrawn;
7907                         sprintf(buf, "%s but bare king", resultDetails);
7908                         resultDetails = buf;
7909                 }
7910             }
7911         }
7912
7913
7914         if(serverMoves != NULL && !loadFlag) { char c = '=';
7915             if(result==WhiteWins) c = '+';
7916             if(result==BlackWins) c = '-';
7917             if(resultDetails != NULL)
7918                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7919         }
7920         if (resultDetails != NULL) {
7921             gameInfo.result = result;
7922             gameInfo.resultDetails = StrSave(resultDetails);
7923
7924             /* display last move only if game was not loaded from file */
7925             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7926                 DisplayMove(currentMove - 1);
7927     
7928             if (forwardMostMove != 0) {
7929                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7930                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7931                                                                 ) {
7932                     if (*appData.saveGameFile != NULLCHAR) {
7933                         SaveGameToFile(appData.saveGameFile, TRUE);
7934                     } else if (appData.autoSaveGames) {
7935                         AutoSaveGame();
7936                     }
7937                     if (*appData.savePositionFile != NULLCHAR) {
7938                         SavePositionToFile(appData.savePositionFile);
7939                     }
7940                 }
7941             }
7942
7943             /* Tell program how game ended in case it is learning */
7944             /* [HGM] Moved this to after saving the PGN, just in case */
7945             /* engine died and we got here through time loss. In that */
7946             /* case we will get a fatal error writing the pipe, which */
7947             /* would otherwise lose us the PGN.                       */
7948             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7949             /* output during GameEnds should never be fatal anymore   */
7950             if (gameMode == MachinePlaysWhite ||
7951                 gameMode == MachinePlaysBlack ||
7952                 gameMode == TwoMachinesPlay ||
7953                 gameMode == IcsPlayingWhite ||
7954                 gameMode == IcsPlayingBlack ||
7955                 gameMode == BeginningOfGame) {
7956                 char buf[MSG_SIZ];
7957                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7958                         resultDetails);
7959                 if (first.pr != NoProc) {
7960                     SendToProgram(buf, &first);
7961                 }
7962                 if (second.pr != NoProc &&
7963                     gameMode == TwoMachinesPlay) {
7964                     SendToProgram(buf, &second);
7965                 }
7966             }
7967         }
7968
7969         if (appData.icsActive) {
7970             if (appData.quietPlay &&
7971                 (gameMode == IcsPlayingWhite ||
7972                  gameMode == IcsPlayingBlack)) {
7973                 SendToICS(ics_prefix);
7974                 SendToICS("set shout 1\n");
7975             }
7976             nextGameMode = IcsIdle;
7977             ics_user_moved = FALSE;
7978             /* clean up premove.  It's ugly when the game has ended and the
7979              * premove highlights are still on the board.
7980              */
7981             if (gotPremove) {
7982               gotPremove = FALSE;
7983               ClearPremoveHighlights();
7984               DrawPosition(FALSE, boards[currentMove]);
7985             }
7986             if (whosays == GE_ICS) {
7987                 switch (result) {
7988                 case WhiteWins:
7989                     if (gameMode == IcsPlayingWhite)
7990                         PlayIcsWinSound();
7991                     else if(gameMode == IcsPlayingBlack)
7992                         PlayIcsLossSound();
7993                     break;
7994                 case BlackWins:
7995                     if (gameMode == IcsPlayingBlack)
7996                         PlayIcsWinSound();
7997                     else if(gameMode == IcsPlayingWhite)
7998                         PlayIcsLossSound();
7999                     break;
8000                 case GameIsDrawn:
8001                     PlayIcsDrawSound();
8002                     break;
8003                 default:
8004                     PlayIcsUnfinishedSound();
8005                 }
8006             }
8007         } else if (gameMode == EditGame ||
8008                    gameMode == PlayFromGameFile || 
8009                    gameMode == AnalyzeMode || 
8010                    gameMode == AnalyzeFile) {
8011             nextGameMode = gameMode;
8012         } else {
8013             nextGameMode = EndOfGame;
8014         }
8015         pausing = FALSE;
8016         ModeHighlight();
8017     } else {
8018         nextGameMode = gameMode;
8019     }
8020
8021     if (appData.noChessProgram) {
8022         gameMode = nextGameMode;
8023         ModeHighlight();
8024         endingGame = 0; /* [HGM] crash */
8025         return;
8026     }
8027
8028     if (first.reuse) {
8029         /* Put first chess program into idle state */
8030         if (first.pr != NoProc &&
8031             (gameMode == MachinePlaysWhite ||
8032              gameMode == MachinePlaysBlack ||
8033              gameMode == TwoMachinesPlay ||
8034              gameMode == IcsPlayingWhite ||
8035              gameMode == IcsPlayingBlack ||
8036              gameMode == BeginningOfGame)) {
8037             SendToProgram("force\n", &first);
8038             if (first.usePing) {
8039               char buf[MSG_SIZ];
8040               sprintf(buf, "ping %d\n", ++first.lastPing);
8041               SendToProgram(buf, &first);
8042             }
8043         }
8044     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8045         /* Kill off first chess program */
8046         if (first.isr != NULL)
8047           RemoveInputSource(first.isr);
8048         first.isr = NULL;
8049     
8050         if (first.pr != NoProc) {
8051             ExitAnalyzeMode();
8052             DoSleep( appData.delayBeforeQuit );
8053             SendToProgram("quit\n", &first);
8054             DoSleep( appData.delayAfterQuit );
8055             DestroyChildProcess(first.pr, first.useSigterm);
8056         }
8057         first.pr = NoProc;
8058     }
8059     if (second.reuse) {
8060         /* Put second chess program into idle state */
8061         if (second.pr != NoProc &&
8062             gameMode == TwoMachinesPlay) {
8063             SendToProgram("force\n", &second);
8064             if (second.usePing) {
8065               char buf[MSG_SIZ];
8066               sprintf(buf, "ping %d\n", ++second.lastPing);
8067               SendToProgram(buf, &second);
8068             }
8069         }
8070     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8071         /* Kill off second chess program */
8072         if (second.isr != NULL)
8073           RemoveInputSource(second.isr);
8074         second.isr = NULL;
8075     
8076         if (second.pr != NoProc) {
8077             DoSleep( appData.delayBeforeQuit );
8078             SendToProgram("quit\n", &second);
8079             DoSleep( appData.delayAfterQuit );
8080             DestroyChildProcess(second.pr, second.useSigterm);
8081         }
8082         second.pr = NoProc;
8083     }
8084
8085     if (matchMode && gameMode == TwoMachinesPlay) {
8086         switch (result) {
8087         case WhiteWins:
8088           if (first.twoMachinesColor[0] == 'w') {
8089             first.matchWins++;
8090           } else {
8091             second.matchWins++;
8092           }
8093           break;
8094         case BlackWins:
8095           if (first.twoMachinesColor[0] == 'b') {
8096             first.matchWins++;
8097           } else {
8098             second.matchWins++;
8099           }
8100           break;
8101         default:
8102           break;
8103         }
8104         if (matchGame < appData.matchGames) {
8105             char *tmp;
8106             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8107                 tmp = first.twoMachinesColor;
8108                 first.twoMachinesColor = second.twoMachinesColor;
8109                 second.twoMachinesColor = tmp;
8110             }
8111             gameMode = nextGameMode;
8112             matchGame++;
8113             if(appData.matchPause>10000 || appData.matchPause<10)
8114                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8115             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8116             endingGame = 0; /* [HGM] crash */
8117             return;
8118         } else {
8119             char buf[MSG_SIZ];
8120             gameMode = nextGameMode;
8121             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8122                     first.tidy, second.tidy,
8123                     first.matchWins, second.matchWins,
8124                     appData.matchGames - (first.matchWins + second.matchWins));
8125             DisplayFatalError(buf, 0, 0);
8126         }
8127     }
8128     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8129         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8130       ExitAnalyzeMode();
8131     gameMode = nextGameMode;
8132     ModeHighlight();
8133     endingGame = 0;  /* [HGM] crash */
8134 }
8135
8136 /* Assumes program was just initialized (initString sent).
8137    Leaves program in force mode. */
8138 void
8139 FeedMovesToProgram(cps, upto) 
8140      ChessProgramState *cps;
8141      int upto;
8142 {
8143     int i;
8144     
8145     if (appData.debugMode)
8146       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8147               startedFromSetupPosition ? "position and " : "",
8148               backwardMostMove, upto, cps->which);
8149     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8150         // [HGM] variantswitch: make engine aware of new variant
8151         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8152                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8153         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8154         SendToProgram(buf, cps);
8155         currentlyInitializedVariant = gameInfo.variant;
8156     }
8157     SendToProgram("force\n", cps);
8158     if (startedFromSetupPosition) {
8159         SendBoard(cps, backwardMostMove);
8160     if (appData.debugMode) {
8161         fprintf(debugFP, "feedMoves\n");
8162     }
8163     }
8164     for (i = backwardMostMove; i < upto; i++) {
8165         SendMoveToProgram(i, cps);
8166     }
8167 }
8168
8169
8170 void
8171 ResurrectChessProgram()
8172 {
8173      /* The chess program may have exited.
8174         If so, restart it and feed it all the moves made so far. */
8175
8176     if (appData.noChessProgram || first.pr != NoProc) return;
8177     
8178     StartChessProgram(&first);
8179     InitChessProgram(&first, FALSE);
8180     FeedMovesToProgram(&first, currentMove);
8181
8182     if (!first.sendTime) {
8183         /* can't tell gnuchess what its clock should read,
8184            so we bow to its notion. */
8185         ResetClocks();
8186         timeRemaining[0][currentMove] = whiteTimeRemaining;
8187         timeRemaining[1][currentMove] = blackTimeRemaining;
8188     }
8189
8190     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8191                 appData.icsEngineAnalyze) && first.analysisSupport) {
8192       SendToProgram("analyze\n", &first);
8193       first.analyzing = TRUE;
8194     }
8195 }
8196
8197 /*
8198  * Button procedures
8199  */
8200 void
8201 Reset(redraw, init)
8202      int redraw, init;
8203 {
8204     int i;
8205
8206     if (appData.debugMode) {
8207         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8208                 redraw, init, gameMode);
8209     }
8210     pausing = pauseExamInvalid = FALSE;
8211     startedFromSetupPosition = blackPlaysFirst = FALSE;
8212     firstMove = TRUE;
8213     whiteFlag = blackFlag = FALSE;
8214     userOfferedDraw = FALSE;
8215     hintRequested = bookRequested = FALSE;
8216     first.maybeThinking = FALSE;
8217     second.maybeThinking = FALSE;
8218     first.bookSuspend = FALSE; // [HGM] book
8219     second.bookSuspend = FALSE;
8220     thinkOutput[0] = NULLCHAR;
8221     lastHint[0] = NULLCHAR;
8222     ClearGameInfo(&gameInfo);
8223     gameInfo.variant = StringToVariant(appData.variant);
8224     ics_user_moved = ics_clock_paused = FALSE;
8225     ics_getting_history = H_FALSE;
8226     ics_gamenum = -1;
8227     white_holding[0] = black_holding[0] = NULLCHAR;
8228     ClearProgramStats();
8229     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8230     
8231     ResetFrontEnd();
8232     ClearHighlights();
8233     flipView = appData.flipView;
8234     ClearPremoveHighlights();
8235     gotPremove = FALSE;
8236     alarmSounded = FALSE;
8237
8238     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8239     if(appData.serverMovesName != NULL) {
8240         /* [HGM] prepare to make moves file for broadcasting */
8241         clock_t t = clock();
8242         if(serverMoves != NULL) fclose(serverMoves);
8243         serverMoves = fopen(appData.serverMovesName, "r");
8244         if(serverMoves != NULL) {
8245             fclose(serverMoves);
8246             /* delay 15 sec before overwriting, so all clients can see end */
8247             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8248         }
8249         serverMoves = fopen(appData.serverMovesName, "w");
8250     }
8251
8252     ExitAnalyzeMode();
8253     gameMode = BeginningOfGame;
8254     ModeHighlight();
8255     if(appData.icsActive) gameInfo.variant = VariantNormal;
8256     currentMove = forwardMostMove = backwardMostMove = 0;
8257     InitPosition(redraw);
8258     for (i = 0; i < MAX_MOVES; i++) {
8259         if (commentList[i] != NULL) {
8260             free(commentList[i]);
8261             commentList[i] = NULL;
8262         }
8263     }
8264     ResetClocks();
8265     timeRemaining[0][0] = whiteTimeRemaining;
8266     timeRemaining[1][0] = blackTimeRemaining;
8267     if (first.pr == NULL) {
8268         StartChessProgram(&first);
8269     }
8270     if (init) {
8271             InitChessProgram(&first, startedFromSetupPosition);
8272     }
8273     DisplayTitle("");
8274     DisplayMessage("", "");
8275     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8276     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8277 }
8278
8279 void
8280 AutoPlayGameLoop()
8281 {
8282     for (;;) {
8283         if (!AutoPlayOneMove())
8284           return;
8285         if (matchMode || appData.timeDelay == 0)
8286           continue;
8287         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8288           return;
8289         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8290         break;
8291     }
8292 }
8293
8294
8295 int
8296 AutoPlayOneMove()
8297 {
8298     int fromX, fromY, toX, toY;
8299
8300     if (appData.debugMode) {
8301       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8302     }
8303
8304     if (gameMode != PlayFromGameFile)
8305       return FALSE;
8306
8307     if (currentMove >= forwardMostMove) {
8308       gameMode = EditGame;
8309       ModeHighlight();
8310
8311       /* [AS] Clear current move marker at the end of a game */
8312       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8313
8314       return FALSE;
8315     }
8316     
8317     toX = moveList[currentMove][2] - AAA;
8318     toY = moveList[currentMove][3] - ONE;
8319
8320     if (moveList[currentMove][1] == '@') {
8321         if (appData.highlightLastMove) {
8322             SetHighlights(-1, -1, toX, toY);
8323         }
8324     } else {
8325         fromX = moveList[currentMove][0] - AAA;
8326         fromY = moveList[currentMove][1] - ONE;
8327
8328         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8329
8330         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8331
8332         if (appData.highlightLastMove) {
8333             SetHighlights(fromX, fromY, toX, toY);
8334         }
8335     }
8336     DisplayMove(currentMove);
8337     SendMoveToProgram(currentMove++, &first);
8338     DisplayBothClocks();
8339     DrawPosition(FALSE, boards[currentMove]);
8340     // [HGM] PV info: always display, routine tests if empty
8341     DisplayComment(currentMove - 1, commentList[currentMove]);
8342     return TRUE;
8343 }
8344
8345
8346 int
8347 LoadGameOneMove(readAhead)
8348      ChessMove readAhead;
8349 {
8350     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8351     char promoChar = NULLCHAR;
8352     ChessMove moveType;
8353     char move[MSG_SIZ];
8354     char *p, *q;
8355     
8356     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8357         gameMode != AnalyzeMode && gameMode != Training) {
8358         gameFileFP = NULL;
8359         return FALSE;
8360     }
8361     
8362     yyboardindex = forwardMostMove;
8363     if (readAhead != (ChessMove)0) {
8364       moveType = readAhead;
8365     } else {
8366       if (gameFileFP == NULL)
8367           return FALSE;
8368       moveType = (ChessMove) yylex();
8369     }
8370     
8371     done = FALSE;
8372     switch (moveType) {
8373       case Comment:
8374         if (appData.debugMode) 
8375           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8376         p = yy_text;
8377         if (*p == '{' || *p == '[' || *p == '(') {
8378             p[strlen(p) - 1] = NULLCHAR;
8379             p++;
8380         }
8381
8382         /* append the comment but don't display it */
8383         while (*p == '\n') p++;
8384         AppendComment(currentMove, p);
8385         return TRUE;
8386
8387       case WhiteCapturesEnPassant:
8388       case BlackCapturesEnPassant:
8389       case WhitePromotionChancellor:
8390       case BlackPromotionChancellor:
8391       case WhitePromotionArchbishop:
8392       case BlackPromotionArchbishop:
8393       case WhitePromotionCentaur:
8394       case BlackPromotionCentaur:
8395       case WhitePromotionQueen:
8396       case BlackPromotionQueen:
8397       case WhitePromotionRook:
8398       case BlackPromotionRook:
8399       case WhitePromotionBishop:
8400       case BlackPromotionBishop:
8401       case WhitePromotionKnight:
8402       case BlackPromotionKnight:
8403       case WhitePromotionKing:
8404       case BlackPromotionKing:
8405       case NormalMove:
8406       case WhiteKingSideCastle:
8407       case WhiteQueenSideCastle:
8408       case BlackKingSideCastle:
8409       case BlackQueenSideCastle:
8410       case WhiteKingSideCastleWild:
8411       case WhiteQueenSideCastleWild:
8412       case BlackKingSideCastleWild:
8413       case BlackQueenSideCastleWild:
8414       /* PUSH Fabien */
8415       case WhiteHSideCastleFR:
8416       case WhiteASideCastleFR:
8417       case BlackHSideCastleFR:
8418       case BlackASideCastleFR:
8419       /* POP Fabien */
8420         if (appData.debugMode)
8421           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8422         fromX = currentMoveString[0] - AAA;
8423         fromY = currentMoveString[1] - ONE;
8424         toX = currentMoveString[2] - AAA;
8425         toY = currentMoveString[3] - ONE;
8426         promoChar = currentMoveString[4];
8427         break;
8428
8429       case WhiteDrop:
8430       case BlackDrop:
8431         if (appData.debugMode)
8432           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8433         fromX = moveType == WhiteDrop ?
8434           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8435         (int) CharToPiece(ToLower(currentMoveString[0]));
8436         fromY = DROP_RANK;
8437         toX = currentMoveString[2] - AAA;
8438         toY = currentMoveString[3] - ONE;
8439         break;
8440
8441       case WhiteWins:
8442       case BlackWins:
8443       case GameIsDrawn:
8444       case GameUnfinished:
8445         if (appData.debugMode)
8446           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8447         p = strchr(yy_text, '{');
8448         if (p == NULL) p = strchr(yy_text, '(');
8449         if (p == NULL) {
8450             p = yy_text;
8451             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8452         } else {
8453             q = strchr(p, *p == '{' ? '}' : ')');
8454             if (q != NULL) *q = NULLCHAR;
8455             p++;
8456         }
8457         GameEnds(moveType, p, GE_FILE);
8458         done = TRUE;
8459         if (cmailMsgLoaded) {
8460             ClearHighlights();
8461             flipView = WhiteOnMove(currentMove);
8462             if (moveType == GameUnfinished) flipView = !flipView;
8463             if (appData.debugMode)
8464               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8465         }
8466         break;
8467
8468       case (ChessMove) 0:       /* end of file */
8469         if (appData.debugMode)
8470           fprintf(debugFP, "Parser hit end of file\n");
8471         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8472                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8473           case MT_NONE:
8474           case MT_CHECK:
8475             break;
8476           case MT_CHECKMATE:
8477           case MT_STAINMATE:
8478             if (WhiteOnMove(currentMove)) {
8479                 GameEnds(BlackWins, "Black mates", GE_FILE);
8480             } else {
8481                 GameEnds(WhiteWins, "White mates", GE_FILE);
8482             }
8483             break;
8484           case MT_STALEMATE:
8485             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8486             break;
8487         }
8488         done = TRUE;
8489         break;
8490
8491       case MoveNumberOne:
8492         if (lastLoadGameStart == GNUChessGame) {
8493             /* GNUChessGames have numbers, but they aren't move numbers */
8494             if (appData.debugMode)
8495               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8496                       yy_text, (int) moveType);
8497             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8498         }
8499         /* else fall thru */
8500
8501       case XBoardGame:
8502       case GNUChessGame:
8503       case PGNTag:
8504         /* Reached start of next game in file */
8505         if (appData.debugMode)
8506           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8507         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8508                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8509           case MT_NONE:
8510           case MT_CHECK:
8511             break;
8512           case MT_CHECKMATE:
8513           case MT_STAINMATE:
8514             if (WhiteOnMove(currentMove)) {
8515                 GameEnds(BlackWins, "Black mates", GE_FILE);
8516             } else {
8517                 GameEnds(WhiteWins, "White mates", GE_FILE);
8518             }
8519             break;
8520           case MT_STALEMATE:
8521             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8522             break;
8523         }
8524         done = TRUE;
8525         break;
8526
8527       case PositionDiagram:     /* should not happen; ignore */
8528       case ElapsedTime:         /* ignore */
8529       case NAG:                 /* ignore */
8530         if (appData.debugMode)
8531           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8532                   yy_text, (int) moveType);
8533         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8534
8535       case IllegalMove:
8536         if (appData.testLegality) {
8537             if (appData.debugMode)
8538               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8539             sprintf(move, _("Illegal move: %d.%s%s"),
8540                     (forwardMostMove / 2) + 1,
8541                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8542             DisplayError(move, 0);
8543             done = TRUE;
8544         } else {
8545             if (appData.debugMode)
8546               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8547                       yy_text, currentMoveString);
8548             fromX = currentMoveString[0] - AAA;
8549             fromY = currentMoveString[1] - ONE;
8550             toX = currentMoveString[2] - AAA;
8551             toY = currentMoveString[3] - ONE;
8552             promoChar = currentMoveString[4];
8553         }
8554         break;
8555
8556       case AmbiguousMove:
8557         if (appData.debugMode)
8558           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8559         sprintf(move, _("Ambiguous move: %d.%s%s"),
8560                 (forwardMostMove / 2) + 1,
8561                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8562         DisplayError(move, 0);
8563         done = TRUE;
8564         break;
8565
8566       default:
8567       case ImpossibleMove:
8568         if (appData.debugMode)
8569           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8570         sprintf(move, _("Illegal move: %d.%s%s"),
8571                 (forwardMostMove / 2) + 1,
8572                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8573         DisplayError(move, 0);
8574         done = TRUE;
8575         break;
8576     }
8577
8578     if (done) {
8579         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8580             DrawPosition(FALSE, boards[currentMove]);
8581             DisplayBothClocks();
8582             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8583               DisplayComment(currentMove - 1, commentList[currentMove]);
8584         }
8585         (void) StopLoadGameTimer();
8586         gameFileFP = NULL;
8587         cmailOldMove = forwardMostMove;
8588         return FALSE;
8589     } else {
8590         /* currentMoveString is set as a side-effect of yylex */
8591         strcat(currentMoveString, "\n");
8592         strcpy(moveList[forwardMostMove], currentMoveString);
8593         
8594         thinkOutput[0] = NULLCHAR;
8595         MakeMove(fromX, fromY, toX, toY, promoChar);
8596         currentMove = forwardMostMove;
8597         return TRUE;
8598     }
8599 }
8600
8601 /* Load the nth game from the given file */
8602 int
8603 LoadGameFromFile(filename, n, title, useList)
8604      char *filename;
8605      int n;
8606      char *title;
8607      /*Boolean*/ int useList;
8608 {
8609     FILE *f;
8610     char buf[MSG_SIZ];
8611
8612     if (strcmp(filename, "-") == 0) {
8613         f = stdin;
8614         title = "stdin";
8615     } else {
8616         f = fopen(filename, "rb");
8617         if (f == NULL) {
8618           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8619             DisplayError(buf, errno);
8620             return FALSE;
8621         }
8622     }
8623     if (fseek(f, 0, 0) == -1) {
8624         /* f is not seekable; probably a pipe */
8625         useList = FALSE;
8626     }
8627     if (useList && n == 0) {
8628         int error = GameListBuild(f);
8629         if (error) {
8630             DisplayError(_("Cannot build game list"), error);
8631         } else if (!ListEmpty(&gameList) &&
8632                    ((ListGame *) gameList.tailPred)->number > 1) {
8633             GameListPopUp(f, title);
8634             return TRUE;
8635         }
8636         GameListDestroy();
8637         n = 1;
8638     }
8639     if (n == 0) n = 1;
8640     return LoadGame(f, n, title, FALSE);
8641 }
8642
8643
8644 void
8645 MakeRegisteredMove()
8646 {
8647     int fromX, fromY, toX, toY;
8648     char promoChar;
8649     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8650         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8651           case CMAIL_MOVE:
8652           case CMAIL_DRAW:
8653             if (appData.debugMode)
8654               fprintf(debugFP, "Restoring %s for game %d\n",
8655                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8656     
8657             thinkOutput[0] = NULLCHAR;
8658             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8659             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8660             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8661             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8662             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8663             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8664             MakeMove(fromX, fromY, toX, toY, promoChar);
8665             ShowMove(fromX, fromY, toX, toY);
8666               
8667             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8668                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8669               case MT_NONE:
8670               case MT_CHECK:
8671                 break;
8672                 
8673               case MT_CHECKMATE:
8674               case MT_STAINMATE:
8675                 if (WhiteOnMove(currentMove)) {
8676                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8677                 } else {
8678                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8679                 }
8680                 break;
8681                 
8682               case MT_STALEMATE:
8683                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8684                 break;
8685             }
8686
8687             break;
8688             
8689           case CMAIL_RESIGN:
8690             if (WhiteOnMove(currentMove)) {
8691                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8692             } else {
8693                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8694             }
8695             break;
8696             
8697           case CMAIL_ACCEPT:
8698             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8699             break;
8700               
8701           default:
8702             break;
8703         }
8704     }
8705
8706     return;
8707 }
8708
8709 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8710 int
8711 CmailLoadGame(f, gameNumber, title, useList)
8712      FILE *f;
8713      int gameNumber;
8714      char *title;
8715      int useList;
8716 {
8717     int retVal;
8718
8719     if (gameNumber > nCmailGames) {
8720         DisplayError(_("No more games in this message"), 0);
8721         return FALSE;
8722     }
8723     if (f == lastLoadGameFP) {
8724         int offset = gameNumber - lastLoadGameNumber;
8725         if (offset == 0) {
8726             cmailMsg[0] = NULLCHAR;
8727             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8728                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8729                 nCmailMovesRegistered--;
8730             }
8731             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8732             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8733                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8734             }
8735         } else {
8736             if (! RegisterMove()) return FALSE;
8737         }
8738     }
8739
8740     retVal = LoadGame(f, gameNumber, title, useList);
8741
8742     /* Make move registered during previous look at this game, if any */
8743     MakeRegisteredMove();
8744
8745     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8746         commentList[currentMove]
8747           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8748         DisplayComment(currentMove - 1, commentList[currentMove]);
8749     }
8750
8751     return retVal;
8752 }
8753
8754 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8755 int
8756 ReloadGame(offset)
8757      int offset;
8758 {
8759     int gameNumber = lastLoadGameNumber + offset;
8760     if (lastLoadGameFP == NULL) {
8761         DisplayError(_("No game has been loaded yet"), 0);
8762         return FALSE;
8763     }
8764     if (gameNumber <= 0) {
8765         DisplayError(_("Can't back up any further"), 0);
8766         return FALSE;
8767     }
8768     if (cmailMsgLoaded) {
8769         return CmailLoadGame(lastLoadGameFP, gameNumber,
8770                              lastLoadGameTitle, lastLoadGameUseList);
8771     } else {
8772         return LoadGame(lastLoadGameFP, gameNumber,
8773                         lastLoadGameTitle, lastLoadGameUseList);
8774     }
8775 }
8776
8777
8778
8779 /* Load the nth game from open file f */
8780 int
8781 LoadGame(f, gameNumber, title, useList)
8782      FILE *f;
8783      int gameNumber;
8784      char *title;
8785      int useList;
8786 {
8787     ChessMove cm;
8788     char buf[MSG_SIZ];
8789     int gn = gameNumber;
8790     ListGame *lg = NULL;
8791     int numPGNTags = 0;
8792     int err;
8793     GameMode oldGameMode;
8794     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8795
8796     if (appData.debugMode) 
8797         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8798
8799     if (gameMode == Training )
8800         SetTrainingModeOff();
8801
8802     oldGameMode = gameMode;
8803     if (gameMode != BeginningOfGame) {
8804       Reset(FALSE, TRUE);
8805     }
8806
8807     gameFileFP = f;
8808     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8809         fclose(lastLoadGameFP);
8810     }
8811
8812     if (useList) {
8813         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8814         
8815         if (lg) {
8816             fseek(f, lg->offset, 0);
8817             GameListHighlight(gameNumber);
8818             gn = 1;
8819         }
8820         else {
8821             DisplayError(_("Game number out of range"), 0);
8822             return FALSE;
8823         }
8824     } else {
8825         GameListDestroy();
8826         if (fseek(f, 0, 0) == -1) {
8827             if (f == lastLoadGameFP ?
8828                 gameNumber == lastLoadGameNumber + 1 :
8829                 gameNumber == 1) {
8830                 gn = 1;
8831             } else {
8832                 DisplayError(_("Can't seek on game file"), 0);
8833                 return FALSE;
8834             }
8835         }
8836     }
8837     lastLoadGameFP = f;
8838     lastLoadGameNumber = gameNumber;
8839     strcpy(lastLoadGameTitle, title);
8840     lastLoadGameUseList = useList;
8841
8842     yynewfile(f);
8843
8844     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8845       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8846                 lg->gameInfo.black);
8847             DisplayTitle(buf);
8848     } else if (*title != NULLCHAR) {
8849         if (gameNumber > 1) {
8850             sprintf(buf, "%s %d", title, gameNumber);
8851             DisplayTitle(buf);
8852         } else {
8853             DisplayTitle(title);
8854         }
8855     }
8856
8857     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8858         gameMode = PlayFromGameFile;
8859         ModeHighlight();
8860     }
8861
8862     currentMove = forwardMostMove = backwardMostMove = 0;
8863     CopyBoard(boards[0], initialPosition);
8864     StopClocks();
8865
8866     /*
8867      * Skip the first gn-1 games in the file.
8868      * Also skip over anything that precedes an identifiable 
8869      * start of game marker, to avoid being confused by 
8870      * garbage at the start of the file.  Currently 
8871      * recognized start of game markers are the move number "1",
8872      * the pattern "gnuchess .* game", the pattern
8873      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8874      * A game that starts with one of the latter two patterns
8875      * will also have a move number 1, possibly
8876      * following a position diagram.
8877      * 5-4-02: Let's try being more lenient and allowing a game to
8878      * start with an unnumbered move.  Does that break anything?
8879      */
8880     cm = lastLoadGameStart = (ChessMove) 0;
8881     while (gn > 0) {
8882         yyboardindex = forwardMostMove;
8883         cm = (ChessMove) yylex();
8884         switch (cm) {
8885           case (ChessMove) 0:
8886             if (cmailMsgLoaded) {
8887                 nCmailGames = CMAIL_MAX_GAMES - gn;
8888             } else {
8889                 Reset(TRUE, TRUE);
8890                 DisplayError(_("Game not found in file"), 0);
8891             }
8892             return FALSE;
8893
8894           case GNUChessGame:
8895           case XBoardGame:
8896             gn--;
8897             lastLoadGameStart = cm;
8898             break;
8899             
8900           case MoveNumberOne:
8901             switch (lastLoadGameStart) {
8902               case GNUChessGame:
8903               case XBoardGame:
8904               case PGNTag:
8905                 break;
8906               case MoveNumberOne:
8907               case (ChessMove) 0:
8908                 gn--;           /* count this game */
8909                 lastLoadGameStart = cm;
8910                 break;
8911               default:
8912                 /* impossible */
8913                 break;
8914             }
8915             break;
8916
8917           case PGNTag:
8918             switch (lastLoadGameStart) {
8919               case GNUChessGame:
8920               case PGNTag:
8921               case MoveNumberOne:
8922               case (ChessMove) 0:
8923                 gn--;           /* count this game */
8924                 lastLoadGameStart = cm;
8925                 break;
8926               case XBoardGame:
8927                 lastLoadGameStart = cm; /* game counted already */
8928                 break;
8929               default:
8930                 /* impossible */
8931                 break;
8932             }
8933             if (gn > 0) {
8934                 do {
8935                     yyboardindex = forwardMostMove;
8936                     cm = (ChessMove) yylex();
8937                 } while (cm == PGNTag || cm == Comment);
8938             }
8939             break;
8940
8941           case WhiteWins:
8942           case BlackWins:
8943           case GameIsDrawn:
8944             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8945                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8946                     != CMAIL_OLD_RESULT) {
8947                     nCmailResults ++ ;
8948                     cmailResult[  CMAIL_MAX_GAMES
8949                                 - gn - 1] = CMAIL_OLD_RESULT;
8950                 }
8951             }
8952             break;
8953
8954           case NormalMove:
8955             /* Only a NormalMove can be at the start of a game
8956              * without a position diagram. */
8957             if (lastLoadGameStart == (ChessMove) 0) {
8958               gn--;
8959               lastLoadGameStart = MoveNumberOne;
8960             }
8961             break;
8962
8963           default:
8964             break;
8965         }
8966     }
8967     
8968     if (appData.debugMode)
8969       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8970
8971     if (cm == XBoardGame) {
8972         /* Skip any header junk before position diagram and/or move 1 */
8973         for (;;) {
8974             yyboardindex = forwardMostMove;
8975             cm = (ChessMove) yylex();
8976
8977             if (cm == (ChessMove) 0 ||
8978                 cm == GNUChessGame || cm == XBoardGame) {
8979                 /* Empty game; pretend end-of-file and handle later */
8980                 cm = (ChessMove) 0;
8981                 break;
8982             }
8983
8984             if (cm == MoveNumberOne || cm == PositionDiagram ||
8985                 cm == PGNTag || cm == Comment)
8986               break;
8987         }
8988     } else if (cm == GNUChessGame) {
8989         if (gameInfo.event != NULL) {
8990             free(gameInfo.event);
8991         }
8992         gameInfo.event = StrSave(yy_text);
8993     }   
8994
8995     startedFromSetupPosition = FALSE;
8996     while (cm == PGNTag) {
8997         if (appData.debugMode) 
8998           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8999         err = ParsePGNTag(yy_text, &gameInfo);
9000         if (!err) numPGNTags++;
9001
9002         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9003         if(gameInfo.variant != oldVariant) {
9004             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9005             InitPosition(TRUE);
9006             oldVariant = gameInfo.variant;
9007             if (appData.debugMode) 
9008               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9009         }
9010
9011
9012         if (gameInfo.fen != NULL) {
9013           Board initial_position;
9014           startedFromSetupPosition = TRUE;
9015           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9016             Reset(TRUE, TRUE);
9017             DisplayError(_("Bad FEN position in file"), 0);
9018             return FALSE;
9019           }
9020           CopyBoard(boards[0], initial_position);
9021           if (blackPlaysFirst) {
9022             currentMove = forwardMostMove = backwardMostMove = 1;
9023             CopyBoard(boards[1], initial_position);
9024             strcpy(moveList[0], "");
9025             strcpy(parseList[0], "");
9026             timeRemaining[0][1] = whiteTimeRemaining;
9027             timeRemaining[1][1] = blackTimeRemaining;
9028             if (commentList[0] != NULL) {
9029               commentList[1] = commentList[0];
9030               commentList[0] = NULL;
9031             }
9032           } else {
9033             currentMove = forwardMostMove = backwardMostMove = 0;
9034           }
9035           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9036           {   int i;
9037               initialRulePlies = FENrulePlies;
9038               epStatus[forwardMostMove] = FENepStatus;
9039               for( i=0; i< nrCastlingRights; i++ )
9040                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9041           }
9042           yyboardindex = forwardMostMove;
9043           free(gameInfo.fen);
9044           gameInfo.fen = NULL;
9045         }
9046
9047         yyboardindex = forwardMostMove;
9048         cm = (ChessMove) yylex();
9049
9050         /* Handle comments interspersed among the tags */
9051         while (cm == Comment) {
9052             char *p;
9053             if (appData.debugMode) 
9054               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9055             p = yy_text;
9056             if (*p == '{' || *p == '[' || *p == '(') {
9057                 p[strlen(p) - 1] = NULLCHAR;
9058                 p++;
9059             }
9060             while (*p == '\n') p++;
9061             AppendComment(currentMove, p);
9062             yyboardindex = forwardMostMove;
9063             cm = (ChessMove) yylex();
9064         }
9065     }
9066
9067     /* don't rely on existence of Event tag since if game was
9068      * pasted from clipboard the Event tag may not exist
9069      */
9070     if (numPGNTags > 0){
9071         char *tags;
9072         if (gameInfo.variant == VariantNormal) {
9073           gameInfo.variant = StringToVariant(gameInfo.event);
9074         }
9075         if (!matchMode) {
9076           if( appData.autoDisplayTags ) {
9077             tags = PGNTags(&gameInfo);
9078             TagsPopUp(tags, CmailMsg());
9079             free(tags);
9080           }
9081         }
9082     } else {
9083         /* Make something up, but don't display it now */
9084         SetGameInfo();
9085         TagsPopDown();
9086     }
9087
9088     if (cm == PositionDiagram) {
9089         int i, j;
9090         char *p;
9091         Board initial_position;
9092
9093         if (appData.debugMode)
9094           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9095
9096         if (!startedFromSetupPosition) {
9097             p = yy_text;
9098             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9099               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9100                 switch (*p) {
9101                   case '[':
9102                   case '-':
9103                   case ' ':
9104                   case '\t':
9105                   case '\n':
9106                   case '\r':
9107                     break;
9108                   default:
9109                     initial_position[i][j++] = CharToPiece(*p);
9110                     break;
9111                 }
9112             while (*p == ' ' || *p == '\t' ||
9113                    *p == '\n' || *p == '\r') p++;
9114         
9115             if (strncmp(p, "black", strlen("black"))==0)
9116               blackPlaysFirst = TRUE;
9117             else
9118               blackPlaysFirst = FALSE;
9119             startedFromSetupPosition = TRUE;
9120         
9121             CopyBoard(boards[0], initial_position);
9122             if (blackPlaysFirst) {
9123                 currentMove = forwardMostMove = backwardMostMove = 1;
9124                 CopyBoard(boards[1], initial_position);
9125                 strcpy(moveList[0], "");
9126                 strcpy(parseList[0], "");
9127                 timeRemaining[0][1] = whiteTimeRemaining;
9128                 timeRemaining[1][1] = blackTimeRemaining;
9129                 if (commentList[0] != NULL) {
9130                     commentList[1] = commentList[0];
9131                     commentList[0] = NULL;
9132                 }
9133             } else {
9134                 currentMove = forwardMostMove = backwardMostMove = 0;
9135             }
9136         }
9137         yyboardindex = forwardMostMove;
9138         cm = (ChessMove) yylex();
9139     }
9140
9141     if (first.pr == NoProc) {
9142         StartChessProgram(&first);
9143     }
9144     InitChessProgram(&first, FALSE);
9145     SendToProgram("force\n", &first);
9146     if (startedFromSetupPosition) {
9147         SendBoard(&first, forwardMostMove);
9148     if (appData.debugMode) {
9149         fprintf(debugFP, "Load Game\n");
9150     }
9151         DisplayBothClocks();
9152     }      
9153
9154     /* [HGM] server: flag to write setup moves in broadcast file as one */
9155     loadFlag = appData.suppressLoadMoves;
9156
9157     while (cm == Comment) {
9158         char *p;
9159         if (appData.debugMode) 
9160           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9161         p = yy_text;
9162         if (*p == '{' || *p == '[' || *p == '(') {
9163             p[strlen(p) - 1] = NULLCHAR;
9164             p++;
9165         }
9166         while (*p == '\n') p++;
9167         AppendComment(currentMove, p);
9168         yyboardindex = forwardMostMove;
9169         cm = (ChessMove) yylex();
9170     }
9171
9172     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9173         cm == WhiteWins || cm == BlackWins ||
9174         cm == GameIsDrawn || cm == GameUnfinished) {
9175         DisplayMessage("", _("No moves in game"));
9176         if (cmailMsgLoaded) {
9177             if (appData.debugMode)
9178               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9179             ClearHighlights();
9180             flipView = FALSE;
9181         }
9182         DrawPosition(FALSE, boards[currentMove]);
9183         DisplayBothClocks();
9184         gameMode = EditGame;
9185         ModeHighlight();
9186         gameFileFP = NULL;
9187         cmailOldMove = 0;
9188         return TRUE;
9189     }
9190
9191     // [HGM] PV info: routine tests if comment empty
9192     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9193         DisplayComment(currentMove - 1, commentList[currentMove]);
9194     }
9195     if (!matchMode && appData.timeDelay != 0) 
9196       DrawPosition(FALSE, boards[currentMove]);
9197
9198     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9199       programStats.ok_to_send = 1;
9200     }
9201
9202     /* if the first token after the PGN tags is a move
9203      * and not move number 1, retrieve it from the parser 
9204      */
9205     if (cm != MoveNumberOne)
9206         LoadGameOneMove(cm);
9207
9208     /* load the remaining moves from the file */
9209     while (LoadGameOneMove((ChessMove)0)) {
9210       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9211       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9212     }
9213
9214     /* rewind to the start of the game */
9215     currentMove = backwardMostMove;
9216
9217     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9218
9219     if (oldGameMode == AnalyzeFile ||
9220         oldGameMode == AnalyzeMode) {
9221       AnalyzeFileEvent();
9222     }
9223
9224     if (matchMode || appData.timeDelay == 0) {
9225       ToEndEvent();
9226       gameMode = EditGame;
9227       ModeHighlight();
9228     } else if (appData.timeDelay > 0) {
9229       AutoPlayGameLoop();
9230     }
9231
9232     if (appData.debugMode) 
9233         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9234
9235     loadFlag = 0; /* [HGM] true game starts */
9236     return TRUE;
9237 }
9238
9239 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9240 int
9241 ReloadPosition(offset)
9242      int offset;
9243 {
9244     int positionNumber = lastLoadPositionNumber + offset;
9245     if (lastLoadPositionFP == NULL) {
9246         DisplayError(_("No position has been loaded yet"), 0);
9247         return FALSE;
9248     }
9249     if (positionNumber <= 0) {
9250         DisplayError(_("Can't back up any further"), 0);
9251         return FALSE;
9252     }
9253     return LoadPosition(lastLoadPositionFP, positionNumber,
9254                         lastLoadPositionTitle);
9255 }
9256
9257 /* Load the nth position from the given file */
9258 int
9259 LoadPositionFromFile(filename, n, title)
9260      char *filename;
9261      int n;
9262      char *title;
9263 {
9264     FILE *f;
9265     char buf[MSG_SIZ];
9266
9267     if (strcmp(filename, "-") == 0) {
9268         return LoadPosition(stdin, n, "stdin");
9269     } else {
9270         f = fopen(filename, "rb");
9271         if (f == NULL) {
9272             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9273             DisplayError(buf, errno);
9274             return FALSE;
9275         } else {
9276             return LoadPosition(f, n, title);
9277         }
9278     }
9279 }
9280
9281 /* Load the nth position from the given open file, and close it */
9282 int
9283 LoadPosition(f, positionNumber, title)
9284      FILE *f;
9285      int positionNumber;
9286      char *title;
9287 {
9288     char *p, line[MSG_SIZ];
9289     Board initial_position;
9290     int i, j, fenMode, pn;
9291     
9292     if (gameMode == Training )
9293         SetTrainingModeOff();
9294
9295     if (gameMode != BeginningOfGame) {
9296         Reset(FALSE, TRUE);
9297     }
9298     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9299         fclose(lastLoadPositionFP);
9300     }
9301     if (positionNumber == 0) positionNumber = 1;
9302     lastLoadPositionFP = f;
9303     lastLoadPositionNumber = positionNumber;
9304     strcpy(lastLoadPositionTitle, title);
9305     if (first.pr == NoProc) {
9306       StartChessProgram(&first);
9307       InitChessProgram(&first, FALSE);
9308     }    
9309     pn = positionNumber;
9310     if (positionNumber < 0) {
9311         /* Negative position number means to seek to that byte offset */
9312         if (fseek(f, -positionNumber, 0) == -1) {
9313             DisplayError(_("Can't seek on position file"), 0);
9314             return FALSE;
9315         };
9316         pn = 1;
9317     } else {
9318         if (fseek(f, 0, 0) == -1) {
9319             if (f == lastLoadPositionFP ?
9320                 positionNumber == lastLoadPositionNumber + 1 :
9321                 positionNumber == 1) {
9322                 pn = 1;
9323             } else {
9324                 DisplayError(_("Can't seek on position file"), 0);
9325                 return FALSE;
9326             }
9327         }
9328     }
9329     /* See if this file is FEN or old-style xboard */
9330     if (fgets(line, MSG_SIZ, f) == NULL) {
9331         DisplayError(_("Position not found in file"), 0);
9332         return FALSE;
9333     }
9334     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9335     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9336
9337     if (pn >= 2) {
9338         if (fenMode || line[0] == '#') pn--;
9339         while (pn > 0) {
9340             /* skip positions before number pn */
9341             if (fgets(line, MSG_SIZ, f) == NULL) {
9342                 Reset(TRUE, TRUE);
9343                 DisplayError(_("Position not found in file"), 0);
9344                 return FALSE;
9345             }
9346             if (fenMode || line[0] == '#') pn--;
9347         }
9348     }
9349
9350     if (fenMode) {
9351         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9352             DisplayError(_("Bad FEN position in file"), 0);
9353             return FALSE;
9354         }
9355     } else {
9356         (void) fgets(line, MSG_SIZ, f);
9357         (void) fgets(line, MSG_SIZ, f);
9358     
9359         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9360             (void) fgets(line, MSG_SIZ, f);
9361             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9362                 if (*p == ' ')
9363                   continue;
9364                 initial_position[i][j++] = CharToPiece(*p);
9365             }
9366         }
9367     
9368         blackPlaysFirst = FALSE;
9369         if (!feof(f)) {
9370             (void) fgets(line, MSG_SIZ, f);
9371             if (strncmp(line, "black", strlen("black"))==0)
9372               blackPlaysFirst = TRUE;
9373         }
9374     }
9375     startedFromSetupPosition = TRUE;
9376     
9377     SendToProgram("force\n", &first);
9378     CopyBoard(boards[0], initial_position);
9379     if (blackPlaysFirst) {
9380         currentMove = forwardMostMove = backwardMostMove = 1;
9381         strcpy(moveList[0], "");
9382         strcpy(parseList[0], "");
9383         CopyBoard(boards[1], initial_position);
9384         DisplayMessage("", _("Black to play"));
9385     } else {
9386         currentMove = forwardMostMove = backwardMostMove = 0;
9387         DisplayMessage("", _("White to play"));
9388     }
9389           /* [HGM] copy FEN attributes as well */
9390           {   int i;
9391               initialRulePlies = FENrulePlies;
9392               epStatus[forwardMostMove] = FENepStatus;
9393               for( i=0; i< nrCastlingRights; i++ )
9394                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9395           }
9396     SendBoard(&first, forwardMostMove);
9397     if (appData.debugMode) {
9398 int i, j;
9399   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9400   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9401         fprintf(debugFP, "Load Position\n");
9402     }
9403
9404     if (positionNumber > 1) {
9405         sprintf(line, "%s %d", title, positionNumber);
9406         DisplayTitle(line);
9407     } else {
9408         DisplayTitle(title);
9409     }
9410     gameMode = EditGame;
9411     ModeHighlight();
9412     ResetClocks();
9413     timeRemaining[0][1] = whiteTimeRemaining;
9414     timeRemaining[1][1] = blackTimeRemaining;
9415     DrawPosition(FALSE, boards[currentMove]);
9416    
9417     return TRUE;
9418 }
9419
9420
9421 void
9422 CopyPlayerNameIntoFileName(dest, src)
9423      char **dest, *src;
9424 {
9425     while (*src != NULLCHAR && *src != ',') {
9426         if (*src == ' ') {
9427             *(*dest)++ = '_';
9428             src++;
9429         } else {
9430             *(*dest)++ = *src++;
9431         }
9432     }
9433 }
9434
9435 char *DefaultFileName(ext)
9436      char *ext;
9437 {
9438     static char def[MSG_SIZ];
9439     char *p;
9440
9441     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9442         p = def;
9443         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9444         *p++ = '-';
9445         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9446         *p++ = '.';
9447         strcpy(p, ext);
9448     } else {
9449         def[0] = NULLCHAR;
9450     }
9451     return def;
9452 }
9453
9454 /* Save the current game to the given file */
9455 int
9456 SaveGameToFile(filename, append)
9457      char *filename;
9458      int append;
9459 {
9460     FILE *f;
9461     char buf[MSG_SIZ];
9462
9463     if (strcmp(filename, "-") == 0) {
9464         return SaveGame(stdout, 0, NULL);
9465     } else {
9466         f = fopen(filename, append ? "a" : "w");
9467         if (f == NULL) {
9468             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9469             DisplayError(buf, errno);
9470             return FALSE;
9471         } else {
9472             return SaveGame(f, 0, NULL);
9473         }
9474     }
9475 }
9476
9477 char *
9478 SavePart(str)
9479      char *str;
9480 {
9481     static char buf[MSG_SIZ];
9482     char *p;
9483     
9484     p = strchr(str, ' ');
9485     if (p == NULL) return str;
9486     strncpy(buf, str, p - str);
9487     buf[p - str] = NULLCHAR;
9488     return buf;
9489 }
9490
9491 #define PGN_MAX_LINE 75
9492
9493 #define PGN_SIDE_WHITE  0
9494 #define PGN_SIDE_BLACK  1
9495
9496 /* [AS] */
9497 static int FindFirstMoveOutOfBook( int side )
9498 {
9499     int result = -1;
9500
9501     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9502         int index = backwardMostMove;
9503         int has_book_hit = 0;
9504
9505         if( (index % 2) != side ) {
9506             index++;
9507         }
9508
9509         while( index < forwardMostMove ) {
9510             /* Check to see if engine is in book */
9511             int depth = pvInfoList[index].depth;
9512             int score = pvInfoList[index].score;
9513             int in_book = 0;
9514
9515             if( depth <= 2 ) {
9516                 in_book = 1;
9517             }
9518             else if( score == 0 && depth == 63 ) {
9519                 in_book = 1; /* Zappa */
9520             }
9521             else if( score == 2 && depth == 99 ) {
9522                 in_book = 1; /* Abrok */
9523             }
9524
9525             has_book_hit += in_book;
9526
9527             if( ! in_book ) {
9528                 result = index;
9529
9530                 break;
9531             }
9532
9533             index += 2;
9534         }
9535     }
9536
9537     return result;
9538 }
9539
9540 /* [AS] */
9541 void GetOutOfBookInfo( char * buf )
9542 {
9543     int oob[2];
9544     int i;
9545     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9546
9547     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9548     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9549
9550     *buf = '\0';
9551
9552     if( oob[0] >= 0 || oob[1] >= 0 ) {
9553         for( i=0; i<2; i++ ) {
9554             int idx = oob[i];
9555
9556             if( idx >= 0 ) {
9557                 if( i > 0 && oob[0] >= 0 ) {
9558                     strcat( buf, "   " );
9559                 }
9560
9561                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9562                 sprintf( buf+strlen(buf), "%s%.2f", 
9563                     pvInfoList[idx].score >= 0 ? "+" : "",
9564                     pvInfoList[idx].score / 100.0 );
9565             }
9566         }
9567     }
9568 }
9569
9570 /* Save game in PGN style and close the file */
9571 int
9572 SaveGamePGN(f)
9573      FILE *f;
9574 {
9575     int i, offset, linelen, newblock;
9576     time_t tm;
9577 //    char *movetext;
9578     char numtext[32];
9579     int movelen, numlen, blank;
9580     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9581
9582     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9583     
9584     tm = time((time_t *) NULL);
9585     
9586     PrintPGNTags(f, &gameInfo);
9587     
9588     if (backwardMostMove > 0 || startedFromSetupPosition) {
9589         char *fen = PositionToFEN(backwardMostMove, NULL);
9590         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9591         fprintf(f, "\n{--------------\n");
9592         PrintPosition(f, backwardMostMove);
9593         fprintf(f, "--------------}\n");
9594         free(fen);
9595     }
9596     else {
9597         /* [AS] Out of book annotation */
9598         if( appData.saveOutOfBookInfo ) {
9599             char buf[64];
9600
9601             GetOutOfBookInfo( buf );
9602
9603             if( buf[0] != '\0' ) {
9604                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9605             }
9606         }
9607
9608         fprintf(f, "\n");
9609     }
9610
9611     i = backwardMostMove;
9612     linelen = 0;
9613     newblock = TRUE;
9614
9615     while (i < forwardMostMove) {
9616         /* Print comments preceding this move */
9617         if (commentList[i] != NULL) {
9618             if (linelen > 0) fprintf(f, "\n");
9619             fprintf(f, "{\n%s}\n", commentList[i]);
9620             linelen = 0;
9621             newblock = TRUE;
9622         }
9623
9624         /* Format move number */
9625         if ((i % 2) == 0) {
9626             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9627         } else {
9628             if (newblock) {
9629                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9630             } else {
9631                 numtext[0] = NULLCHAR;
9632             }
9633         }
9634         numlen = strlen(numtext);
9635         newblock = FALSE;
9636
9637         /* Print move number */
9638         blank = linelen > 0 && numlen > 0;
9639         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9640             fprintf(f, "\n");
9641             linelen = 0;
9642             blank = 0;
9643         }
9644         if (blank) {
9645             fprintf(f, " ");
9646             linelen++;
9647         }
9648         fprintf(f, "%s", numtext);
9649         linelen += numlen;
9650
9651         /* Get move */
9652         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9653         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9654
9655         /* Print move */
9656         blank = linelen > 0 && movelen > 0;
9657         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9658             fprintf(f, "\n");
9659             linelen = 0;
9660             blank = 0;
9661         }
9662         if (blank) {
9663             fprintf(f, " ");
9664             linelen++;
9665         }
9666         fprintf(f, "%s", move_buffer);
9667         linelen += movelen;
9668
9669         /* [AS] Add PV info if present */
9670         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9671             /* [HGM] add time */
9672             char buf[MSG_SIZ]; int seconds = 0;
9673
9674             if(i >= backwardMostMove) {
9675                 if(WhiteOnMove(i))
9676                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9677                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9678                 else
9679                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9680                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9681             }
9682             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9683
9684             if( seconds <= 0) buf[0] = 0; else
9685             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9686                 seconds = (seconds + 4)/10; // round to full seconds
9687                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9688                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9689             }
9690
9691             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9692                 pvInfoList[i].score >= 0 ? "+" : "",
9693                 pvInfoList[i].score / 100.0,
9694                 pvInfoList[i].depth,
9695                 buf );
9696
9697             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9698
9699             /* Print score/depth */
9700             blank = linelen > 0 && movelen > 0;
9701             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9702                 fprintf(f, "\n");
9703                 linelen = 0;
9704                 blank = 0;
9705             }
9706             if (blank) {
9707                 fprintf(f, " ");
9708                 linelen++;
9709             }
9710             fprintf(f, "%s", move_buffer);
9711             linelen += movelen;
9712         }
9713
9714         i++;
9715     }
9716     
9717     /* Start a new line */
9718     if (linelen > 0) fprintf(f, "\n");
9719
9720     /* Print comments after last move */
9721     if (commentList[i] != NULL) {
9722         fprintf(f, "{\n%s}\n", commentList[i]);
9723     }
9724
9725     /* Print result */
9726     if (gameInfo.resultDetails != NULL &&
9727         gameInfo.resultDetails[0] != NULLCHAR) {
9728         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9729                 PGNResult(gameInfo.result));
9730     } else {
9731         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9732     }
9733
9734     fclose(f);
9735     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9736     return TRUE;
9737 }
9738
9739 /* Save game in old style and close the file */
9740 int
9741 SaveGameOldStyle(f)
9742      FILE *f;
9743 {
9744     int i, offset;
9745     time_t tm;
9746     
9747     tm = time((time_t *) NULL);
9748     
9749     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9750     PrintOpponents(f);
9751     
9752     if (backwardMostMove > 0 || startedFromSetupPosition) {
9753         fprintf(f, "\n[--------------\n");
9754         PrintPosition(f, backwardMostMove);
9755         fprintf(f, "--------------]\n");
9756     } else {
9757         fprintf(f, "\n");
9758     }
9759
9760     i = backwardMostMove;
9761     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9762
9763     while (i < forwardMostMove) {
9764         if (commentList[i] != NULL) {
9765             fprintf(f, "[%s]\n", commentList[i]);
9766         }
9767
9768         if ((i % 2) == 1) {
9769             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9770             i++;
9771         } else {
9772             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9773             i++;
9774             if (commentList[i] != NULL) {
9775                 fprintf(f, "\n");
9776                 continue;
9777             }
9778             if (i >= forwardMostMove) {
9779                 fprintf(f, "\n");
9780                 break;
9781             }
9782             fprintf(f, "%s\n", parseList[i]);
9783             i++;
9784         }
9785     }
9786     
9787     if (commentList[i] != NULL) {
9788         fprintf(f, "[%s]\n", commentList[i]);
9789     }
9790
9791     /* This isn't really the old style, but it's close enough */
9792     if (gameInfo.resultDetails != NULL &&
9793         gameInfo.resultDetails[0] != NULLCHAR) {
9794         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9795                 gameInfo.resultDetails);
9796     } else {
9797         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9798     }
9799
9800     fclose(f);
9801     return TRUE;
9802 }
9803
9804 /* Save the current game to open file f and close the file */
9805 int
9806 SaveGame(f, dummy, dummy2)
9807      FILE *f;
9808      int dummy;
9809      char *dummy2;
9810 {
9811     if (gameMode == EditPosition) EditPositionDone();
9812     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9813     if (appData.oldSaveStyle)
9814       return SaveGameOldStyle(f);
9815     else
9816       return SaveGamePGN(f);
9817 }
9818
9819 /* Save the current position to the given file */
9820 int
9821 SavePositionToFile(filename)
9822      char *filename;
9823 {
9824     FILE *f;
9825     char buf[MSG_SIZ];
9826
9827     if (strcmp(filename, "-") == 0) {
9828         return SavePosition(stdout, 0, NULL);
9829     } else {
9830         f = fopen(filename, "a");
9831         if (f == NULL) {
9832             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9833             DisplayError(buf, errno);
9834             return FALSE;
9835         } else {
9836             SavePosition(f, 0, NULL);
9837             return TRUE;
9838         }
9839     }
9840 }
9841
9842 /* Save the current position to the given open file and close the file */
9843 int
9844 SavePosition(f, dummy, dummy2)
9845      FILE *f;
9846      int dummy;
9847      char *dummy2;
9848 {
9849     time_t tm;
9850     char *fen;
9851     
9852     if (appData.oldSaveStyle) {
9853         tm = time((time_t *) NULL);
9854     
9855         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9856         PrintOpponents(f);
9857         fprintf(f, "[--------------\n");
9858         PrintPosition(f, currentMove);
9859         fprintf(f, "--------------]\n");
9860     } else {
9861         fen = PositionToFEN(currentMove, NULL);
9862         fprintf(f, "%s\n", fen);
9863         free(fen);
9864     }
9865     fclose(f);
9866     return TRUE;
9867 }
9868
9869 void
9870 ReloadCmailMsgEvent(unregister)
9871      int unregister;
9872 {
9873 #if !WIN32
9874     static char *inFilename = NULL;
9875     static char *outFilename;
9876     int i;
9877     struct stat inbuf, outbuf;
9878     int status;
9879     
9880     /* Any registered moves are unregistered if unregister is set, */
9881     /* i.e. invoked by the signal handler */
9882     if (unregister) {
9883         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9884             cmailMoveRegistered[i] = FALSE;
9885             if (cmailCommentList[i] != NULL) {
9886                 free(cmailCommentList[i]);
9887                 cmailCommentList[i] = NULL;
9888             }
9889         }
9890         nCmailMovesRegistered = 0;
9891     }
9892
9893     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9894         cmailResult[i] = CMAIL_NOT_RESULT;
9895     }
9896     nCmailResults = 0;
9897
9898     if (inFilename == NULL) {
9899         /* Because the filenames are static they only get malloced once  */
9900         /* and they never get freed                                      */
9901         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9902         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9903
9904         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9905         sprintf(outFilename, "%s.out", appData.cmailGameName);
9906     }
9907     
9908     status = stat(outFilename, &outbuf);
9909     if (status < 0) {
9910         cmailMailedMove = FALSE;
9911     } else {
9912         status = stat(inFilename, &inbuf);
9913         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9914     }
9915     
9916     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9917        counts the games, notes how each one terminated, etc.
9918        
9919        It would be nice to remove this kludge and instead gather all
9920        the information while building the game list.  (And to keep it
9921        in the game list nodes instead of having a bunch of fixed-size
9922        parallel arrays.)  Note this will require getting each game's
9923        termination from the PGN tags, as the game list builder does
9924        not process the game moves.  --mann
9925        */
9926     cmailMsgLoaded = TRUE;
9927     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9928     
9929     /* Load first game in the file or popup game menu */
9930     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9931
9932 #endif /* !WIN32 */
9933     return;
9934 }
9935
9936 int
9937 RegisterMove()
9938 {
9939     FILE *f;
9940     char string[MSG_SIZ];
9941
9942     if (   cmailMailedMove
9943         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9944         return TRUE;            /* Allow free viewing  */
9945     }
9946
9947     /* Unregister move to ensure that we don't leave RegisterMove        */
9948     /* with the move registered when the conditions for registering no   */
9949     /* longer hold                                                       */
9950     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9951         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9952         nCmailMovesRegistered --;
9953
9954         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9955           {
9956               free(cmailCommentList[lastLoadGameNumber - 1]);
9957               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9958           }
9959     }
9960
9961     if (cmailOldMove == -1) {
9962         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9963         return FALSE;
9964     }
9965
9966     if (currentMove > cmailOldMove + 1) {
9967         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9968         return FALSE;
9969     }
9970
9971     if (currentMove < cmailOldMove) {
9972         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9973         return FALSE;
9974     }
9975
9976     if (forwardMostMove > currentMove) {
9977         /* Silently truncate extra moves */
9978         TruncateGame();
9979     }
9980
9981     if (   (currentMove == cmailOldMove + 1)
9982         || (   (currentMove == cmailOldMove)
9983             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9984                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9985         if (gameInfo.result != GameUnfinished) {
9986             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9987         }
9988
9989         if (commentList[currentMove] != NULL) {
9990             cmailCommentList[lastLoadGameNumber - 1]
9991               = StrSave(commentList[currentMove]);
9992         }
9993         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9994
9995         if (appData.debugMode)
9996           fprintf(debugFP, "Saving %s for game %d\n",
9997                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9998
9999         sprintf(string,
10000                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10001         
10002         f = fopen(string, "w");
10003         if (appData.oldSaveStyle) {
10004             SaveGameOldStyle(f); /* also closes the file */
10005             
10006             sprintf(string, "%s.pos.out", appData.cmailGameName);
10007             f = fopen(string, "w");
10008             SavePosition(f, 0, NULL); /* also closes the file */
10009         } else {
10010             fprintf(f, "{--------------\n");
10011             PrintPosition(f, currentMove);
10012             fprintf(f, "--------------}\n\n");
10013             
10014             SaveGame(f, 0, NULL); /* also closes the file*/
10015         }
10016         
10017         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10018         nCmailMovesRegistered ++;
10019     } else if (nCmailGames == 1) {
10020         DisplayError(_("You have not made a move yet"), 0);
10021         return FALSE;
10022     }
10023
10024     return TRUE;
10025 }
10026
10027 void
10028 MailMoveEvent()
10029 {
10030 #if !WIN32
10031     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10032     FILE *commandOutput;
10033     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10034     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10035     int nBuffers;
10036     int i;
10037     int archived;
10038     char *arcDir;
10039
10040     if (! cmailMsgLoaded) {
10041         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10042         return;
10043     }
10044
10045     if (nCmailGames == nCmailResults) {
10046         DisplayError(_("No unfinished games"), 0);
10047         return;
10048     }
10049
10050 #if CMAIL_PROHIBIT_REMAIL
10051     if (cmailMailedMove) {
10052         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);
10053         DisplayError(msg, 0);
10054         return;
10055     }
10056 #endif
10057
10058     if (! (cmailMailedMove || RegisterMove())) return;
10059     
10060     if (   cmailMailedMove
10061         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10062         sprintf(string, partCommandString,
10063                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10064         commandOutput = popen(string, "r");
10065
10066         if (commandOutput == NULL) {
10067             DisplayError(_("Failed to invoke cmail"), 0);
10068         } else {
10069             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10070                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10071             }
10072             if (nBuffers > 1) {
10073                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10074                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10075                 nBytes = MSG_SIZ - 1;
10076             } else {
10077                 (void) memcpy(msg, buffer, nBytes);
10078             }
10079             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10080
10081             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10082                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10083
10084                 archived = TRUE;
10085                 for (i = 0; i < nCmailGames; i ++) {
10086                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10087                         archived = FALSE;
10088                     }
10089                 }
10090                 if (   archived
10091                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10092                         != NULL)) {
10093                     sprintf(buffer, "%s/%s.%s.archive",
10094                             arcDir,
10095                             appData.cmailGameName,
10096                             gameInfo.date);
10097                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10098                     cmailMsgLoaded = FALSE;
10099                 }
10100             }
10101
10102             DisplayInformation(msg);
10103             pclose(commandOutput);
10104         }
10105     } else {
10106         if ((*cmailMsg) != '\0') {
10107             DisplayInformation(cmailMsg);
10108         }
10109     }
10110
10111     return;
10112 #endif /* !WIN32 */
10113 }
10114
10115 char *
10116 CmailMsg()
10117 {
10118 #if WIN32
10119     return NULL;
10120 #else
10121     int  prependComma = 0;
10122     char number[5];
10123     char string[MSG_SIZ];       /* Space for game-list */
10124     int  i;
10125     
10126     if (!cmailMsgLoaded) return "";
10127
10128     if (cmailMailedMove) {
10129         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10130     } else {
10131         /* Create a list of games left */
10132         sprintf(string, "[");
10133         for (i = 0; i < nCmailGames; i ++) {
10134             if (! (   cmailMoveRegistered[i]
10135                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10136                 if (prependComma) {
10137                     sprintf(number, ",%d", i + 1);
10138                 } else {
10139                     sprintf(number, "%d", i + 1);
10140                     prependComma = 1;
10141                 }
10142                 
10143                 strcat(string, number);
10144             }
10145         }
10146         strcat(string, "]");
10147
10148         if (nCmailMovesRegistered + nCmailResults == 0) {
10149             switch (nCmailGames) {
10150               case 1:
10151                 sprintf(cmailMsg,
10152                         _("Still need to make move for game\n"));
10153                 break;
10154                 
10155               case 2:
10156                 sprintf(cmailMsg,
10157                         _("Still need to make moves for both games\n"));
10158                 break;
10159                 
10160               default:
10161                 sprintf(cmailMsg,
10162                         _("Still need to make moves for all %d games\n"),
10163                         nCmailGames);
10164                 break;
10165             }
10166         } else {
10167             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10168               case 1:
10169                 sprintf(cmailMsg,
10170                         _("Still need to make a move for game %s\n"),
10171                         string);
10172                 break;
10173                 
10174               case 0:
10175                 if (nCmailResults == nCmailGames) {
10176                     sprintf(cmailMsg, _("No unfinished games\n"));
10177                 } else {
10178                     sprintf(cmailMsg, _("Ready to send mail\n"));
10179                 }
10180                 break;
10181                 
10182               default:
10183                 sprintf(cmailMsg,
10184                         _("Still need to make moves for games %s\n"),
10185                         string);
10186             }
10187         }
10188     }
10189     return cmailMsg;
10190 #endif /* WIN32 */
10191 }
10192
10193 void
10194 ResetGameEvent()
10195 {
10196     if (gameMode == Training)
10197       SetTrainingModeOff();
10198
10199     Reset(TRUE, TRUE);
10200     cmailMsgLoaded = FALSE;
10201     if (appData.icsActive) {
10202       SendToICS(ics_prefix);
10203       SendToICS("refresh\n");
10204     }
10205 }
10206
10207 void
10208 ExitEvent(status)
10209      int status;
10210 {
10211     exiting++;
10212     if (exiting > 2) {
10213       /* Give up on clean exit */
10214       exit(status);
10215     }
10216     if (exiting > 1) {
10217       /* Keep trying for clean exit */
10218       return;
10219     }
10220
10221     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10222
10223     if (telnetISR != NULL) {
10224       RemoveInputSource(telnetISR);
10225     }
10226     if (icsPR != NoProc) {
10227       DestroyChildProcess(icsPR, TRUE);
10228     }
10229
10230     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10231     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10232
10233     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10234     /* make sure this other one finishes before killing it!                  */
10235     if(endingGame) { int count = 0;
10236         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10237         while(endingGame && count++ < 10) DoSleep(1);
10238         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10239     }
10240
10241     /* Kill off chess programs */
10242     if (first.pr != NoProc) {
10243         ExitAnalyzeMode();
10244         
10245         DoSleep( appData.delayBeforeQuit );
10246         SendToProgram("quit\n", &first);
10247         DoSleep( appData.delayAfterQuit );
10248         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10249     }
10250     if (second.pr != NoProc) {
10251         DoSleep( appData.delayBeforeQuit );
10252         SendToProgram("quit\n", &second);
10253         DoSleep( appData.delayAfterQuit );
10254         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10255     }
10256     if (first.isr != NULL) {
10257         RemoveInputSource(first.isr);
10258     }
10259     if (second.isr != NULL) {
10260         RemoveInputSource(second.isr);
10261     }
10262
10263     ShutDownFrontEnd();
10264     exit(status);
10265 }
10266
10267 void
10268 PauseEvent()
10269 {
10270     if (appData.debugMode)
10271         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10272     if (pausing) {
10273         pausing = FALSE;
10274         ModeHighlight();
10275         if (gameMode == MachinePlaysWhite ||
10276             gameMode == MachinePlaysBlack) {
10277             StartClocks();
10278         } else {
10279             DisplayBothClocks();
10280         }
10281         if (gameMode == PlayFromGameFile) {
10282             if (appData.timeDelay >= 0) 
10283                 AutoPlayGameLoop();
10284         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10285             Reset(FALSE, TRUE);
10286             SendToICS(ics_prefix);
10287             SendToICS("refresh\n");
10288         } else if (currentMove < forwardMostMove) {
10289             ForwardInner(forwardMostMove);
10290         }
10291         pauseExamInvalid = FALSE;
10292     } else {
10293         switch (gameMode) {
10294           default:
10295             return;
10296           case IcsExamining:
10297             pauseExamForwardMostMove = forwardMostMove;
10298             pauseExamInvalid = FALSE;
10299             /* fall through */
10300           case IcsObserving:
10301           case IcsPlayingWhite:
10302           case IcsPlayingBlack:
10303             pausing = TRUE;
10304             ModeHighlight();
10305             return;
10306           case PlayFromGameFile:
10307             (void) StopLoadGameTimer();
10308             pausing = TRUE;
10309             ModeHighlight();
10310             break;
10311           case BeginningOfGame:
10312             if (appData.icsActive) return;
10313             /* else fall through */
10314           case MachinePlaysWhite:
10315           case MachinePlaysBlack:
10316           case TwoMachinesPlay:
10317             if (forwardMostMove == 0)
10318               return;           /* don't pause if no one has moved */
10319             if ((gameMode == MachinePlaysWhite &&
10320                  !WhiteOnMove(forwardMostMove)) ||
10321                 (gameMode == MachinePlaysBlack &&
10322                  WhiteOnMove(forwardMostMove))) {
10323                 StopClocks();
10324             }
10325             pausing = TRUE;
10326             ModeHighlight();
10327             break;
10328         }
10329     }
10330 }
10331
10332 void
10333 EditCommentEvent()
10334 {
10335     char title[MSG_SIZ];
10336
10337     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10338         strcpy(title, _("Edit comment"));
10339     } else {
10340         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10341                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10342                 parseList[currentMove - 1]);
10343     }
10344
10345     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10346 }
10347
10348
10349 void
10350 EditTagsEvent()
10351 {
10352     char *tags = PGNTags(&gameInfo);
10353     EditTagsPopUp(tags);
10354     free(tags);
10355 }
10356
10357 void
10358 AnalyzeModeEvent()
10359 {
10360     if (appData.noChessProgram || gameMode == AnalyzeMode)
10361       return;
10362
10363     if (gameMode != AnalyzeFile) {
10364         if (!appData.icsEngineAnalyze) {
10365                EditGameEvent();
10366                if (gameMode != EditGame) return;
10367         }
10368         ResurrectChessProgram();
10369         SendToProgram("analyze\n", &first);
10370         first.analyzing = TRUE;
10371         /*first.maybeThinking = TRUE;*/
10372         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10373         EngineOutputPopUp();
10374     }
10375     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10376     pausing = FALSE;
10377     ModeHighlight();
10378     SetGameInfo();
10379
10380     StartAnalysisClock();
10381     GetTimeMark(&lastNodeCountTime);
10382     lastNodeCount = 0;
10383 }
10384
10385 void
10386 AnalyzeFileEvent()
10387 {
10388     if (appData.noChessProgram || gameMode == AnalyzeFile)
10389       return;
10390
10391     if (gameMode != AnalyzeMode) {
10392         EditGameEvent();
10393         if (gameMode != EditGame) return;
10394         ResurrectChessProgram();
10395         SendToProgram("analyze\n", &first);
10396         first.analyzing = TRUE;
10397         /*first.maybeThinking = TRUE;*/
10398         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10399         EngineOutputPopUp();
10400     }
10401     gameMode = AnalyzeFile;
10402     pausing = FALSE;
10403     ModeHighlight();
10404     SetGameInfo();
10405
10406     StartAnalysisClock();
10407     GetTimeMark(&lastNodeCountTime);
10408     lastNodeCount = 0;
10409 }
10410
10411 void
10412 MachineWhiteEvent()
10413 {
10414     char buf[MSG_SIZ];
10415     char *bookHit = NULL;
10416
10417     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10418       return;
10419
10420
10421     if (gameMode == PlayFromGameFile || 
10422         gameMode == TwoMachinesPlay  || 
10423         gameMode == Training         || 
10424         gameMode == AnalyzeMode      || 
10425         gameMode == EndOfGame)
10426         EditGameEvent();
10427
10428     if (gameMode == EditPosition) 
10429         EditPositionDone();
10430
10431     if (!WhiteOnMove(currentMove)) {
10432         DisplayError(_("It is not White's turn"), 0);
10433         return;
10434     }
10435   
10436     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10437       ExitAnalyzeMode();
10438
10439     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10440         gameMode == AnalyzeFile)
10441         TruncateGame();
10442
10443     ResurrectChessProgram();    /* in case it isn't running */
10444     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10445         gameMode = MachinePlaysWhite;
10446         ResetClocks();
10447     } else
10448     gameMode = MachinePlaysWhite;
10449     pausing = FALSE;
10450     ModeHighlight();
10451     SetGameInfo();
10452     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10453     DisplayTitle(buf);
10454     if (first.sendName) {
10455       sprintf(buf, "name %s\n", gameInfo.black);
10456       SendToProgram(buf, &first);
10457     }
10458     if (first.sendTime) {
10459       if (first.useColors) {
10460         SendToProgram("black\n", &first); /*gnu kludge*/
10461       }
10462       SendTimeRemaining(&first, TRUE);
10463     }
10464     if (first.useColors) {
10465       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10466     }
10467     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10468     SetMachineThinkingEnables();
10469     first.maybeThinking = TRUE;
10470     StartClocks();
10471     firstMove = FALSE;
10472
10473     if (appData.autoFlipView && !flipView) {
10474       flipView = !flipView;
10475       DrawPosition(FALSE, NULL);
10476       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10477     }
10478
10479     if(bookHit) { // [HGM] book: simulate book reply
10480         static char bookMove[MSG_SIZ]; // a bit generous?
10481
10482         programStats.nodes = programStats.depth = programStats.time = 
10483         programStats.score = programStats.got_only_move = 0;
10484         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10485
10486         strcpy(bookMove, "move ");
10487         strcat(bookMove, bookHit);
10488         HandleMachineMove(bookMove, &first);
10489     }
10490 }
10491
10492 void
10493 MachineBlackEvent()
10494 {
10495     char buf[MSG_SIZ];
10496    char *bookHit = NULL;
10497
10498     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10499         return;
10500
10501
10502     if (gameMode == PlayFromGameFile || 
10503         gameMode == TwoMachinesPlay  || 
10504         gameMode == Training         || 
10505         gameMode == AnalyzeMode      || 
10506         gameMode == EndOfGame)
10507         EditGameEvent();
10508
10509     if (gameMode == EditPosition) 
10510         EditPositionDone();
10511
10512     if (WhiteOnMove(currentMove)) {
10513         DisplayError(_("It is not Black's turn"), 0);
10514         return;
10515     }
10516     
10517     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10518       ExitAnalyzeMode();
10519
10520     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10521         gameMode == AnalyzeFile)
10522         TruncateGame();
10523
10524     ResurrectChessProgram();    /* in case it isn't running */
10525     gameMode = MachinePlaysBlack;
10526     pausing = FALSE;
10527     ModeHighlight();
10528     SetGameInfo();
10529     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10530     DisplayTitle(buf);
10531     if (first.sendName) {
10532       sprintf(buf, "name %s\n", gameInfo.white);
10533       SendToProgram(buf, &first);
10534     }
10535     if (first.sendTime) {
10536       if (first.useColors) {
10537         SendToProgram("white\n", &first); /*gnu kludge*/
10538       }
10539       SendTimeRemaining(&first, FALSE);
10540     }
10541     if (first.useColors) {
10542       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10543     }
10544     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10545     SetMachineThinkingEnables();
10546     first.maybeThinking = TRUE;
10547     StartClocks();
10548
10549     if (appData.autoFlipView && flipView) {
10550       flipView = !flipView;
10551       DrawPosition(FALSE, NULL);
10552       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10553     }
10554     if(bookHit) { // [HGM] book: simulate book reply
10555         static char bookMove[MSG_SIZ]; // a bit generous?
10556
10557         programStats.nodes = programStats.depth = programStats.time = 
10558         programStats.score = programStats.got_only_move = 0;
10559         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10560
10561         strcpy(bookMove, "move ");
10562         strcat(bookMove, bookHit);
10563         HandleMachineMove(bookMove, &first);
10564     }
10565 }
10566
10567
10568 void
10569 DisplayTwoMachinesTitle()
10570 {
10571     char buf[MSG_SIZ];
10572     if (appData.matchGames > 0) {
10573         if (first.twoMachinesColor[0] == 'w') {
10574             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10575                     gameInfo.white, gameInfo.black,
10576                     first.matchWins, second.matchWins,
10577                     matchGame - 1 - (first.matchWins + second.matchWins));
10578         } else {
10579             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10580                     gameInfo.white, gameInfo.black,
10581                     second.matchWins, first.matchWins,
10582                     matchGame - 1 - (first.matchWins + second.matchWins));
10583         }
10584     } else {
10585         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10586     }
10587     DisplayTitle(buf);
10588 }
10589
10590 void
10591 TwoMachinesEvent P((void))
10592 {
10593     int i;
10594     char buf[MSG_SIZ];
10595     ChessProgramState *onmove;
10596     char *bookHit = NULL;
10597     
10598     if (appData.noChessProgram) return;
10599
10600     switch (gameMode) {
10601       case TwoMachinesPlay:
10602         return;
10603       case MachinePlaysWhite:
10604       case MachinePlaysBlack:
10605         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10606             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10607             return;
10608         }
10609         /* fall through */
10610       case BeginningOfGame:
10611       case PlayFromGameFile:
10612       case EndOfGame:
10613         EditGameEvent();
10614         if (gameMode != EditGame) return;
10615         break;
10616       case EditPosition:
10617         EditPositionDone();
10618         break;
10619       case AnalyzeMode:
10620       case AnalyzeFile:
10621         ExitAnalyzeMode();
10622         break;
10623       case EditGame:
10624       default:
10625         break;
10626     }
10627
10628     forwardMostMove = currentMove;
10629     ResurrectChessProgram();    /* in case first program isn't running */
10630
10631     if (second.pr == NULL) {
10632         StartChessProgram(&second);
10633         if (second.protocolVersion == 1) {
10634           TwoMachinesEventIfReady();
10635         } else {
10636           /* kludge: allow timeout for initial "feature" command */
10637           FreezeUI();
10638           DisplayMessage("", _("Starting second chess program"));
10639           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10640         }
10641         return;
10642     }
10643     DisplayMessage("", "");
10644     InitChessProgram(&second, FALSE);
10645     SendToProgram("force\n", &second);
10646     if (startedFromSetupPosition) {
10647         SendBoard(&second, backwardMostMove);
10648     if (appData.debugMode) {
10649         fprintf(debugFP, "Two Machines\n");
10650     }
10651     }
10652     for (i = backwardMostMove; i < forwardMostMove; i++) {
10653         SendMoveToProgram(i, &second);
10654     }
10655
10656     gameMode = TwoMachinesPlay;
10657     pausing = FALSE;
10658     ModeHighlight();
10659     SetGameInfo();
10660     DisplayTwoMachinesTitle();
10661     firstMove = TRUE;
10662     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10663         onmove = &first;
10664     } else {
10665         onmove = &second;
10666     }
10667
10668     SendToProgram(first.computerString, &first);
10669     if (first.sendName) {
10670       sprintf(buf, "name %s\n", second.tidy);
10671       SendToProgram(buf, &first);
10672     }
10673     SendToProgram(second.computerString, &second);
10674     if (second.sendName) {
10675       sprintf(buf, "name %s\n", first.tidy);
10676       SendToProgram(buf, &second);
10677     }
10678
10679     ResetClocks();
10680     if (!first.sendTime || !second.sendTime) {
10681         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10682         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10683     }
10684     if (onmove->sendTime) {
10685       if (onmove->useColors) {
10686         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10687       }
10688       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10689     }
10690     if (onmove->useColors) {
10691       SendToProgram(onmove->twoMachinesColor, onmove);
10692     }
10693     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10694 //    SendToProgram("go\n", onmove);
10695     onmove->maybeThinking = TRUE;
10696     SetMachineThinkingEnables();
10697
10698     StartClocks();
10699
10700     if(bookHit) { // [HGM] book: simulate book reply
10701         static char bookMove[MSG_SIZ]; // a bit generous?
10702
10703         programStats.nodes = programStats.depth = programStats.time = 
10704         programStats.score = programStats.got_only_move = 0;
10705         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10706
10707         strcpy(bookMove, "move ");
10708         strcat(bookMove, bookHit);
10709         HandleMachineMove(bookMove, &first);
10710     }
10711 }
10712
10713 void
10714 TrainingEvent()
10715 {
10716     if (gameMode == Training) {
10717       SetTrainingModeOff();
10718       gameMode = PlayFromGameFile;
10719       DisplayMessage("", _("Training mode off"));
10720     } else {
10721       gameMode = Training;
10722       animateTraining = appData.animate;
10723
10724       /* make sure we are not already at the end of the game */
10725       if (currentMove < forwardMostMove) {
10726         SetTrainingModeOn();
10727         DisplayMessage("", _("Training mode on"));
10728       } else {
10729         gameMode = PlayFromGameFile;
10730         DisplayError(_("Already at end of game"), 0);
10731       }
10732     }
10733     ModeHighlight();
10734 }
10735
10736 void
10737 IcsClientEvent()
10738 {
10739     if (!appData.icsActive) return;
10740     switch (gameMode) {
10741       case IcsPlayingWhite:
10742       case IcsPlayingBlack:
10743       case IcsObserving:
10744       case IcsIdle:
10745       case BeginningOfGame:
10746       case IcsExamining:
10747         return;
10748
10749       case EditGame:
10750         break;
10751
10752       case EditPosition:
10753         EditPositionDone();
10754         break;
10755
10756       case AnalyzeMode:
10757       case AnalyzeFile:
10758         ExitAnalyzeMode();
10759         break;
10760         
10761       default:
10762         EditGameEvent();
10763         break;
10764     }
10765
10766     gameMode = IcsIdle;
10767     ModeHighlight();
10768     return;
10769 }
10770
10771
10772 void
10773 EditGameEvent()
10774 {
10775     int i;
10776
10777     switch (gameMode) {
10778       case Training:
10779         SetTrainingModeOff();
10780         break;
10781       case MachinePlaysWhite:
10782       case MachinePlaysBlack:
10783       case BeginningOfGame:
10784         SendToProgram("force\n", &first);
10785         SetUserThinkingEnables();
10786         break;
10787       case PlayFromGameFile:
10788         (void) StopLoadGameTimer();
10789         if (gameFileFP != NULL) {
10790             gameFileFP = NULL;
10791         }
10792         break;
10793       case EditPosition:
10794         EditPositionDone();
10795         break;
10796       case AnalyzeMode:
10797       case AnalyzeFile:
10798         ExitAnalyzeMode();
10799         SendToProgram("force\n", &first);
10800         break;
10801       case TwoMachinesPlay:
10802         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10803         ResurrectChessProgram();
10804         SetUserThinkingEnables();
10805         break;
10806       case EndOfGame:
10807         ResurrectChessProgram();
10808         break;
10809       case IcsPlayingBlack:
10810       case IcsPlayingWhite:
10811         DisplayError(_("Warning: You are still playing a game"), 0);
10812         break;
10813       case IcsObserving:
10814         DisplayError(_("Warning: You are still observing a game"), 0);
10815         break;
10816       case IcsExamining:
10817         DisplayError(_("Warning: You are still examining a game"), 0);
10818         break;
10819       case IcsIdle:
10820         break;
10821       case EditGame:
10822       default:
10823         return;
10824     }
10825     
10826     pausing = FALSE;
10827     StopClocks();
10828     first.offeredDraw = second.offeredDraw = 0;
10829
10830     if (gameMode == PlayFromGameFile) {
10831         whiteTimeRemaining = timeRemaining[0][currentMove];
10832         blackTimeRemaining = timeRemaining[1][currentMove];
10833         DisplayTitle("");
10834     }
10835
10836     if (gameMode == MachinePlaysWhite ||
10837         gameMode == MachinePlaysBlack ||
10838         gameMode == TwoMachinesPlay ||
10839         gameMode == EndOfGame) {
10840         i = forwardMostMove;
10841         while (i > currentMove) {
10842             SendToProgram("undo\n", &first);
10843             i--;
10844         }
10845         whiteTimeRemaining = timeRemaining[0][currentMove];
10846         blackTimeRemaining = timeRemaining[1][currentMove];
10847         DisplayBothClocks();
10848         if (whiteFlag || blackFlag) {
10849             whiteFlag = blackFlag = 0;
10850         }
10851         DisplayTitle("");
10852     }           
10853     
10854     gameMode = EditGame;
10855     ModeHighlight();
10856     SetGameInfo();
10857 }
10858
10859
10860 void
10861 EditPositionEvent()
10862 {
10863     if (gameMode == EditPosition) {
10864         EditGameEvent();
10865         return;
10866     }
10867     
10868     EditGameEvent();
10869     if (gameMode != EditGame) return;
10870     
10871     gameMode = EditPosition;
10872     ModeHighlight();
10873     SetGameInfo();
10874     if (currentMove > 0)
10875       CopyBoard(boards[0], boards[currentMove]);
10876     
10877     blackPlaysFirst = !WhiteOnMove(currentMove);
10878     ResetClocks();
10879     currentMove = forwardMostMove = backwardMostMove = 0;
10880     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10881     DisplayMove(-1);
10882 }
10883
10884 void
10885 ExitAnalyzeMode()
10886 {
10887     /* [DM] icsEngineAnalyze - possible call from other functions */
10888     if (appData.icsEngineAnalyze) {
10889         appData.icsEngineAnalyze = FALSE;
10890
10891         DisplayMessage("",_("Close ICS engine analyze..."));
10892     }
10893     if (first.analysisSupport && first.analyzing) {
10894       SendToProgram("exit\n", &first);
10895       first.analyzing = FALSE;
10896     }
10897     thinkOutput[0] = NULLCHAR;
10898 }
10899
10900 void
10901 EditPositionDone()
10902 {
10903     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10904
10905     startedFromSetupPosition = TRUE;
10906     InitChessProgram(&first, FALSE);
10907     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10908     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10909         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10910         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10911     } else castlingRights[0][2] = -1;
10912     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10913         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10914         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10915     } else castlingRights[0][5] = -1;
10916     SendToProgram("force\n", &first);
10917     if (blackPlaysFirst) {
10918         strcpy(moveList[0], "");
10919         strcpy(parseList[0], "");
10920         currentMove = forwardMostMove = backwardMostMove = 1;
10921         CopyBoard(boards[1], boards[0]);
10922         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10923         { int i;
10924           epStatus[1] = epStatus[0];
10925           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10926         }
10927     } else {
10928         currentMove = forwardMostMove = backwardMostMove = 0;
10929     }
10930     SendBoard(&first, forwardMostMove);
10931     if (appData.debugMode) {
10932         fprintf(debugFP, "EditPosDone\n");
10933     }
10934     DisplayTitle("");
10935     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10936     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10937     gameMode = EditGame;
10938     ModeHighlight();
10939     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10940     ClearHighlights(); /* [AS] */
10941 }
10942
10943 /* Pause for `ms' milliseconds */
10944 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10945 void
10946 TimeDelay(ms)
10947      long ms;
10948 {
10949     TimeMark m1, m2;
10950
10951     GetTimeMark(&m1);
10952     do {
10953         GetTimeMark(&m2);
10954     } while (SubtractTimeMarks(&m2, &m1) < ms);
10955 }
10956
10957 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10958 void
10959 SendMultiLineToICS(buf)
10960      char *buf;
10961 {
10962     char temp[MSG_SIZ+1], *p;
10963     int len;
10964
10965     len = strlen(buf);
10966     if (len > MSG_SIZ)
10967       len = MSG_SIZ;
10968   
10969     strncpy(temp, buf, len);
10970     temp[len] = 0;
10971
10972     p = temp;
10973     while (*p) {
10974         if (*p == '\n' || *p == '\r')
10975           *p = ' ';
10976         ++p;
10977     }
10978
10979     strcat(temp, "\n");
10980     SendToICS(temp);
10981     SendToPlayer(temp, strlen(temp));
10982 }
10983
10984 void
10985 SetWhiteToPlayEvent()
10986 {
10987     if (gameMode == EditPosition) {
10988         blackPlaysFirst = FALSE;
10989         DisplayBothClocks();    /* works because currentMove is 0 */
10990     } else if (gameMode == IcsExamining) {
10991         SendToICS(ics_prefix);
10992         SendToICS("tomove white\n");
10993     }
10994 }
10995
10996 void
10997 SetBlackToPlayEvent()
10998 {
10999     if (gameMode == EditPosition) {
11000         blackPlaysFirst = TRUE;
11001         currentMove = 1;        /* kludge */
11002         DisplayBothClocks();
11003         currentMove = 0;
11004     } else if (gameMode == IcsExamining) {
11005         SendToICS(ics_prefix);
11006         SendToICS("tomove black\n");
11007     }
11008 }
11009
11010 void
11011 EditPositionMenuEvent(selection, x, y)
11012      ChessSquare selection;
11013      int x, y;
11014 {
11015     char buf[MSG_SIZ];
11016     ChessSquare piece = boards[0][y][x];
11017
11018     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11019
11020     switch (selection) {
11021       case ClearBoard:
11022         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11023             SendToICS(ics_prefix);
11024             SendToICS("bsetup clear\n");
11025         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11026             SendToICS(ics_prefix);
11027             SendToICS("clearboard\n");
11028         } else {
11029             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11030                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11031                 for (y = 0; y < BOARD_HEIGHT; y++) {
11032                     if (gameMode == IcsExamining) {
11033                         if (boards[currentMove][y][x] != EmptySquare) {
11034                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11035                                     AAA + x, ONE + y);
11036                             SendToICS(buf);
11037                         }
11038                     } else {
11039                         boards[0][y][x] = p;
11040                     }
11041                 }
11042             }
11043         }
11044         if (gameMode == EditPosition) {
11045             DrawPosition(FALSE, boards[0]);
11046         }
11047         break;
11048
11049       case WhitePlay:
11050         SetWhiteToPlayEvent();
11051         break;
11052
11053       case BlackPlay:
11054         SetBlackToPlayEvent();
11055         break;
11056
11057       case EmptySquare:
11058         if (gameMode == IcsExamining) {
11059             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11060             SendToICS(buf);
11061         } else {
11062             boards[0][y][x] = EmptySquare;
11063             DrawPosition(FALSE, boards[0]);
11064         }
11065         break;
11066
11067       case PromotePiece:
11068         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11069            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11070             selection = (ChessSquare) (PROMOTED piece);
11071         } else if(piece == EmptySquare) selection = WhiteSilver;
11072         else selection = (ChessSquare)((int)piece - 1);
11073         goto defaultlabel;
11074
11075       case DemotePiece:
11076         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11077            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11078             selection = (ChessSquare) (DEMOTED piece);
11079         } else if(piece == EmptySquare) selection = BlackSilver;
11080         else selection = (ChessSquare)((int)piece + 1);       
11081         goto defaultlabel;
11082
11083       case WhiteQueen:
11084       case BlackQueen:
11085         if(gameInfo.variant == VariantShatranj ||
11086            gameInfo.variant == VariantXiangqi  ||
11087            gameInfo.variant == VariantCourier    )
11088             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11089         goto defaultlabel;
11090
11091       case WhiteKing:
11092       case BlackKing:
11093         if(gameInfo.variant == VariantXiangqi)
11094             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11095         if(gameInfo.variant == VariantKnightmate)
11096             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11097       default:
11098         defaultlabel:
11099         if (gameMode == IcsExamining) {
11100             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11101                     PieceToChar(selection), AAA + x, ONE + y);
11102             SendToICS(buf);
11103         } else {
11104             boards[0][y][x] = selection;
11105             DrawPosition(FALSE, boards[0]);
11106         }
11107         break;
11108     }
11109 }
11110
11111
11112 void
11113 DropMenuEvent(selection, x, y)
11114      ChessSquare selection;
11115      int x, y;
11116 {
11117     ChessMove moveType;
11118
11119     switch (gameMode) {
11120       case IcsPlayingWhite:
11121       case MachinePlaysBlack:
11122         if (!WhiteOnMove(currentMove)) {
11123             DisplayMoveError(_("It is Black's turn"));
11124             return;
11125         }
11126         moveType = WhiteDrop;
11127         break;
11128       case IcsPlayingBlack:
11129       case MachinePlaysWhite:
11130         if (WhiteOnMove(currentMove)) {
11131             DisplayMoveError(_("It is White's turn"));
11132             return;
11133         }
11134         moveType = BlackDrop;
11135         break;
11136       case EditGame:
11137         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11138         break;
11139       default:
11140         return;
11141     }
11142
11143     if (moveType == BlackDrop && selection < BlackPawn) {
11144       selection = (ChessSquare) ((int) selection
11145                                  + (int) BlackPawn - (int) WhitePawn);
11146     }
11147     if (boards[currentMove][y][x] != EmptySquare) {
11148         DisplayMoveError(_("That square is occupied"));
11149         return;
11150     }
11151
11152     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11153 }
11154
11155 void
11156 AcceptEvent()
11157 {
11158     /* Accept a pending offer of any kind from opponent */
11159     
11160     if (appData.icsActive) {
11161         SendToICS(ics_prefix);
11162         SendToICS("accept\n");
11163     } else if (cmailMsgLoaded) {
11164         if (currentMove == cmailOldMove &&
11165             commentList[cmailOldMove] != NULL &&
11166             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11167                    "Black offers a draw" : "White offers a draw")) {
11168             TruncateGame();
11169             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11170             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11171         } else {
11172             DisplayError(_("There is no pending offer on this move"), 0);
11173             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11174         }
11175     } else {
11176         /* Not used for offers from chess program */
11177     }
11178 }
11179
11180 void
11181 DeclineEvent()
11182 {
11183     /* Decline a pending offer of any kind from opponent */
11184     
11185     if (appData.icsActive) {
11186         SendToICS(ics_prefix);
11187         SendToICS("decline\n");
11188     } else if (cmailMsgLoaded) {
11189         if (currentMove == cmailOldMove &&
11190             commentList[cmailOldMove] != NULL &&
11191             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11192                    "Black offers a draw" : "White offers a draw")) {
11193 #ifdef NOTDEF
11194             AppendComment(cmailOldMove, "Draw declined");
11195             DisplayComment(cmailOldMove - 1, "Draw declined");
11196 #endif /*NOTDEF*/
11197         } else {
11198             DisplayError(_("There is no pending offer on this move"), 0);
11199         }
11200     } else {
11201         /* Not used for offers from chess program */
11202     }
11203 }
11204
11205 void
11206 RematchEvent()
11207 {
11208     /* Issue ICS rematch command */
11209     if (appData.icsActive) {
11210         SendToICS(ics_prefix);
11211         SendToICS("rematch\n");
11212     }
11213 }
11214
11215 void
11216 CallFlagEvent()
11217 {
11218     /* Call your opponent's flag (claim a win on time) */
11219     if (appData.icsActive) {
11220         SendToICS(ics_prefix);
11221         SendToICS("flag\n");
11222     } else {
11223         switch (gameMode) {
11224           default:
11225             return;
11226           case MachinePlaysWhite:
11227             if (whiteFlag) {
11228                 if (blackFlag)
11229                   GameEnds(GameIsDrawn, "Both players ran out of time",
11230                            GE_PLAYER);
11231                 else
11232                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11233             } else {
11234                 DisplayError(_("Your opponent is not out of time"), 0);
11235             }
11236             break;
11237           case MachinePlaysBlack:
11238             if (blackFlag) {
11239                 if (whiteFlag)
11240                   GameEnds(GameIsDrawn, "Both players ran out of time",
11241                            GE_PLAYER);
11242                 else
11243                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11244             } else {
11245                 DisplayError(_("Your opponent is not out of time"), 0);
11246             }
11247             break;
11248         }
11249     }
11250 }
11251
11252 void
11253 DrawEvent()
11254 {
11255     /* Offer draw or accept pending draw offer from opponent */
11256     
11257     if (appData.icsActive) {
11258         /* Note: tournament rules require draw offers to be
11259            made after you make your move but before you punch
11260            your clock.  Currently ICS doesn't let you do that;
11261            instead, you immediately punch your clock after making
11262            a move, but you can offer a draw at any time. */
11263         
11264         SendToICS(ics_prefix);
11265         SendToICS("draw\n");
11266     } else if (cmailMsgLoaded) {
11267         if (currentMove == cmailOldMove &&
11268             commentList[cmailOldMove] != NULL &&
11269             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11270                    "Black offers a draw" : "White offers a draw")) {
11271             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11272             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11273         } else if (currentMove == cmailOldMove + 1) {
11274             char *offer = WhiteOnMove(cmailOldMove) ?
11275               "White offers a draw" : "Black offers a draw";
11276             AppendComment(currentMove, offer);
11277             DisplayComment(currentMove - 1, offer);
11278             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11279         } else {
11280             DisplayError(_("You must make your move before offering a draw"), 0);
11281             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11282         }
11283     } else if (first.offeredDraw) {
11284         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11285     } else {
11286         if (first.sendDrawOffers) {
11287             SendToProgram("draw\n", &first);
11288             userOfferedDraw = TRUE;
11289         }
11290     }
11291 }
11292
11293 void
11294 AdjournEvent()
11295 {
11296     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11297     
11298     if (appData.icsActive) {
11299         SendToICS(ics_prefix);
11300         SendToICS("adjourn\n");
11301     } else {
11302         /* Currently GNU Chess doesn't offer or accept Adjourns */
11303     }
11304 }
11305
11306
11307 void
11308 AbortEvent()
11309 {
11310     /* Offer Abort or accept pending Abort offer from opponent */
11311     
11312     if (appData.icsActive) {
11313         SendToICS(ics_prefix);
11314         SendToICS("abort\n");
11315     } else {
11316         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11317     }
11318 }
11319
11320 void
11321 ResignEvent()
11322 {
11323     /* Resign.  You can do this even if it's not your turn. */
11324     
11325     if (appData.icsActive) {
11326         SendToICS(ics_prefix);
11327         SendToICS("resign\n");
11328     } else {
11329         switch (gameMode) {
11330           case MachinePlaysWhite:
11331             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11332             break;
11333           case MachinePlaysBlack:
11334             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11335             break;
11336           case EditGame:
11337             if (cmailMsgLoaded) {
11338                 TruncateGame();
11339                 if (WhiteOnMove(cmailOldMove)) {
11340                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11341                 } else {
11342                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11343                 }
11344                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11345             }
11346             break;
11347           default:
11348             break;
11349         }
11350     }
11351 }
11352
11353
11354 void
11355 StopObservingEvent()
11356 {
11357     /* Stop observing current games */
11358     SendToICS(ics_prefix);
11359     SendToICS("unobserve\n");
11360 }
11361
11362 void
11363 StopExaminingEvent()
11364 {
11365     /* Stop observing current game */
11366     SendToICS(ics_prefix);
11367     SendToICS("unexamine\n");
11368 }
11369
11370 void
11371 ForwardInner(target)
11372      int target;
11373 {
11374     int limit;
11375
11376     if (appData.debugMode)
11377         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11378                 target, currentMove, forwardMostMove);
11379
11380     if (gameMode == EditPosition)
11381       return;
11382
11383     if (gameMode == PlayFromGameFile && !pausing)
11384       PauseEvent();
11385     
11386     if (gameMode == IcsExamining && pausing)
11387       limit = pauseExamForwardMostMove;
11388     else
11389       limit = forwardMostMove;
11390     
11391     if (target > limit) target = limit;
11392
11393     if (target > 0 && moveList[target - 1][0]) {
11394         int fromX, fromY, toX, toY;
11395         toX = moveList[target - 1][2] - AAA;
11396         toY = moveList[target - 1][3] - ONE;
11397         if (moveList[target - 1][1] == '@') {
11398             if (appData.highlightLastMove) {
11399                 SetHighlights(-1, -1, toX, toY);
11400             }
11401         } else {
11402             fromX = moveList[target - 1][0] - AAA;
11403             fromY = moveList[target - 1][1] - ONE;
11404             if (target == currentMove + 1) {
11405                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11406             }
11407             if (appData.highlightLastMove) {
11408                 SetHighlights(fromX, fromY, toX, toY);
11409             }
11410         }
11411     }
11412     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11413         gameMode == Training || gameMode == PlayFromGameFile || 
11414         gameMode == AnalyzeFile) {
11415         while (currentMove < target) {
11416             SendMoveToProgram(currentMove++, &first);
11417         }
11418     } else {
11419         currentMove = target;
11420     }
11421     
11422     if (gameMode == EditGame || gameMode == EndOfGame) {
11423         whiteTimeRemaining = timeRemaining[0][currentMove];
11424         blackTimeRemaining = timeRemaining[1][currentMove];
11425     }
11426     DisplayBothClocks();
11427     DisplayMove(currentMove - 1);
11428     DrawPosition(FALSE, boards[currentMove]);
11429     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11430     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11431         DisplayComment(currentMove - 1, commentList[currentMove]);
11432     }
11433 }
11434
11435
11436 void
11437 ForwardEvent()
11438 {
11439     if (gameMode == IcsExamining && !pausing) {
11440         SendToICS(ics_prefix);
11441         SendToICS("forward\n");
11442     } else {
11443         ForwardInner(currentMove + 1);
11444     }
11445 }
11446
11447 void
11448 ToEndEvent()
11449 {
11450     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11451         /* to optimze, we temporarily turn off analysis mode while we feed
11452          * the remaining moves to the engine. Otherwise we get analysis output
11453          * after each move.
11454          */ 
11455         if (first.analysisSupport) {
11456           SendToProgram("exit\nforce\n", &first);
11457           first.analyzing = FALSE;
11458         }
11459     }
11460         
11461     if (gameMode == IcsExamining && !pausing) {
11462         SendToICS(ics_prefix);
11463         SendToICS("forward 999999\n");
11464     } else {
11465         ForwardInner(forwardMostMove);
11466     }
11467
11468     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11469         /* we have fed all the moves, so reactivate analysis mode */
11470         SendToProgram("analyze\n", &first);
11471         first.analyzing = TRUE;
11472         /*first.maybeThinking = TRUE;*/
11473         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11474     }
11475 }
11476
11477 void
11478 BackwardInner(target)
11479      int target;
11480 {
11481     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11482
11483     if (appData.debugMode)
11484         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11485                 target, currentMove, forwardMostMove);
11486
11487     if (gameMode == EditPosition) return;
11488     if (currentMove <= backwardMostMove) {
11489         ClearHighlights();
11490         DrawPosition(full_redraw, boards[currentMove]);
11491         return;
11492     }
11493     if (gameMode == PlayFromGameFile && !pausing)
11494       PauseEvent();
11495     
11496     if (moveList[target][0]) {
11497         int fromX, fromY, toX, toY;
11498         toX = moveList[target][2] - AAA;
11499         toY = moveList[target][3] - ONE;
11500         if (moveList[target][1] == '@') {
11501             if (appData.highlightLastMove) {
11502                 SetHighlights(-1, -1, toX, toY);
11503             }
11504         } else {
11505             fromX = moveList[target][0] - AAA;
11506             fromY = moveList[target][1] - ONE;
11507             if (target == currentMove - 1) {
11508                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11509             }
11510             if (appData.highlightLastMove) {
11511                 SetHighlights(fromX, fromY, toX, toY);
11512             }
11513         }
11514     }
11515     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11516         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11517         while (currentMove > target) {
11518             SendToProgram("undo\n", &first);
11519             currentMove--;
11520         }
11521     } else {
11522         currentMove = target;
11523     }
11524     
11525     if (gameMode == EditGame || gameMode == EndOfGame) {
11526         whiteTimeRemaining = timeRemaining[0][currentMove];
11527         blackTimeRemaining = timeRemaining[1][currentMove];
11528     }
11529     DisplayBothClocks();
11530     DisplayMove(currentMove - 1);
11531     DrawPosition(full_redraw, boards[currentMove]);
11532     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11533     // [HGM] PV info: routine tests if comment empty
11534     DisplayComment(currentMove - 1, commentList[currentMove]);
11535 }
11536
11537 void
11538 BackwardEvent()
11539 {
11540     if (gameMode == IcsExamining && !pausing) {
11541         SendToICS(ics_prefix);
11542         SendToICS("backward\n");
11543     } else {
11544         BackwardInner(currentMove - 1);
11545     }
11546 }
11547
11548 void
11549 ToStartEvent()
11550 {
11551     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11552         /* to optimze, we temporarily turn off analysis mode while we undo
11553          * all the moves. Otherwise we get analysis output after each undo.
11554          */ 
11555         if (first.analysisSupport) {
11556           SendToProgram("exit\nforce\n", &first);
11557           first.analyzing = FALSE;
11558         }
11559     }
11560
11561     if (gameMode == IcsExamining && !pausing) {
11562         SendToICS(ics_prefix);
11563         SendToICS("backward 999999\n");
11564     } else {
11565         BackwardInner(backwardMostMove);
11566     }
11567
11568     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11569         /* we have fed all the moves, so reactivate analysis mode */
11570         SendToProgram("analyze\n", &first);
11571         first.analyzing = TRUE;
11572         /*first.maybeThinking = TRUE;*/
11573         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11574     }
11575 }
11576
11577 void
11578 ToNrEvent(int to)
11579 {
11580   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11581   if (to >= forwardMostMove) to = forwardMostMove;
11582   if (to <= backwardMostMove) to = backwardMostMove;
11583   if (to < currentMove) {
11584     BackwardInner(to);
11585   } else {
11586     ForwardInner(to);
11587   }
11588 }
11589
11590 void
11591 RevertEvent()
11592 {
11593     if (gameMode != IcsExamining) {
11594         DisplayError(_("You are not examining a game"), 0);
11595         return;
11596     }
11597     if (pausing) {
11598         DisplayError(_("You can't revert while pausing"), 0);
11599         return;
11600     }
11601     SendToICS(ics_prefix);
11602     SendToICS("revert\n");
11603 }
11604
11605 void
11606 RetractMoveEvent()
11607 {
11608     switch (gameMode) {
11609       case MachinePlaysWhite:
11610       case MachinePlaysBlack:
11611         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11612             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11613             return;
11614         }
11615         if (forwardMostMove < 2) return;
11616         currentMove = forwardMostMove = forwardMostMove - 2;
11617         whiteTimeRemaining = timeRemaining[0][currentMove];
11618         blackTimeRemaining = timeRemaining[1][currentMove];
11619         DisplayBothClocks();
11620         DisplayMove(currentMove - 1);
11621         ClearHighlights();/*!! could figure this out*/
11622         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11623         SendToProgram("remove\n", &first);
11624         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11625         break;
11626
11627       case BeginningOfGame:
11628       default:
11629         break;
11630
11631       case IcsPlayingWhite:
11632       case IcsPlayingBlack:
11633         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11634             SendToICS(ics_prefix);
11635             SendToICS("takeback 2\n");
11636         } else {
11637             SendToICS(ics_prefix);
11638             SendToICS("takeback 1\n");
11639         }
11640         break;
11641     }
11642 }
11643
11644 void
11645 MoveNowEvent()
11646 {
11647     ChessProgramState *cps;
11648
11649     switch (gameMode) {
11650       case MachinePlaysWhite:
11651         if (!WhiteOnMove(forwardMostMove)) {
11652             DisplayError(_("It is your turn"), 0);
11653             return;
11654         }
11655         cps = &first;
11656         break;
11657       case MachinePlaysBlack:
11658         if (WhiteOnMove(forwardMostMove)) {
11659             DisplayError(_("It is your turn"), 0);
11660             return;
11661         }
11662         cps = &first;
11663         break;
11664       case TwoMachinesPlay:
11665         if (WhiteOnMove(forwardMostMove) ==
11666             (first.twoMachinesColor[0] == 'w')) {
11667             cps = &first;
11668         } else {
11669             cps = &second;
11670         }
11671         break;
11672       case BeginningOfGame:
11673       default:
11674         return;
11675     }
11676     SendToProgram("?\n", cps);
11677 }
11678
11679 void
11680 TruncateGameEvent()
11681 {
11682     EditGameEvent();
11683     if (gameMode != EditGame) return;
11684     TruncateGame();
11685 }
11686
11687 void
11688 TruncateGame()
11689 {
11690     if (forwardMostMove > currentMove) {
11691         if (gameInfo.resultDetails != NULL) {
11692             free(gameInfo.resultDetails);
11693             gameInfo.resultDetails = NULL;
11694             gameInfo.result = GameUnfinished;
11695         }
11696         forwardMostMove = currentMove;
11697         HistorySet(parseList, backwardMostMove, forwardMostMove,
11698                    currentMove-1);
11699     }
11700 }
11701
11702 void
11703 HintEvent()
11704 {
11705     if (appData.noChessProgram) return;
11706     switch (gameMode) {
11707       case MachinePlaysWhite:
11708         if (WhiteOnMove(forwardMostMove)) {
11709             DisplayError(_("Wait until your turn"), 0);
11710             return;
11711         }
11712         break;
11713       case BeginningOfGame:
11714       case MachinePlaysBlack:
11715         if (!WhiteOnMove(forwardMostMove)) {
11716             DisplayError(_("Wait until your turn"), 0);
11717             return;
11718         }
11719         break;
11720       default:
11721         DisplayError(_("No hint available"), 0);
11722         return;
11723     }
11724     SendToProgram("hint\n", &first);
11725     hintRequested = TRUE;
11726 }
11727
11728 void
11729 BookEvent()
11730 {
11731     if (appData.noChessProgram) return;
11732     switch (gameMode) {
11733       case MachinePlaysWhite:
11734         if (WhiteOnMove(forwardMostMove)) {
11735             DisplayError(_("Wait until your turn"), 0);
11736             return;
11737         }
11738         break;
11739       case BeginningOfGame:
11740       case MachinePlaysBlack:
11741         if (!WhiteOnMove(forwardMostMove)) {
11742             DisplayError(_("Wait until your turn"), 0);
11743             return;
11744         }
11745         break;
11746       case EditPosition:
11747         EditPositionDone();
11748         break;
11749       case TwoMachinesPlay:
11750         return;
11751       default:
11752         break;
11753     }
11754     SendToProgram("bk\n", &first);
11755     bookOutput[0] = NULLCHAR;
11756     bookRequested = TRUE;
11757 }
11758
11759 void
11760 AboutGameEvent()
11761 {
11762     char *tags = PGNTags(&gameInfo);
11763     TagsPopUp(tags, CmailMsg());
11764     free(tags);
11765 }
11766
11767 /* end button procedures */
11768
11769 void
11770 PrintPosition(fp, move)
11771      FILE *fp;
11772      int move;
11773 {
11774     int i, j;
11775     
11776     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11777         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11778             char c = PieceToChar(boards[move][i][j]);
11779             fputc(c == 'x' ? '.' : c, fp);
11780             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11781         }
11782     }
11783     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11784       fprintf(fp, "white to play\n");
11785     else
11786       fprintf(fp, "black to play\n");
11787 }
11788
11789 void
11790 PrintOpponents(fp)
11791      FILE *fp;
11792 {
11793     if (gameInfo.white != NULL) {
11794         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11795     } else {
11796         fprintf(fp, "\n");
11797     }
11798 }
11799
11800 /* Find last component of program's own name, using some heuristics */
11801 void
11802 TidyProgramName(prog, host, buf)
11803      char *prog, *host, buf[MSG_SIZ];
11804 {
11805     char *p, *q;
11806     int local = (strcmp(host, "localhost") == 0);
11807     while (!local && (p = strchr(prog, ';')) != NULL) {
11808         p++;
11809         while (*p == ' ') p++;
11810         prog = p;
11811     }
11812     if (*prog == '"' || *prog == '\'') {
11813         q = strchr(prog + 1, *prog);
11814     } else {
11815         q = strchr(prog, ' ');
11816     }
11817     if (q == NULL) q = prog + strlen(prog);
11818     p = q;
11819     while (p >= prog && *p != '/' && *p != '\\') p--;
11820     p++;
11821     if(p == prog && *p == '"') p++;
11822     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11823     memcpy(buf, p, q - p);
11824     buf[q - p] = NULLCHAR;
11825     if (!local) {
11826         strcat(buf, "@");
11827         strcat(buf, host);
11828     }
11829 }
11830
11831 char *
11832 TimeControlTagValue()
11833 {
11834     char buf[MSG_SIZ];
11835     if (!appData.clockMode) {
11836         strcpy(buf, "-");
11837     } else if (movesPerSession > 0) {
11838         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11839     } else if (timeIncrement == 0) {
11840         sprintf(buf, "%ld", timeControl/1000);
11841     } else {
11842         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11843     }
11844     return StrSave(buf);
11845 }
11846
11847 void
11848 SetGameInfo()
11849 {
11850     /* This routine is used only for certain modes */
11851     VariantClass v = gameInfo.variant;
11852     ClearGameInfo(&gameInfo);
11853     gameInfo.variant = v;
11854
11855     switch (gameMode) {
11856       case MachinePlaysWhite:
11857         gameInfo.event = StrSave( appData.pgnEventHeader );
11858         gameInfo.site = StrSave(HostName());
11859         gameInfo.date = PGNDate();
11860         gameInfo.round = StrSave("-");
11861         gameInfo.white = StrSave(first.tidy);
11862         gameInfo.black = StrSave(UserName());
11863         gameInfo.timeControl = TimeControlTagValue();
11864         break;
11865
11866       case MachinePlaysBlack:
11867         gameInfo.event = StrSave( appData.pgnEventHeader );
11868         gameInfo.site = StrSave(HostName());
11869         gameInfo.date = PGNDate();
11870         gameInfo.round = StrSave("-");
11871         gameInfo.white = StrSave(UserName());
11872         gameInfo.black = StrSave(first.tidy);
11873         gameInfo.timeControl = TimeControlTagValue();
11874         break;
11875
11876       case TwoMachinesPlay:
11877         gameInfo.event = StrSave( appData.pgnEventHeader );
11878         gameInfo.site = StrSave(HostName());
11879         gameInfo.date = PGNDate();
11880         if (matchGame > 0) {
11881             char buf[MSG_SIZ];
11882             sprintf(buf, "%d", matchGame);
11883             gameInfo.round = StrSave(buf);
11884         } else {
11885             gameInfo.round = StrSave("-");
11886         }
11887         if (first.twoMachinesColor[0] == 'w') {
11888             gameInfo.white = StrSave(first.tidy);
11889             gameInfo.black = StrSave(second.tidy);
11890         } else {
11891             gameInfo.white = StrSave(second.tidy);
11892             gameInfo.black = StrSave(first.tidy);
11893         }
11894         gameInfo.timeControl = TimeControlTagValue();
11895         break;
11896
11897       case EditGame:
11898         gameInfo.event = StrSave("Edited game");
11899         gameInfo.site = StrSave(HostName());
11900         gameInfo.date = PGNDate();
11901         gameInfo.round = StrSave("-");
11902         gameInfo.white = StrSave("-");
11903         gameInfo.black = StrSave("-");
11904         break;
11905
11906       case EditPosition:
11907         gameInfo.event = StrSave("Edited position");
11908         gameInfo.site = StrSave(HostName());
11909         gameInfo.date = PGNDate();
11910         gameInfo.round = StrSave("-");
11911         gameInfo.white = StrSave("-");
11912         gameInfo.black = StrSave("-");
11913         break;
11914
11915       case IcsPlayingWhite:
11916       case IcsPlayingBlack:
11917       case IcsObserving:
11918       case IcsExamining:
11919         break;
11920
11921       case PlayFromGameFile:
11922         gameInfo.event = StrSave("Game from non-PGN file");
11923         gameInfo.site = StrSave(HostName());
11924         gameInfo.date = PGNDate();
11925         gameInfo.round = StrSave("-");
11926         gameInfo.white = StrSave("?");
11927         gameInfo.black = StrSave("?");
11928         break;
11929
11930       default:
11931         break;
11932     }
11933 }
11934
11935 void
11936 ReplaceComment(index, text)
11937      int index;
11938      char *text;
11939 {
11940     int len;
11941
11942     while (*text == '\n') text++;
11943     len = strlen(text);
11944     while (len > 0 && text[len - 1] == '\n') len--;
11945
11946     if (commentList[index] != NULL)
11947       free(commentList[index]);
11948
11949     if (len == 0) {
11950         commentList[index] = NULL;
11951         return;
11952     }
11953     commentList[index] = (char *) malloc(len + 2);
11954     strncpy(commentList[index], text, len);
11955     commentList[index][len] = '\n';
11956     commentList[index][len + 1] = NULLCHAR;
11957 }
11958
11959 void
11960 CrushCRs(text)
11961      char *text;
11962 {
11963   char *p = text;
11964   char *q = text;
11965   char ch;
11966
11967   do {
11968     ch = *p++;
11969     if (ch == '\r') continue;
11970     *q++ = ch;
11971   } while (ch != '\0');
11972 }
11973
11974 void
11975 AppendComment(index, text)
11976      int index;
11977      char *text;
11978 {
11979     int oldlen, len;
11980     char *old;
11981
11982     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11983
11984     CrushCRs(text);
11985     while (*text == '\n') text++;
11986     len = strlen(text);
11987     while (len > 0 && text[len - 1] == '\n') len--;
11988
11989     if (len == 0) return;
11990
11991     if (commentList[index] != NULL) {
11992         old = commentList[index];
11993         oldlen = strlen(old);
11994         commentList[index] = (char *) malloc(oldlen + len + 2);
11995         strcpy(commentList[index], old);
11996         free(old);
11997         strncpy(&commentList[index][oldlen], text, len);
11998         commentList[index][oldlen + len] = '\n';
11999         commentList[index][oldlen + len + 1] = NULLCHAR;
12000     } else {
12001         commentList[index] = (char *) malloc(len + 2);
12002         strncpy(commentList[index], text, len);
12003         commentList[index][len] = '\n';
12004         commentList[index][len + 1] = NULLCHAR;
12005     }
12006 }
12007
12008 static char * FindStr( char * text, char * sub_text )
12009 {
12010     char * result = strstr( text, sub_text );
12011
12012     if( result != NULL ) {
12013         result += strlen( sub_text );
12014     }
12015
12016     return result;
12017 }
12018
12019 /* [AS] Try to extract PV info from PGN comment */
12020 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12021 char *GetInfoFromComment( int index, char * text )
12022 {
12023     char * sep = text;
12024
12025     if( text != NULL && index > 0 ) {
12026         int score = 0;
12027         int depth = 0;
12028         int time = -1, sec = 0, deci;
12029         char * s_eval = FindStr( text, "[%eval " );
12030         char * s_emt = FindStr( text, "[%emt " );
12031
12032         if( s_eval != NULL || s_emt != NULL ) {
12033             /* New style */
12034             char delim;
12035
12036             if( s_eval != NULL ) {
12037                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12038                     return text;
12039                 }
12040
12041                 if( delim != ']' ) {
12042                     return text;
12043                 }
12044             }
12045
12046             if( s_emt != NULL ) {
12047             }
12048         }
12049         else {
12050             /* We expect something like: [+|-]nnn.nn/dd */
12051             int score_lo = 0;
12052
12053             sep = strchr( text, '/' );
12054             if( sep == NULL || sep < (text+4) ) {
12055                 return text;
12056             }
12057
12058             time = -1; sec = -1; deci = -1;
12059             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12060                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12061                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12062                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12063                 return text;
12064             }
12065
12066             if( score_lo < 0 || score_lo >= 100 ) {
12067                 return text;
12068             }
12069
12070             if(sec >= 0) time = 600*time + 10*sec; else
12071             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12072
12073             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12074
12075             /* [HGM] PV time: now locate end of PV info */
12076             while( *++sep >= '0' && *sep <= '9'); // strip depth
12077             if(time >= 0)
12078             while( *++sep >= '0' && *sep <= '9'); // strip time
12079             if(sec >= 0)
12080             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12081             if(deci >= 0)
12082             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12083             while(*sep == ' ') sep++;
12084         }
12085
12086         if( depth <= 0 ) {
12087             return text;
12088         }
12089
12090         if( time < 0 ) {
12091             time = -1;
12092         }
12093
12094         pvInfoList[index-1].depth = depth;
12095         pvInfoList[index-1].score = score;
12096         pvInfoList[index-1].time  = 10*time; // centi-sec
12097     }
12098     return sep;
12099 }
12100
12101 void
12102 SendToProgram(message, cps)
12103      char *message;
12104      ChessProgramState *cps;
12105 {
12106     int count, outCount, error;
12107     char buf[MSG_SIZ];
12108
12109     if (cps->pr == NULL) return;
12110     Attention(cps);
12111     
12112     if (appData.debugMode) {
12113         TimeMark now;
12114         GetTimeMark(&now);
12115         fprintf(debugFP, "%ld >%-6s: %s", 
12116                 SubtractTimeMarks(&now, &programStartTime),
12117                 cps->which, message);
12118     }
12119     
12120     count = strlen(message);
12121     outCount = OutputToProcess(cps->pr, message, count, &error);
12122     if (outCount < count && !exiting 
12123                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12124         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12125         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12126             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12127                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12128                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12129             } else {
12130                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12131             }
12132             gameInfo.resultDetails = buf;
12133         }
12134         DisplayFatalError(buf, error, 1);
12135     }
12136 }
12137
12138 void
12139 ReceiveFromProgram(isr, closure, message, count, error)
12140      InputSourceRef isr;
12141      VOIDSTAR closure;
12142      char *message;
12143      int count;
12144      int error;
12145 {
12146     char *end_str;
12147     char buf[MSG_SIZ];
12148     ChessProgramState *cps = (ChessProgramState *)closure;
12149
12150     if (isr != cps->isr) return; /* Killed intentionally */
12151     if (count <= 0) {
12152         if (count == 0) {
12153             sprintf(buf,
12154                     _("Error: %s chess program (%s) exited unexpectedly"),
12155                     cps->which, cps->program);
12156         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12157                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12158                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12159                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12160                 } else {
12161                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12162                 }
12163                 gameInfo.resultDetails = buf;
12164             }
12165             RemoveInputSource(cps->isr);
12166             DisplayFatalError(buf, 0, 1);
12167         } else {
12168             sprintf(buf,
12169                     _("Error reading from %s chess program (%s)"),
12170                     cps->which, cps->program);
12171             RemoveInputSource(cps->isr);
12172
12173             /* [AS] Program is misbehaving badly... kill it */
12174             if( count == -2 ) {
12175                 DestroyChildProcess( cps->pr, 9 );
12176                 cps->pr = NoProc;
12177             }
12178
12179             DisplayFatalError(buf, error, 1);
12180         }
12181         return;
12182     }
12183     
12184     if ((end_str = strchr(message, '\r')) != NULL)
12185       *end_str = NULLCHAR;
12186     if ((end_str = strchr(message, '\n')) != NULL)
12187       *end_str = NULLCHAR;
12188     
12189     if (appData.debugMode) {
12190         TimeMark now; int print = 1;
12191         char *quote = ""; char c; int i;
12192
12193         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12194                 char start = message[0];
12195                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12196                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12197                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12198                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12199                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12200                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12201                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12202                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12203                         { quote = "# "; print = (appData.engineComments == 2); }
12204                 message[0] = start; // restore original message
12205         }
12206         if(print) {
12207                 GetTimeMark(&now);
12208                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12209                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12210                         quote,
12211                         message);
12212         }
12213     }
12214
12215     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12216     if (appData.icsEngineAnalyze) {
12217         if (strstr(message, "whisper") != NULL ||
12218              strstr(message, "kibitz") != NULL || 
12219             strstr(message, "tellics") != NULL) return;
12220     }
12221
12222     HandleMachineMove(message, cps);
12223 }
12224
12225
12226 void
12227 SendTimeControl(cps, mps, tc, inc, sd, st)
12228      ChessProgramState *cps;
12229      int mps, inc, sd, st;
12230      long tc;
12231 {
12232     char buf[MSG_SIZ];
12233     int seconds;
12234
12235     if( timeControl_2 > 0 ) {
12236         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12237             tc = timeControl_2;
12238         }
12239     }
12240     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12241     inc /= cps->timeOdds;
12242     st  /= cps->timeOdds;
12243
12244     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12245
12246     if (st > 0) {
12247       /* Set exact time per move, normally using st command */
12248       if (cps->stKludge) {
12249         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12250         seconds = st % 60;
12251         if (seconds == 0) {
12252           sprintf(buf, "level 1 %d\n", st/60);
12253         } else {
12254           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12255         }
12256       } else {
12257         sprintf(buf, "st %d\n", st);
12258       }
12259     } else {
12260       /* Set conventional or incremental time control, using level command */
12261       if (seconds == 0) {
12262         /* Note old gnuchess bug -- minutes:seconds used to not work.
12263            Fixed in later versions, but still avoid :seconds
12264            when seconds is 0. */
12265         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12266       } else {
12267         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12268                 seconds, inc/1000);
12269       }
12270     }
12271     SendToProgram(buf, cps);
12272
12273     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12274     /* Orthogonally, limit search to given depth */
12275     if (sd > 0) {
12276       if (cps->sdKludge) {
12277         sprintf(buf, "depth\n%d\n", sd);
12278       } else {
12279         sprintf(buf, "sd %d\n", sd);
12280       }
12281       SendToProgram(buf, cps);
12282     }
12283
12284     if(cps->nps > 0) { /* [HGM] nps */
12285         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12286         else {
12287                 sprintf(buf, "nps %d\n", cps->nps);
12288               SendToProgram(buf, cps);
12289         }
12290     }
12291 }
12292
12293 ChessProgramState *WhitePlayer()
12294 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12295 {
12296     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12297        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12298         return &second;
12299     return &first;
12300 }
12301
12302 void
12303 SendTimeRemaining(cps, machineWhite)
12304      ChessProgramState *cps;
12305      int /*boolean*/ machineWhite;
12306 {
12307     char message[MSG_SIZ];
12308     long time, otime;
12309
12310     /* Note: this routine must be called when the clocks are stopped
12311        or when they have *just* been set or switched; otherwise
12312        it will be off by the time since the current tick started.
12313     */
12314     if (machineWhite) {
12315         time = whiteTimeRemaining / 10;
12316         otime = blackTimeRemaining / 10;
12317     } else {
12318         time = blackTimeRemaining / 10;
12319         otime = whiteTimeRemaining / 10;
12320     }
12321     /* [HGM] translate opponent's time by time-odds factor */
12322     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12323     if (appData.debugMode) {
12324         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12325     }
12326
12327     if (time <= 0) time = 1;
12328     if (otime <= 0) otime = 1;
12329     
12330     sprintf(message, "time %ld\n", time);
12331     SendToProgram(message, cps);
12332
12333     sprintf(message, "otim %ld\n", otime);
12334     SendToProgram(message, cps);
12335 }
12336
12337 int
12338 BoolFeature(p, name, loc, cps)
12339      char **p;
12340      char *name;
12341      int *loc;
12342      ChessProgramState *cps;
12343 {
12344   char buf[MSG_SIZ];
12345   int len = strlen(name);
12346   int val;
12347   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12348     (*p) += len + 1;
12349     sscanf(*p, "%d", &val);
12350     *loc = (val != 0);
12351     while (**p && **p != ' ') (*p)++;
12352     sprintf(buf, "accepted %s\n", name);
12353     SendToProgram(buf, cps);
12354     return TRUE;
12355   }
12356   return FALSE;
12357 }
12358
12359 int
12360 IntFeature(p, name, loc, cps)
12361      char **p;
12362      char *name;
12363      int *loc;
12364      ChessProgramState *cps;
12365 {
12366   char buf[MSG_SIZ];
12367   int len = strlen(name);
12368   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12369     (*p) += len + 1;
12370     sscanf(*p, "%d", loc);
12371     while (**p && **p != ' ') (*p)++;
12372     sprintf(buf, "accepted %s\n", name);
12373     SendToProgram(buf, cps);
12374     return TRUE;
12375   }
12376   return FALSE;
12377 }
12378
12379 int
12380 StringFeature(p, name, loc, cps)
12381      char **p;
12382      char *name;
12383      char loc[];
12384      ChessProgramState *cps;
12385 {
12386   char buf[MSG_SIZ];
12387   int len = strlen(name);
12388   if (strncmp((*p), name, len) == 0
12389       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12390     (*p) += len + 2;
12391     sscanf(*p, "%[^\"]", loc);
12392     while (**p && **p != '\"') (*p)++;
12393     if (**p == '\"') (*p)++;
12394     sprintf(buf, "accepted %s\n", name);
12395     SendToProgram(buf, cps);
12396     return TRUE;
12397   }
12398   return FALSE;
12399 }
12400
12401 int 
12402 ParseOption(Option *opt, ChessProgramState *cps)
12403 // [HGM] options: process the string that defines an engine option, and determine
12404 // name, type, default value, and allowed value range
12405 {
12406         char *p, *q, buf[MSG_SIZ];
12407         int n, min = (-1)<<31, max = 1<<31, def;
12408
12409         if(p = strstr(opt->name, " -spin ")) {
12410             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12411             if(max < min) max = min; // enforce consistency
12412             if(def < min) def = min;
12413             if(def > max) def = max;
12414             opt->value = def;
12415             opt->min = min;
12416             opt->max = max;
12417             opt->type = Spin;
12418         } else if((p = strstr(opt->name, " -slider "))) {
12419             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12420             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12421             if(max < min) max = min; // enforce consistency
12422             if(def < min) def = min;
12423             if(def > max) def = max;
12424             opt->value = def;
12425             opt->min = min;
12426             opt->max = max;
12427             opt->type = Spin; // Slider;
12428         } else if((p = strstr(opt->name, " -string "))) {
12429             opt->textValue = p+9;
12430             opt->type = TextBox;
12431         } else if((p = strstr(opt->name, " -file "))) {
12432             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12433             opt->textValue = p+7;
12434             opt->type = TextBox; // FileName;
12435         } else if((p = strstr(opt->name, " -path "))) {
12436             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12437             opt->textValue = p+7;
12438             opt->type = TextBox; // PathName;
12439         } else if(p = strstr(opt->name, " -check ")) {
12440             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12441             opt->value = (def != 0);
12442             opt->type = CheckBox;
12443         } else if(p = strstr(opt->name, " -combo ")) {
12444             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12445             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12446             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12447             opt->value = n = 0;
12448             while(q = StrStr(q, " /// ")) {
12449                 n++; *q = 0;    // count choices, and null-terminate each of them
12450                 q += 5;
12451                 if(*q == '*') { // remember default, which is marked with * prefix
12452                     q++;
12453                     opt->value = n;
12454                 }
12455                 cps->comboList[cps->comboCnt++] = q;
12456             }
12457             cps->comboList[cps->comboCnt++] = NULL;
12458             opt->max = n + 1;
12459             opt->type = ComboBox;
12460         } else if(p = strstr(opt->name, " -button")) {
12461             opt->type = Button;
12462         } else if(p = strstr(opt->name, " -save")) {
12463             opt->type = SaveButton;
12464         } else return FALSE;
12465         *p = 0; // terminate option name
12466         // now look if the command-line options define a setting for this engine option.
12467         if(cps->optionSettings && cps->optionSettings[0])
12468             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12469         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12470                 sprintf(buf, "option %s", p);
12471                 if(p = strstr(buf, ",")) *p = 0;
12472                 strcat(buf, "\n");
12473                 SendToProgram(buf, cps);
12474         }
12475         return TRUE;
12476 }
12477
12478 void
12479 FeatureDone(cps, val)
12480      ChessProgramState* cps;
12481      int val;
12482 {
12483   DelayedEventCallback cb = GetDelayedEvent();
12484   if ((cb == InitBackEnd3 && cps == &first) ||
12485       (cb == TwoMachinesEventIfReady && cps == &second)) {
12486     CancelDelayedEvent();
12487     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12488   }
12489   cps->initDone = val;
12490 }
12491
12492 /* Parse feature command from engine */
12493 void
12494 ParseFeatures(args, cps)
12495      char* args;
12496      ChessProgramState *cps;  
12497 {
12498   char *p = args;
12499   char *q;
12500   int val;
12501   char buf[MSG_SIZ];
12502
12503   for (;;) {
12504     while (*p == ' ') p++;
12505     if (*p == NULLCHAR) return;
12506
12507     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12508     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12509     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12510     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12511     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12512     if (BoolFeature(&p, "reuse", &val, cps)) {
12513       /* Engine can disable reuse, but can't enable it if user said no */
12514       if (!val) cps->reuse = FALSE;
12515       continue;
12516     }
12517     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12518     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12519       if (gameMode == TwoMachinesPlay) {
12520         DisplayTwoMachinesTitle();
12521       } else {
12522         DisplayTitle("");
12523       }
12524       continue;
12525     }
12526     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12527     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12528     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12529     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12530     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12531     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12532     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12533     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12534     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12535     if (IntFeature(&p, "done", &val, cps)) {
12536       FeatureDone(cps, val);
12537       continue;
12538     }
12539     /* Added by Tord: */
12540     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12541     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12542     /* End of additions by Tord */
12543
12544     /* [HGM] added features: */
12545     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12546     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12547     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12548     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12549     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12550     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12551     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12552         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12553             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12554             SendToProgram(buf, cps);
12555             continue;
12556         }
12557         if(cps->nrOptions >= MAX_OPTIONS) {
12558             cps->nrOptions--;
12559             sprintf(buf, "%s engine has too many options\n", cps->which);
12560             DisplayError(buf, 0);
12561         }
12562         continue;
12563     }
12564     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12565     /* End of additions by HGM */
12566
12567     /* unknown feature: complain and skip */
12568     q = p;
12569     while (*q && *q != '=') q++;
12570     sprintf(buf, "rejected %.*s\n", q-p, p);
12571     SendToProgram(buf, cps);
12572     p = q;
12573     if (*p == '=') {
12574       p++;
12575       if (*p == '\"') {
12576         p++;
12577         while (*p && *p != '\"') p++;
12578         if (*p == '\"') p++;
12579       } else {
12580         while (*p && *p != ' ') p++;
12581       }
12582     }
12583   }
12584
12585 }
12586
12587 void
12588 PeriodicUpdatesEvent(newState)
12589      int newState;
12590 {
12591     if (newState == appData.periodicUpdates)
12592       return;
12593
12594     appData.periodicUpdates=newState;
12595
12596     /* Display type changes, so update it now */
12597 //    DisplayAnalysis();
12598
12599     /* Get the ball rolling again... */
12600     if (newState) {
12601         AnalysisPeriodicEvent(1);
12602         StartAnalysisClock();
12603     }
12604 }
12605
12606 void
12607 PonderNextMoveEvent(newState)
12608      int newState;
12609 {
12610     if (newState == appData.ponderNextMove) return;
12611     if (gameMode == EditPosition) EditPositionDone();
12612     if (newState) {
12613         SendToProgram("hard\n", &first);
12614         if (gameMode == TwoMachinesPlay) {
12615             SendToProgram("hard\n", &second);
12616         }
12617     } else {
12618         SendToProgram("easy\n", &first);
12619         thinkOutput[0] = NULLCHAR;
12620         if (gameMode == TwoMachinesPlay) {
12621             SendToProgram("easy\n", &second);
12622         }
12623     }
12624     appData.ponderNextMove = newState;
12625 }
12626
12627 void
12628 NewSettingEvent(option, command, value)
12629      char *command;
12630      int option, value;
12631 {
12632     char buf[MSG_SIZ];
12633
12634     if (gameMode == EditPosition) EditPositionDone();
12635     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12636     SendToProgram(buf, &first);
12637     if (gameMode == TwoMachinesPlay) {
12638         SendToProgram(buf, &second);
12639     }
12640 }
12641
12642 void
12643 ShowThinkingEvent()
12644 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12645 {
12646     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12647     int newState = appData.showThinking
12648         // [HGM] thinking: other features now need thinking output as well
12649         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12650     
12651     if (oldState == newState) return;
12652     oldState = newState;
12653     if (gameMode == EditPosition) EditPositionDone();
12654     if (oldState) {
12655         SendToProgram("post\n", &first);
12656         if (gameMode == TwoMachinesPlay) {
12657             SendToProgram("post\n", &second);
12658         }
12659     } else {
12660         SendToProgram("nopost\n", &first);
12661         thinkOutput[0] = NULLCHAR;
12662         if (gameMode == TwoMachinesPlay) {
12663             SendToProgram("nopost\n", &second);
12664         }
12665     }
12666 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12667 }
12668
12669 void
12670 AskQuestionEvent(title, question, replyPrefix, which)
12671      char *title; char *question; char *replyPrefix; char *which;
12672 {
12673   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12674   if (pr == NoProc) return;
12675   AskQuestion(title, question, replyPrefix, pr);
12676 }
12677
12678 void
12679 DisplayMove(moveNumber)
12680      int moveNumber;
12681 {
12682     char message[MSG_SIZ];
12683     char res[MSG_SIZ];
12684     char cpThinkOutput[MSG_SIZ];
12685
12686     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12687     
12688     if (moveNumber == forwardMostMove - 1 || 
12689         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12690
12691         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12692
12693         if (strchr(cpThinkOutput, '\n')) {
12694             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12695         }
12696     } else {
12697         *cpThinkOutput = NULLCHAR;
12698     }
12699
12700     /* [AS] Hide thinking from human user */
12701     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12702         *cpThinkOutput = NULLCHAR;
12703         if( thinkOutput[0] != NULLCHAR ) {
12704             int i;
12705
12706             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12707                 cpThinkOutput[i] = '.';
12708             }
12709             cpThinkOutput[i] = NULLCHAR;
12710             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12711         }
12712     }
12713
12714     if (moveNumber == forwardMostMove - 1 &&
12715         gameInfo.resultDetails != NULL) {
12716         if (gameInfo.resultDetails[0] == NULLCHAR) {
12717             sprintf(res, " %s", PGNResult(gameInfo.result));
12718         } else {
12719             sprintf(res, " {%s} %s",
12720                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12721         }
12722     } else {
12723         res[0] = NULLCHAR;
12724     }
12725
12726     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12727         DisplayMessage(res, cpThinkOutput);
12728     } else {
12729         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12730                 WhiteOnMove(moveNumber) ? " " : ".. ",
12731                 parseList[moveNumber], res);
12732         DisplayMessage(message, cpThinkOutput);
12733     }
12734 }
12735
12736 void
12737 DisplayComment(moveNumber, text)
12738      int moveNumber;
12739      char *text;
12740 {
12741     char title[MSG_SIZ];
12742     char buf[8000]; // comment can be long!
12743     int score, depth;
12744
12745     if( appData.autoDisplayComment ) {
12746         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12747             strcpy(title, "Comment");
12748         } else {
12749             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12750                     WhiteOnMove(moveNumber) ? " " : ".. ",
12751                     parseList[moveNumber]);
12752         }
12753         // [HGM] PV info: display PV info together with (or as) comment
12754         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12755             if(text == NULL) text = "";                                           
12756             score = pvInfoList[moveNumber].score;
12757             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12758                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12759             text = buf;
12760         }
12761     } else title[0] = 0;
12762
12763     if (text != NULL)
12764         CommentPopUp(title, text);
12765 }
12766
12767 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12768  * might be busy thinking or pondering.  It can be omitted if your
12769  * gnuchess is configured to stop thinking immediately on any user
12770  * input.  However, that gnuchess feature depends on the FIONREAD
12771  * ioctl, which does not work properly on some flavors of Unix.
12772  */
12773 void
12774 Attention(cps)
12775      ChessProgramState *cps;
12776 {
12777 #if ATTENTION
12778     if (!cps->useSigint) return;
12779     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12780     switch (gameMode) {
12781       case MachinePlaysWhite:
12782       case MachinePlaysBlack:
12783       case TwoMachinesPlay:
12784       case IcsPlayingWhite:
12785       case IcsPlayingBlack:
12786       case AnalyzeMode:
12787       case AnalyzeFile:
12788         /* Skip if we know it isn't thinking */
12789         if (!cps->maybeThinking) return;
12790         if (appData.debugMode)
12791           fprintf(debugFP, "Interrupting %s\n", cps->which);
12792         InterruptChildProcess(cps->pr);
12793         cps->maybeThinking = FALSE;
12794         break;
12795       default:
12796         break;
12797     }
12798 #endif /*ATTENTION*/
12799 }
12800
12801 int
12802 CheckFlags()
12803 {
12804     if (whiteTimeRemaining <= 0) {
12805         if (!whiteFlag) {
12806             whiteFlag = TRUE;
12807             if (appData.icsActive) {
12808                 if (appData.autoCallFlag &&
12809                     gameMode == IcsPlayingBlack && !blackFlag) {
12810                   SendToICS(ics_prefix);
12811                   SendToICS("flag\n");
12812                 }
12813             } else {
12814                 if (blackFlag) {
12815                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12816                 } else {
12817                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12818                     if (appData.autoCallFlag) {
12819                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12820                         return TRUE;
12821                     }
12822                 }
12823             }
12824         }
12825     }
12826     if (blackTimeRemaining <= 0) {
12827         if (!blackFlag) {
12828             blackFlag = TRUE;
12829             if (appData.icsActive) {
12830                 if (appData.autoCallFlag &&
12831                     gameMode == IcsPlayingWhite && !whiteFlag) {
12832                   SendToICS(ics_prefix);
12833                   SendToICS("flag\n");
12834                 }
12835             } else {
12836                 if (whiteFlag) {
12837                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12838                 } else {
12839                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12840                     if (appData.autoCallFlag) {
12841                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12842                         return TRUE;
12843                     }
12844                 }
12845             }
12846         }
12847     }
12848     return FALSE;
12849 }
12850
12851 void
12852 CheckTimeControl()
12853 {
12854     if (!appData.clockMode || appData.icsActive ||
12855         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12856
12857     /*
12858      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12859      */
12860     if ( !WhiteOnMove(forwardMostMove) )
12861         /* White made time control */
12862         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12863         /* [HGM] time odds: correct new time quota for time odds! */
12864                                             / WhitePlayer()->timeOdds;
12865       else
12866         /* Black made time control */
12867         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12868                                             / WhitePlayer()->other->timeOdds;
12869 }
12870
12871 void
12872 DisplayBothClocks()
12873 {
12874     int wom = gameMode == EditPosition ?
12875       !blackPlaysFirst : WhiteOnMove(currentMove);
12876     DisplayWhiteClock(whiteTimeRemaining, wom);
12877     DisplayBlackClock(blackTimeRemaining, !wom);
12878 }
12879
12880
12881 /* Timekeeping seems to be a portability nightmare.  I think everyone
12882    has ftime(), but I'm really not sure, so I'm including some ifdefs
12883    to use other calls if you don't.  Clocks will be less accurate if
12884    you have neither ftime nor gettimeofday.
12885 */
12886
12887 /* VS 2008 requires the #include outside of the function */
12888 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12889 #include <sys/timeb.h>
12890 #endif
12891
12892 /* Get the current time as a TimeMark */
12893 void
12894 GetTimeMark(tm)
12895      TimeMark *tm;
12896 {
12897 #if HAVE_GETTIMEOFDAY
12898
12899     struct timeval timeVal;
12900     struct timezone timeZone;
12901
12902     gettimeofday(&timeVal, &timeZone);
12903     tm->sec = (long) timeVal.tv_sec; 
12904     tm->ms = (int) (timeVal.tv_usec / 1000L);
12905
12906 #else /*!HAVE_GETTIMEOFDAY*/
12907 #if HAVE_FTIME
12908
12909 // include <sys/timeb.h> / moved to just above start of function
12910     struct timeb timeB;
12911
12912     ftime(&timeB);
12913     tm->sec = (long) timeB.time;
12914     tm->ms = (int) timeB.millitm;
12915
12916 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12917     tm->sec = (long) time(NULL);
12918     tm->ms = 0;
12919 #endif
12920 #endif
12921 }
12922
12923 /* Return the difference in milliseconds between two
12924    time marks.  We assume the difference will fit in a long!
12925 */
12926 long
12927 SubtractTimeMarks(tm2, tm1)
12928      TimeMark *tm2, *tm1;
12929 {
12930     return 1000L*(tm2->sec - tm1->sec) +
12931            (long) (tm2->ms - tm1->ms);
12932 }
12933
12934
12935 /*
12936  * Code to manage the game clocks.
12937  *
12938  * In tournament play, black starts the clock and then white makes a move.
12939  * We give the human user a slight advantage if he is playing white---the
12940  * clocks don't run until he makes his first move, so it takes zero time.
12941  * Also, we don't account for network lag, so we could get out of sync
12942  * with GNU Chess's clock -- but then, referees are always right.  
12943  */
12944
12945 static TimeMark tickStartTM;
12946 static long intendedTickLength;
12947
12948 long
12949 NextTickLength(timeRemaining)
12950      long timeRemaining;
12951 {
12952     long nominalTickLength, nextTickLength;
12953
12954     if (timeRemaining > 0L && timeRemaining <= 10000L)
12955       nominalTickLength = 100L;
12956     else
12957       nominalTickLength = 1000L;
12958     nextTickLength = timeRemaining % nominalTickLength;
12959     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12960
12961     return nextTickLength;
12962 }
12963
12964 /* Adjust clock one minute up or down */
12965 void
12966 AdjustClock(Boolean which, int dir)
12967 {
12968     if(which) blackTimeRemaining += 60000*dir;
12969     else      whiteTimeRemaining += 60000*dir;
12970     DisplayBothClocks();
12971 }
12972
12973 /* Stop clocks and reset to a fresh time control */
12974 void
12975 ResetClocks() 
12976 {
12977     (void) StopClockTimer();
12978     if (appData.icsActive) {
12979         whiteTimeRemaining = blackTimeRemaining = 0;
12980     } else { /* [HGM] correct new time quote for time odds */
12981         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
12982         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
12983     }
12984     if (whiteFlag || blackFlag) {
12985         DisplayTitle("");
12986         whiteFlag = blackFlag = FALSE;
12987     }
12988     DisplayBothClocks();
12989 }
12990
12991 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
12992
12993 /* Decrement running clock by amount of time that has passed */
12994 void
12995 DecrementClocks()
12996 {
12997     long timeRemaining;
12998     long lastTickLength, fudge;
12999     TimeMark now;
13000
13001     if (!appData.clockMode) return;
13002     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13003         
13004     GetTimeMark(&now);
13005
13006     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13007
13008     /* Fudge if we woke up a little too soon */
13009     fudge = intendedTickLength - lastTickLength;
13010     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13011
13012     if (WhiteOnMove(forwardMostMove)) {
13013         if(whiteNPS >= 0) lastTickLength = 0;
13014         timeRemaining = whiteTimeRemaining -= lastTickLength;
13015         DisplayWhiteClock(whiteTimeRemaining - fudge,
13016                           WhiteOnMove(currentMove));
13017     } else {
13018         if(blackNPS >= 0) lastTickLength = 0;
13019         timeRemaining = blackTimeRemaining -= lastTickLength;
13020         DisplayBlackClock(blackTimeRemaining - fudge,
13021                           !WhiteOnMove(currentMove));
13022     }
13023
13024     if (CheckFlags()) return;
13025         
13026     tickStartTM = now;
13027     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13028     StartClockTimer(intendedTickLength);
13029
13030     /* if the time remaining has fallen below the alarm threshold, sound the
13031      * alarm. if the alarm has sounded and (due to a takeback or time control
13032      * with increment) the time remaining has increased to a level above the
13033      * threshold, reset the alarm so it can sound again. 
13034      */
13035     
13036     if (appData.icsActive && appData.icsAlarm) {
13037
13038         /* make sure we are dealing with the user's clock */
13039         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13040                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13041            )) return;
13042
13043         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13044             alarmSounded = FALSE;
13045         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13046             PlayAlarmSound();
13047             alarmSounded = TRUE;
13048         }
13049     }
13050 }
13051
13052
13053 /* A player has just moved, so stop the previously running
13054    clock and (if in clock mode) start the other one.
13055    We redisplay both clocks in case we're in ICS mode, because
13056    ICS gives us an update to both clocks after every move.
13057    Note that this routine is called *after* forwardMostMove
13058    is updated, so the last fractional tick must be subtracted
13059    from the color that is *not* on move now.
13060 */
13061 void
13062 SwitchClocks()
13063 {
13064     long lastTickLength;
13065     TimeMark now;
13066     int flagged = FALSE;
13067
13068     GetTimeMark(&now);
13069
13070     if (StopClockTimer() && appData.clockMode) {
13071         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13072         if (WhiteOnMove(forwardMostMove)) {
13073             if(blackNPS >= 0) lastTickLength = 0;
13074             blackTimeRemaining -= lastTickLength;
13075            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13076 //         if(pvInfoList[forwardMostMove-1].time == -1)
13077                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13078                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13079         } else {
13080            if(whiteNPS >= 0) lastTickLength = 0;
13081            whiteTimeRemaining -= lastTickLength;
13082            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13083 //         if(pvInfoList[forwardMostMove-1].time == -1)
13084                  pvInfoList[forwardMostMove-1].time = 
13085                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13086         }
13087         flagged = CheckFlags();
13088     }
13089     CheckTimeControl();
13090
13091     if (flagged || !appData.clockMode) return;
13092
13093     switch (gameMode) {
13094       case MachinePlaysBlack:
13095       case MachinePlaysWhite:
13096       case BeginningOfGame:
13097         if (pausing) return;
13098         break;
13099
13100       case EditGame:
13101       case PlayFromGameFile:
13102       case IcsExamining:
13103         return;
13104
13105       default:
13106         break;
13107     }
13108
13109     tickStartTM = now;
13110     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13111       whiteTimeRemaining : blackTimeRemaining);
13112     StartClockTimer(intendedTickLength);
13113 }
13114         
13115
13116 /* Stop both clocks */
13117 void
13118 StopClocks()
13119 {       
13120     long lastTickLength;
13121     TimeMark now;
13122
13123     if (!StopClockTimer()) return;
13124     if (!appData.clockMode) return;
13125
13126     GetTimeMark(&now);
13127
13128     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13129     if (WhiteOnMove(forwardMostMove)) {
13130         if(whiteNPS >= 0) lastTickLength = 0;
13131         whiteTimeRemaining -= lastTickLength;
13132         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13133     } else {
13134         if(blackNPS >= 0) lastTickLength = 0;
13135         blackTimeRemaining -= lastTickLength;
13136         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13137     }
13138     CheckFlags();
13139 }
13140         
13141 /* Start clock of player on move.  Time may have been reset, so
13142    if clock is already running, stop and restart it. */
13143 void
13144 StartClocks()
13145 {
13146     (void) StopClockTimer(); /* in case it was running already */
13147     DisplayBothClocks();
13148     if (CheckFlags()) return;
13149
13150     if (!appData.clockMode) return;
13151     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13152
13153     GetTimeMark(&tickStartTM);
13154     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13155       whiteTimeRemaining : blackTimeRemaining);
13156
13157    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13158     whiteNPS = blackNPS = -1; 
13159     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13160        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13161         whiteNPS = first.nps;
13162     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13163        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13164         blackNPS = first.nps;
13165     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13166         whiteNPS = second.nps;
13167     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13168         blackNPS = second.nps;
13169     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13170
13171     StartClockTimer(intendedTickLength);
13172 }
13173
13174 char *
13175 TimeString(ms)
13176      long ms;
13177 {
13178     long second, minute, hour, day;
13179     char *sign = "";
13180     static char buf[32];
13181     
13182     if (ms > 0 && ms <= 9900) {
13183       /* convert milliseconds to tenths, rounding up */
13184       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13185
13186       sprintf(buf, " %03.1f ", tenths/10.0);
13187       return buf;
13188     }
13189
13190     /* convert milliseconds to seconds, rounding up */
13191     /* use floating point to avoid strangeness of integer division
13192        with negative dividends on many machines */
13193     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13194
13195     if (second < 0) {
13196         sign = "-";
13197         second = -second;
13198     }
13199     
13200     day = second / (60 * 60 * 24);
13201     second = second % (60 * 60 * 24);
13202     hour = second / (60 * 60);
13203     second = second % (60 * 60);
13204     minute = second / 60;
13205     second = second % 60;
13206     
13207     if (day > 0)
13208       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13209               sign, day, hour, minute, second);
13210     else if (hour > 0)
13211       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13212     else
13213       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13214     
13215     return buf;
13216 }
13217
13218
13219 /*
13220  * This is necessary because some C libraries aren't ANSI C compliant yet.
13221  */
13222 char *
13223 StrStr(string, match)
13224      char *string, *match;
13225 {
13226     int i, length;
13227     
13228     length = strlen(match);
13229     
13230     for (i = strlen(string) - length; i >= 0; i--, string++)
13231       if (!strncmp(match, string, length))
13232         return string;
13233     
13234     return NULL;
13235 }
13236
13237 char *
13238 StrCaseStr(string, match)
13239      char *string, *match;
13240 {
13241     int i, j, length;
13242     
13243     length = strlen(match);
13244     
13245     for (i = strlen(string) - length; i >= 0; i--, string++) {
13246         for (j = 0; j < length; j++) {
13247             if (ToLower(match[j]) != ToLower(string[j]))
13248               break;
13249         }
13250         if (j == length) return string;
13251     }
13252
13253     return NULL;
13254 }
13255
13256 #ifndef _amigados
13257 int
13258 StrCaseCmp(s1, s2)
13259      char *s1, *s2;
13260 {
13261     char c1, c2;
13262     
13263     for (;;) {
13264         c1 = ToLower(*s1++);
13265         c2 = ToLower(*s2++);
13266         if (c1 > c2) return 1;
13267         if (c1 < c2) return -1;
13268         if (c1 == NULLCHAR) return 0;
13269     }
13270 }
13271
13272
13273 int
13274 ToLower(c)
13275      int c;
13276 {
13277     return isupper(c) ? tolower(c) : c;
13278 }
13279
13280
13281 int
13282 ToUpper(c)
13283      int c;
13284 {
13285     return islower(c) ? toupper(c) : c;
13286 }
13287 #endif /* !_amigados    */
13288
13289 char *
13290 StrSave(s)
13291      char *s;
13292 {
13293     char *ret;
13294
13295     if ((ret = (char *) malloc(strlen(s) + 1))) {
13296         strcpy(ret, s);
13297     }
13298     return ret;
13299 }
13300
13301 char *
13302 StrSavePtr(s, savePtr)
13303      char *s, **savePtr;
13304 {
13305     if (*savePtr) {
13306         free(*savePtr);
13307     }
13308     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13309         strcpy(*savePtr, s);
13310     }
13311     return(*savePtr);
13312 }
13313
13314 char *
13315 PGNDate()
13316 {
13317     time_t clock;
13318     struct tm *tm;
13319     char buf[MSG_SIZ];
13320
13321     clock = time((time_t *)NULL);
13322     tm = localtime(&clock);
13323     sprintf(buf, "%04d.%02d.%02d",
13324             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13325     return StrSave(buf);
13326 }
13327
13328
13329 char *
13330 PositionToFEN(move, overrideCastling)
13331      int move;
13332      char *overrideCastling;
13333 {
13334     int i, j, fromX, fromY, toX, toY;
13335     int whiteToPlay;
13336     char buf[128];
13337     char *p, *q;
13338     int emptycount;
13339     ChessSquare piece;
13340
13341     whiteToPlay = (gameMode == EditPosition) ?
13342       !blackPlaysFirst : (move % 2 == 0);
13343     p = buf;
13344
13345     /* Piece placement data */
13346     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13347         emptycount = 0;
13348         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13349             if (boards[move][i][j] == EmptySquare) {
13350                 emptycount++;
13351             } else { ChessSquare piece = boards[move][i][j];
13352                 if (emptycount > 0) {
13353                     if(emptycount<10) /* [HGM] can be >= 10 */
13354                         *p++ = '0' + emptycount;
13355                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13356                     emptycount = 0;
13357                 }
13358                 if(PieceToChar(piece) == '+') {
13359                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13360                     *p++ = '+';
13361                     piece = (ChessSquare)(DEMOTED piece);
13362                 } 
13363                 *p++ = PieceToChar(piece);
13364                 if(p[-1] == '~') {
13365                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13366                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13367                     *p++ = '~';
13368                 }
13369             }
13370         }
13371         if (emptycount > 0) {
13372             if(emptycount<10) /* [HGM] can be >= 10 */
13373                 *p++ = '0' + emptycount;
13374             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13375             emptycount = 0;
13376         }
13377         *p++ = '/';
13378     }
13379     *(p - 1) = ' ';
13380
13381     /* [HGM] print Crazyhouse or Shogi holdings */
13382     if( gameInfo.holdingsWidth ) {
13383         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13384         q = p;
13385         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13386             piece = boards[move][i][BOARD_WIDTH-1];
13387             if( piece != EmptySquare )
13388               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13389                   *p++ = PieceToChar(piece);
13390         }
13391         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13392             piece = boards[move][BOARD_HEIGHT-i-1][0];
13393             if( piece != EmptySquare )
13394               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13395                   *p++ = PieceToChar(piece);
13396         }
13397
13398         if( q == p ) *p++ = '-';
13399         *p++ = ']';
13400         *p++ = ' ';
13401     }
13402
13403     /* Active color */
13404     *p++ = whiteToPlay ? 'w' : 'b';
13405     *p++ = ' ';
13406
13407   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13408     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13409   } else {
13410   if(nrCastlingRights) {
13411      q = p;
13412      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13413        /* [HGM] write directly from rights */
13414            if(castlingRights[move][2] >= 0 &&
13415               castlingRights[move][0] >= 0   )
13416                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13417            if(castlingRights[move][2] >= 0 &&
13418               castlingRights[move][1] >= 0   )
13419                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13420            if(castlingRights[move][5] >= 0 &&
13421               castlingRights[move][3] >= 0   )
13422                 *p++ = castlingRights[move][3] + AAA;
13423            if(castlingRights[move][5] >= 0 &&
13424               castlingRights[move][4] >= 0   )
13425                 *p++ = castlingRights[move][4] + AAA;
13426      } else {
13427
13428         /* [HGM] write true castling rights */
13429         if( nrCastlingRights == 6 ) {
13430             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13431                castlingRights[move][2] >= 0  ) *p++ = 'K';
13432             if(castlingRights[move][1] == BOARD_LEFT &&
13433                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13434             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13435                castlingRights[move][5] >= 0  ) *p++ = 'k';
13436             if(castlingRights[move][4] == BOARD_LEFT &&
13437                castlingRights[move][5] >= 0  ) *p++ = 'q';
13438         }
13439      }
13440      if (q == p) *p++ = '-'; /* No castling rights */
13441      *p++ = ' ';
13442   }
13443
13444   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13445      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13446     /* En passant target square */
13447     if (move > backwardMostMove) {
13448         fromX = moveList[move - 1][0] - AAA;
13449         fromY = moveList[move - 1][1] - ONE;
13450         toX = moveList[move - 1][2] - AAA;
13451         toY = moveList[move - 1][3] - ONE;
13452         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13453             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13454             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13455             fromX == toX) {
13456             /* 2-square pawn move just happened */
13457             *p++ = toX + AAA;
13458             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13459         } else {
13460             *p++ = '-';
13461         }
13462     } else if(move == backwardMostMove) {
13463         // [HGM] perhaps we should always do it like this, and forget the above?
13464         if(epStatus[move] >= 0) {
13465             *p++ = epStatus[move] + AAA;
13466             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13467         } else {
13468             *p++ = '-';
13469         }
13470     } else {
13471         *p++ = '-';
13472     }
13473     *p++ = ' ';
13474   }
13475   }
13476
13477     /* [HGM] find reversible plies */
13478     {   int i = 0, j=move;
13479
13480         if (appData.debugMode) { int k;
13481             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13482             for(k=backwardMostMove; k<=forwardMostMove; k++)
13483                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13484
13485         }
13486
13487         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13488         if( j == backwardMostMove ) i += initialRulePlies;
13489         sprintf(p, "%d ", i);
13490         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13491     }
13492     /* Fullmove number */
13493     sprintf(p, "%d", (move / 2) + 1);
13494     
13495     return StrSave(buf);
13496 }
13497
13498 Boolean
13499 ParseFEN(board, blackPlaysFirst, fen)
13500     Board board;
13501      int *blackPlaysFirst;
13502      char *fen;
13503 {
13504     int i, j;
13505     char *p;
13506     int emptycount;
13507     ChessSquare piece;
13508
13509     p = fen;
13510
13511     /* [HGM] by default clear Crazyhouse holdings, if present */
13512     if(gameInfo.holdingsWidth) {
13513        for(i=0; i<BOARD_HEIGHT; i++) {
13514            board[i][0]             = EmptySquare; /* black holdings */
13515            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13516            board[i][1]             = (ChessSquare) 0; /* black counts */
13517            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13518        }
13519     }
13520
13521     /* Piece placement data */
13522     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13523         j = 0;
13524         for (;;) {
13525             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13526                 if (*p == '/') p++;
13527                 emptycount = gameInfo.boardWidth - j;
13528                 while (emptycount--)
13529                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13530                 break;
13531 #if(BOARD_SIZE >= 10)
13532             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13533                 p++; emptycount=10;
13534                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13535                 while (emptycount--)
13536                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13537 #endif
13538             } else if (isdigit(*p)) {
13539                 emptycount = *p++ - '0';
13540                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13541                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13542                 while (emptycount--)
13543                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13544             } else if (*p == '+' || isalpha(*p)) {
13545                 if (j >= gameInfo.boardWidth) return FALSE;
13546                 if(*p=='+') {
13547                     piece = CharToPiece(*++p);
13548                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13549                     piece = (ChessSquare) (PROMOTED piece ); p++;
13550                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13551                 } else piece = CharToPiece(*p++);
13552
13553                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13554                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13555                     piece = (ChessSquare) (PROMOTED piece);
13556                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13557                     p++;
13558                 }
13559                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13560             } else {
13561                 return FALSE;
13562             }
13563         }
13564     }
13565     while (*p == '/' || *p == ' ') p++;
13566
13567     /* [HGM] look for Crazyhouse holdings here */
13568     while(*p==' ') p++;
13569     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13570         if(*p == '[') p++;
13571         if(*p == '-' ) *p++; /* empty holdings */ else {
13572             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13573             /* if we would allow FEN reading to set board size, we would   */
13574             /* have to add holdings and shift the board read so far here   */
13575             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13576                 *p++;
13577                 if((int) piece >= (int) BlackPawn ) {
13578                     i = (int)piece - (int)BlackPawn;
13579                     i = PieceToNumber((ChessSquare)i);
13580                     if( i >= gameInfo.holdingsSize ) return FALSE;
13581                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13582                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13583                 } else {
13584                     i = (int)piece - (int)WhitePawn;
13585                     i = PieceToNumber((ChessSquare)i);
13586                     if( i >= gameInfo.holdingsSize ) return FALSE;
13587                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13588                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13589                 }
13590             }
13591         }
13592         if(*p == ']') *p++;
13593     }
13594
13595     while(*p == ' ') p++;
13596
13597     /* Active color */
13598     switch (*p++) {
13599       case 'w':
13600         *blackPlaysFirst = FALSE;
13601         break;
13602       case 'b': 
13603         *blackPlaysFirst = TRUE;
13604         break;
13605       default:
13606         return FALSE;
13607     }
13608
13609     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13610     /* return the extra info in global variiables             */
13611
13612     /* set defaults in case FEN is incomplete */
13613     FENepStatus = EP_UNKNOWN;
13614     for(i=0; i<nrCastlingRights; i++ ) {
13615         FENcastlingRights[i] =
13616             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13617     }   /* assume possible unless obviously impossible */
13618     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13619     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13620     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13621     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13622     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13623     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13624     FENrulePlies = 0;
13625
13626     while(*p==' ') p++;
13627     if(nrCastlingRights) {
13628       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13629           /* castling indicator present, so default becomes no castlings */
13630           for(i=0; i<nrCastlingRights; i++ ) {
13631                  FENcastlingRights[i] = -1;
13632           }
13633       }
13634       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13635              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13636              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13637              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13638         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13639
13640         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13641             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13642             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13643         }
13644         switch(c) {
13645           case'K':
13646               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13647               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13648               FENcastlingRights[2] = whiteKingFile;
13649               break;
13650           case'Q':
13651               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13652               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13653               FENcastlingRights[2] = whiteKingFile;
13654               break;
13655           case'k':
13656               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13657               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13658               FENcastlingRights[5] = blackKingFile;
13659               break;
13660           case'q':
13661               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13662               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13663               FENcastlingRights[5] = blackKingFile;
13664           case '-':
13665               break;
13666           default: /* FRC castlings */
13667               if(c >= 'a') { /* black rights */
13668                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13669                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13670                   if(i == BOARD_RGHT) break;
13671                   FENcastlingRights[5] = i;
13672                   c -= AAA;
13673                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13674                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13675                   if(c > i)
13676                       FENcastlingRights[3] = c;
13677                   else
13678                       FENcastlingRights[4] = c;
13679               } else { /* white rights */
13680                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13681                     if(board[0][i] == WhiteKing) break;
13682                   if(i == BOARD_RGHT) break;
13683                   FENcastlingRights[2] = i;
13684                   c -= AAA - 'a' + 'A';
13685                   if(board[0][c] >= WhiteKing) break;
13686                   if(c > i)
13687                       FENcastlingRights[0] = c;
13688                   else
13689                       FENcastlingRights[1] = c;
13690               }
13691         }
13692       }
13693     if (appData.debugMode) {
13694         fprintf(debugFP, "FEN castling rights:");
13695         for(i=0; i<nrCastlingRights; i++)
13696         fprintf(debugFP, " %d", FENcastlingRights[i]);
13697         fprintf(debugFP, "\n");
13698     }
13699
13700       while(*p==' ') p++;
13701     }
13702
13703     /* read e.p. field in games that know e.p. capture */
13704     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13705        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13706       if(*p=='-') {
13707         p++; FENepStatus = EP_NONE;
13708       } else {
13709          char c = *p++ - AAA;
13710
13711          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13712          if(*p >= '0' && *p <='9') *p++;
13713          FENepStatus = c;
13714       }
13715     }
13716
13717
13718     if(sscanf(p, "%d", &i) == 1) {
13719         FENrulePlies = i; /* 50-move ply counter */
13720         /* (The move number is still ignored)    */
13721     }
13722
13723     return TRUE;
13724 }
13725       
13726 void
13727 EditPositionPasteFEN(char *fen)
13728 {
13729   if (fen != NULL) {
13730     Board initial_position;
13731
13732     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13733       DisplayError(_("Bad FEN position in clipboard"), 0);
13734       return ;
13735     } else {
13736       int savedBlackPlaysFirst = blackPlaysFirst;
13737       EditPositionEvent();
13738       blackPlaysFirst = savedBlackPlaysFirst;
13739       CopyBoard(boards[0], initial_position);
13740           /* [HGM] copy FEN attributes as well */
13741           {   int i;
13742               initialRulePlies = FENrulePlies;
13743               epStatus[0] = FENepStatus;
13744               for( i=0; i<nrCastlingRights; i++ )
13745                   castlingRights[0][i] = FENcastlingRights[i];
13746           }
13747       EditPositionDone();
13748       DisplayBothClocks();
13749       DrawPosition(FALSE, boards[currentMove]);
13750     }
13751   }
13752 }