fix for bug #27751: negative holding counts displayed
[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     set_cont_sequence(appData.wrapContSeq);
1046     if (appData.matchGames > 0) {
1047         appData.matchMode = TRUE;
1048     } else if (appData.matchMode) {
1049         appData.matchGames = 1;
1050     }
1051     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052         appData.matchGames = appData.sameColorGames;
1053     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1056     }
1057     Reset(TRUE, FALSE);
1058     if (appData.noChessProgram || first.protocolVersion == 1) {
1059       InitBackEnd3();
1060     } else {
1061       /* kludge: allow timeout for initial "feature" commands */
1062       FreezeUI();
1063       DisplayMessage("", _("Starting chess program"));
1064       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1065     }
1066 }
1067
1068 void
1069 InitBackEnd3 P((void))
1070 {
1071     GameMode initialMode;
1072     char buf[MSG_SIZ];
1073     int err;
1074
1075     InitChessProgram(&first, startedFromSetupPosition);
1076
1077
1078     if (appData.icsActive) {
1079 #ifdef WIN32
1080         /* [DM] Make a console window if needed [HGM] merged ifs */
1081         ConsoleCreate(); 
1082 #endif
1083         err = establish();
1084         if (err != 0) {
1085             if (*appData.icsCommPort != NULLCHAR) {
1086                 sprintf(buf, _("Could not open comm port %s"),  
1087                         appData.icsCommPort);
1088             } else {
1089                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1090                         appData.icsHost, appData.icsPort);
1091             }
1092             DisplayFatalError(buf, err, 1);
1093             return;
1094         }
1095         SetICSMode();
1096         telnetISR =
1097           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1098         fromUserISR =
1099           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100     } else if (appData.noChessProgram) {
1101         SetNCPMode();
1102     } else {
1103         SetGNUMode();
1104     }
1105
1106     if (*appData.cmailGameName != NULLCHAR) {
1107         SetCmailMode();
1108         OpenLoopback(&cmailPR);
1109         cmailISR =
1110           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1111     }
1112     
1113     ThawUI();
1114     DisplayMessage("", "");
1115     if (StrCaseCmp(appData.initialMode, "") == 0) {
1116       initialMode = BeginningOfGame;
1117     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118       initialMode = TwoMachinesPlay;
1119     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120       initialMode = AnalyzeFile; 
1121     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122       initialMode = AnalyzeMode;
1123     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124       initialMode = MachinePlaysWhite;
1125     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126       initialMode = MachinePlaysBlack;
1127     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128       initialMode = EditGame;
1129     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130       initialMode = EditPosition;
1131     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132       initialMode = Training;
1133     } else {
1134       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135       DisplayFatalError(buf, 0, 2);
1136       return;
1137     }
1138
1139     if (appData.matchMode) {
1140         /* Set up machine vs. machine match */
1141         if (appData.noChessProgram) {
1142             DisplayFatalError(_("Can't have a match with no chess programs"),
1143                               0, 2);
1144             return;
1145         }
1146         matchMode = TRUE;
1147         matchGame = 1;
1148         if (*appData.loadGameFile != NULLCHAR) {
1149             int index = appData.loadGameIndex; // [HGM] autoinc
1150             if(index<0) lastIndex = index = 1;
1151             if (!LoadGameFromFile(appData.loadGameFile,
1152                                   index,
1153                                   appData.loadGameFile, FALSE)) {
1154                 DisplayFatalError(_("Bad game file"), 0, 1);
1155                 return;
1156             }
1157         } else if (*appData.loadPositionFile != NULLCHAR) {
1158             int index = appData.loadPositionIndex; // [HGM] autoinc
1159             if(index<0) lastIndex = index = 1;
1160             if (!LoadPositionFromFile(appData.loadPositionFile,
1161                                       index,
1162                                       appData.loadPositionFile)) {
1163                 DisplayFatalError(_("Bad position file"), 0, 1);
1164                 return;
1165             }
1166         }
1167         TwoMachinesEvent();
1168     } else if (*appData.cmailGameName != NULLCHAR) {
1169         /* Set up cmail mode */
1170         ReloadCmailMsgEvent(TRUE);
1171     } else {
1172         /* Set up other modes */
1173         if (initialMode == AnalyzeFile) {
1174           if (*appData.loadGameFile == NULLCHAR) {
1175             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1176             return;
1177           }
1178         }
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             (void) LoadGameFromFile(appData.loadGameFile,
1181                                     appData.loadGameIndex,
1182                                     appData.loadGameFile, TRUE);
1183         } else if (*appData.loadPositionFile != NULLCHAR) {
1184             (void) LoadPositionFromFile(appData.loadPositionFile,
1185                                         appData.loadPositionIndex,
1186                                         appData.loadPositionFile);
1187             /* [HGM] try to make self-starting even after FEN load */
1188             /* to allow automatic setup of fairy variants with wtm */
1189             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190                 gameMode = BeginningOfGame;
1191                 setboardSpoiledMachineBlack = 1;
1192             }
1193             /* [HGM] loadPos: make that every new game uses the setup */
1194             /* from file as long as we do not switch variant          */
1195             if(!blackPlaysFirst) { int i;
1196                 startedFromPositionFile = TRUE;
1197                 CopyBoard(filePosition, boards[0]);
1198                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1199             }
1200         }
1201         if (initialMode == AnalyzeMode) {
1202           if (appData.noChessProgram) {
1203             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1204             return;
1205           }
1206           if (appData.icsActive) {
1207             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1208             return;
1209           }
1210           AnalyzeModeEvent();
1211         } else if (initialMode == AnalyzeFile) {
1212           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213           ShowThinkingEvent();
1214           AnalyzeFileEvent();
1215           AnalysisPeriodicEvent(1);
1216         } else if (initialMode == MachinePlaysWhite) {
1217           if (appData.noChessProgram) {
1218             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1219                               0, 2);
1220             return;
1221           }
1222           if (appData.icsActive) {
1223             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1224                               0, 2);
1225             return;
1226           }
1227           MachineWhiteEvent();
1228         } else if (initialMode == MachinePlaysBlack) {
1229           if (appData.noChessProgram) {
1230             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1231                               0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1236                               0, 2);
1237             return;
1238           }
1239           MachineBlackEvent();
1240         } else if (initialMode == TwoMachinesPlay) {
1241           if (appData.noChessProgram) {
1242             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1243                               0, 2);
1244             return;
1245           }
1246           if (appData.icsActive) {
1247             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1248                               0, 2);
1249             return;
1250           }
1251           TwoMachinesEvent();
1252         } else if (initialMode == EditGame) {
1253           EditGameEvent();
1254         } else if (initialMode == EditPosition) {
1255           EditPositionEvent();
1256         } else if (initialMode == Training) {
1257           if (*appData.loadGameFile == NULLCHAR) {
1258             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1259             return;
1260           }
1261           TrainingEvent();
1262         }
1263     }
1264 }
1265
1266 /*
1267  * Establish will establish a contact to a remote host.port.
1268  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269  *  used to talk to the host.
1270  * Returns 0 if okay, error code if not.
1271  */
1272 int
1273 establish()
1274 {
1275     char buf[MSG_SIZ];
1276
1277     if (*appData.icsCommPort != NULLCHAR) {
1278         /* Talk to the host through a serial comm port */
1279         return OpenCommPort(appData.icsCommPort, &icsPR);
1280
1281     } else if (*appData.gateway != NULLCHAR) {
1282         if (*appData.remoteShell == NULLCHAR) {
1283             /* Use the rcmd protocol to run telnet program on a gateway host */
1284             snprintf(buf, sizeof(buf), "%s %s %s",
1285                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1286             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1287
1288         } else {
1289             /* Use the rsh program to run telnet program on a gateway host */
1290             if (*appData.remoteUser == NULLCHAR) {
1291                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292                         appData.gateway, appData.telnetProgram,
1293                         appData.icsHost, appData.icsPort);
1294             } else {
1295                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296                         appData.remoteShell, appData.gateway, 
1297                         appData.remoteUser, appData.telnetProgram,
1298                         appData.icsHost, appData.icsPort);
1299             }
1300             return StartChildProcess(buf, "", &icsPR);
1301
1302         }
1303     } else if (appData.useTelnet) {
1304         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1305
1306     } else {
1307         /* TCP socket interface differs somewhat between
1308            Unix and NT; handle details in the front end.
1309            */
1310         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1311     }
1312 }
1313
1314 void
1315 show_bytes(fp, buf, count)
1316      FILE *fp;
1317      char *buf;
1318      int count;
1319 {
1320     while (count--) {
1321         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322             fprintf(fp, "\\%03o", *buf & 0xff);
1323         } else {
1324             putc(*buf, fp);
1325         }
1326         buf++;
1327     }
1328     fflush(fp);
1329 }
1330
1331 /* Returns an errno value */
1332 int
1333 OutputMaybeTelnet(pr, message, count, outError)
1334      ProcRef pr;
1335      char *message;
1336      int count;
1337      int *outError;
1338 {
1339     char buf[8192], *p, *q, *buflim;
1340     int left, newcount, outcount;
1341
1342     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343         *appData.gateway != NULLCHAR) {
1344         if (appData.debugMode) {
1345             fprintf(debugFP, ">ICS: ");
1346             show_bytes(debugFP, message, count);
1347             fprintf(debugFP, "\n");
1348         }
1349         return OutputToProcess(pr, message, count, outError);
1350     }
1351
1352     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1353     p = message;
1354     q = buf;
1355     left = count;
1356     newcount = 0;
1357     while (left) {
1358         if (q >= buflim) {
1359             if (appData.debugMode) {
1360                 fprintf(debugFP, ">ICS: ");
1361                 show_bytes(debugFP, buf, newcount);
1362                 fprintf(debugFP, "\n");
1363             }
1364             outcount = OutputToProcess(pr, buf, newcount, outError);
1365             if (outcount < newcount) return -1; /* to be sure */
1366             q = buf;
1367             newcount = 0;
1368         }
1369         if (*p == '\n') {
1370             *q++ = '\r';
1371             newcount++;
1372         } else if (((unsigned char) *p) == TN_IAC) {
1373             *q++ = (char) TN_IAC;
1374             newcount ++;
1375         }
1376         *q++ = *p++;
1377         newcount++;
1378         left--;
1379     }
1380     if (appData.debugMode) {
1381         fprintf(debugFP, ">ICS: ");
1382         show_bytes(debugFP, buf, newcount);
1383         fprintf(debugFP, "\n");
1384     }
1385     outcount = OutputToProcess(pr, buf, newcount, outError);
1386     if (outcount < newcount) return -1; /* to be sure */
1387     return count;
1388 }
1389
1390 void
1391 read_from_player(isr, closure, message, count, error)
1392      InputSourceRef isr;
1393      VOIDSTAR closure;
1394      char *message;
1395      int count;
1396      int error;
1397 {
1398     int outError, outCount;
1399     static int gotEof = 0;
1400
1401     /* Pass data read from player on to ICS */
1402     if (count > 0) {
1403         gotEof = 0;
1404         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405         if (outCount < count) {
1406             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1407         }
1408     } else if (count < 0) {
1409         RemoveInputSource(isr);
1410         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411     } else if (gotEof++ > 0) {
1412         RemoveInputSource(isr);
1413         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1414     }
1415 }
1416
1417 void
1418 KeepAlive()
1419 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420     SendToICS("date\n");
1421     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1422 }
1423
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1426 {
1427     char buffer[MSG_SIZ];
1428     va_list args;
1429
1430     va_start(args, format);
1431     vsnprintf(buffer, sizeof(buffer), format, args);
1432     buffer[sizeof(buffer)-1] = '\0';
1433     SendToICS(buffer);
1434     va_end(args);
1435 }
1436
1437 void
1438 SendToICS(s)
1439      char *s;
1440 {
1441     int count, outCount, outError;
1442
1443     if (icsPR == NULL) return;
1444
1445     count = strlen(s);
1446     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447     if (outCount < count) {
1448         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449     }
1450 }
1451
1452 /* This is used for sending logon scripts to the ICS. Sending
1453    without a delay causes problems when using timestamp on ICC
1454    (at least on my machine). */
1455 void
1456 SendToICSDelayed(s,msdelay)
1457      char *s;
1458      long msdelay;
1459 {
1460     int count, outCount, outError;
1461
1462     if (icsPR == NULL) return;
1463
1464     count = strlen(s);
1465     if (appData.debugMode) {
1466         fprintf(debugFP, ">ICS: ");
1467         show_bytes(debugFP, s, count);
1468         fprintf(debugFP, "\n");
1469     }
1470     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1471                                       msdelay);
1472     if (outCount < count) {
1473         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1474     }
1475 }
1476
1477
1478 /* Remove all highlighting escape sequences in s
1479    Also deletes any suffix starting with '(' 
1480    */
1481 char *
1482 StripHighlightAndTitle(s)
1483      char *s;
1484 {
1485     static char retbuf[MSG_SIZ];
1486     char *p = retbuf;
1487
1488     while (*s != NULLCHAR) {
1489         while (*s == '\033') {
1490             while (*s != NULLCHAR && !isalpha(*s)) s++;
1491             if (*s != NULLCHAR) s++;
1492         }
1493         while (*s != NULLCHAR && *s != '\033') {
1494             if (*s == '(' || *s == '[') {
1495                 *p = NULLCHAR;
1496                 return retbuf;
1497             }
1498             *p++ = *s++;
1499         }
1500     }
1501     *p = NULLCHAR;
1502     return retbuf;
1503 }
1504
1505 /* Remove all highlighting escape sequences in s */
1506 char *
1507 StripHighlight(s)
1508      char *s;
1509 {
1510     static char retbuf[MSG_SIZ];
1511     char *p = retbuf;
1512
1513     while (*s != NULLCHAR) {
1514         while (*s == '\033') {
1515             while (*s != NULLCHAR && !isalpha(*s)) s++;
1516             if (*s != NULLCHAR) s++;
1517         }
1518         while (*s != NULLCHAR && *s != '\033') {
1519             *p++ = *s++;
1520         }
1521     }
1522     *p = NULLCHAR;
1523     return retbuf;
1524 }
1525
1526 char *variantNames[] = VARIANT_NAMES;
1527 char *
1528 VariantName(v)
1529      VariantClass v;
1530 {
1531     return variantNames[v];
1532 }
1533
1534
1535 /* Identify a variant from the strings the chess servers use or the
1536    PGN Variant tag names we use. */
1537 VariantClass
1538 StringToVariant(e)
1539      char *e;
1540 {
1541     char *p;
1542     int wnum = -1;
1543     VariantClass v = VariantNormal;
1544     int i, found = FALSE;
1545     char buf[MSG_SIZ];
1546
1547     if (!e) return v;
1548
1549     /* [HGM] skip over optional board-size prefixes */
1550     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552         while( *e++ != '_');
1553     }
1554
1555     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1556         v = VariantNormal;
1557         found = TRUE;
1558     } else
1559     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560       if (StrCaseStr(e, variantNames[i])) {
1561         v = (VariantClass) i;
1562         found = TRUE;
1563         break;
1564       }
1565     }
1566
1567     if (!found) {
1568       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569           || StrCaseStr(e, "wild/fr") 
1570           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571         v = VariantFischeRandom;
1572       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573                  (i = 1, p = StrCaseStr(e, "w"))) {
1574         p += i;
1575         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1576         if (isdigit(*p)) {
1577           wnum = atoi(p);
1578         } else {
1579           wnum = -1;
1580         }
1581         switch (wnum) {
1582         case 0: /* FICS only, actually */
1583         case 1:
1584           /* Castling legal even if K starts on d-file */
1585           v = VariantWildCastle;
1586           break;
1587         case 2:
1588         case 3:
1589         case 4:
1590           /* Castling illegal even if K & R happen to start in
1591              normal positions. */
1592           v = VariantNoCastle;
1593           break;
1594         case 5:
1595         case 7:
1596         case 8:
1597         case 10:
1598         case 11:
1599         case 12:
1600         case 13:
1601         case 14:
1602         case 15:
1603         case 18:
1604         case 19:
1605           /* Castling legal iff K & R start in normal positions */
1606           v = VariantNormal;
1607           break;
1608         case 6:
1609         case 20:
1610         case 21:
1611           /* Special wilds for position setup; unclear what to do here */
1612           v = VariantLoadable;
1613           break;
1614         case 9:
1615           /* Bizarre ICC game */
1616           v = VariantTwoKings;
1617           break;
1618         case 16:
1619           v = VariantKriegspiel;
1620           break;
1621         case 17:
1622           v = VariantLosers;
1623           break;
1624         case 22:
1625           v = VariantFischeRandom;
1626           break;
1627         case 23:
1628           v = VariantCrazyhouse;
1629           break;
1630         case 24:
1631           v = VariantBughouse;
1632           break;
1633         case 25:
1634           v = Variant3Check;
1635           break;
1636         case 26:
1637           /* Not quite the same as FICS suicide! */
1638           v = VariantGiveaway;
1639           break;
1640         case 27:
1641           v = VariantAtomic;
1642           break;
1643         case 28:
1644           v = VariantShatranj;
1645           break;
1646
1647         /* Temporary names for future ICC types.  The name *will* change in 
1648            the next xboard/WinBoard release after ICC defines it. */
1649         case 29:
1650           v = Variant29;
1651           break;
1652         case 30:
1653           v = Variant30;
1654           break;
1655         case 31:
1656           v = Variant31;
1657           break;
1658         case 32:
1659           v = Variant32;
1660           break;
1661         case 33:
1662           v = Variant33;
1663           break;
1664         case 34:
1665           v = Variant34;
1666           break;
1667         case 35:
1668           v = Variant35;
1669           break;
1670         case 36:
1671           v = Variant36;
1672           break;
1673         case 37:
1674           v = VariantShogi;
1675           break;
1676         case 38:
1677           v = VariantXiangqi;
1678           break;
1679         case 39:
1680           v = VariantCourier;
1681           break;
1682         case 40:
1683           v = VariantGothic;
1684           break;
1685         case 41:
1686           v = VariantCapablanca;
1687           break;
1688         case 42:
1689           v = VariantKnightmate;
1690           break;
1691         case 43:
1692           v = VariantFairy;
1693           break;
1694         case 44:
1695           v = VariantCylinder;
1696           break;
1697         case 45:
1698           v = VariantFalcon;
1699           break;
1700         case 46:
1701           v = VariantCapaRandom;
1702           break;
1703         case 47:
1704           v = VariantBerolina;
1705           break;
1706         case 48:
1707           v = VariantJanus;
1708           break;
1709         case 49:
1710           v = VariantSuper;
1711           break;
1712         case 50:
1713           v = VariantGreat;
1714           break;
1715         case -1:
1716           /* Found "wild" or "w" in the string but no number;
1717              must assume it's normal chess. */
1718           v = VariantNormal;
1719           break;
1720         default:
1721           sprintf(buf, _("Unknown wild type %d"), wnum);
1722           DisplayError(buf, 0);
1723           v = VariantUnknown;
1724           break;
1725         }
1726       }
1727     }
1728     if (appData.debugMode) {
1729       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730               e, wnum, VariantName(v));
1731     }
1732     return v;
1733 }
1734
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1737
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739    advance *index beyond it, and set leftover_start to the new value of
1740    *index; else return FALSE.  If pattern contains the character '*', it
1741    matches any sequence of characters not containing '\r', '\n', or the
1742    character following the '*' (if any), and the matched sequence(s) are
1743    copied into star_match.
1744    */
1745 int
1746 looking_at(buf, index, pattern)
1747      char *buf;
1748      int *index;
1749      char *pattern;
1750 {
1751     char *bufp = &buf[*index], *patternp = pattern;
1752     int star_count = 0;
1753     char *matchp = star_match[0];
1754     
1755     for (;;) {
1756         if (*patternp == NULLCHAR) {
1757             *index = leftover_start = bufp - buf;
1758             *matchp = NULLCHAR;
1759             return TRUE;
1760         }
1761         if (*bufp == NULLCHAR) return FALSE;
1762         if (*patternp == '*') {
1763             if (*bufp == *(patternp + 1)) {
1764                 *matchp = NULLCHAR;
1765                 matchp = star_match[++star_count];
1766                 patternp += 2;
1767                 bufp++;
1768                 continue;
1769             } else if (*bufp == '\n' || *bufp == '\r') {
1770                 patternp++;
1771                 if (*patternp == NULLCHAR)
1772                   continue;
1773                 else
1774                   return FALSE;
1775             } else {
1776                 *matchp++ = *bufp++;
1777                 continue;
1778             }
1779         }
1780         if (*patternp != *bufp) return FALSE;
1781         patternp++;
1782         bufp++;
1783     }
1784 }
1785
1786 void
1787 SendToPlayer(data, length)
1788      char *data;
1789      int length;
1790 {
1791     int error, outCount;
1792     outCount = OutputToProcess(NoProc, data, length, &error);
1793     if (outCount < length) {
1794         DisplayFatalError(_("Error writing to display"), error, 1);
1795     }
1796 }
1797
1798 void
1799 PackHolding(packed, holding)
1800      char packed[];
1801      char *holding;
1802 {
1803     char *p = holding;
1804     char *q = packed;
1805     int runlength = 0;
1806     int curr = 9999;
1807     do {
1808         if (*p == curr) {
1809             runlength++;
1810         } else {
1811             switch (runlength) {
1812               case 0:
1813                 break;
1814               case 1:
1815                 *q++ = curr;
1816                 break;
1817               case 2:
1818                 *q++ = curr;
1819                 *q++ = curr;
1820                 break;
1821               default:
1822                 sprintf(q, "%d", runlength);
1823                 while (*q) q++;
1824                 *q++ = curr;
1825                 break;
1826             }
1827             runlength = 1;
1828             curr = *p;
1829         }
1830     } while (*p++);
1831     *q = NULLCHAR;
1832 }
1833
1834 /* Telnet protocol requests from the front end */
1835 void
1836 TelnetRequest(ddww, option)
1837      unsigned char ddww, option;
1838 {
1839     unsigned char msg[3];
1840     int outCount, outError;
1841
1842     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1843
1844     if (appData.debugMode) {
1845         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1846         switch (ddww) {
1847           case TN_DO:
1848             ddwwStr = "DO";
1849             break;
1850           case TN_DONT:
1851             ddwwStr = "DONT";
1852             break;
1853           case TN_WILL:
1854             ddwwStr = "WILL";
1855             break;
1856           case TN_WONT:
1857             ddwwStr = "WONT";
1858             break;
1859           default:
1860             ddwwStr = buf1;
1861             sprintf(buf1, "%d", ddww);
1862             break;
1863         }
1864         switch (option) {
1865           case TN_ECHO:
1866             optionStr = "ECHO";
1867             break;
1868           default:
1869             optionStr = buf2;
1870             sprintf(buf2, "%d", option);
1871             break;
1872         }
1873         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1874     }
1875     msg[0] = TN_IAC;
1876     msg[1] = ddww;
1877     msg[2] = option;
1878     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1879     if (outCount < 3) {
1880         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1881     }
1882 }
1883
1884 void
1885 DoEcho()
1886 {
1887     if (!appData.icsActive) return;
1888     TelnetRequest(TN_DO, TN_ECHO);
1889 }
1890
1891 void
1892 DontEcho()
1893 {
1894     if (!appData.icsActive) return;
1895     TelnetRequest(TN_DONT, TN_ECHO);
1896 }
1897
1898 void
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1900 {
1901     /* put the holdings sent to us by the server on the board holdings area */
1902     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1903     char p;
1904     ChessSquare piece;
1905
1906     if(gameInfo.holdingsWidth < 2)  return;
1907
1908     if( (int)lowestPiece >= BlackPawn ) {
1909         holdingsColumn = 0;
1910         countsColumn = 1;
1911         holdingsStartRow = BOARD_HEIGHT-1;
1912         direction = -1;
1913     } else {
1914         holdingsColumn = BOARD_WIDTH-1;
1915         countsColumn = BOARD_WIDTH-2;
1916         holdingsStartRow = 0;
1917         direction = 1;
1918     }
1919
1920     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1921         board[i][holdingsColumn] = EmptySquare;
1922         board[i][countsColumn]   = (ChessSquare) 0;
1923     }
1924     while( (p=*holdings++) != NULLCHAR ) {
1925         piece = CharToPiece( ToUpper(p) );
1926         if(piece == EmptySquare) continue;
1927         /*j = (int) piece - (int) WhitePawn;*/
1928         j = PieceToNumber(piece);
1929         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1930         if(j < 0) continue;               /* should not happen */
1931         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1932         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1933         board[holdingsStartRow+j*direction][countsColumn]++;
1934     }
1935
1936 }
1937
1938
1939 void
1940 VariantSwitch(Board board, VariantClass newVariant)
1941 {
1942    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1943
1944    startedFromPositionFile = FALSE;
1945    if(gameInfo.variant == newVariant) return;
1946
1947    /* [HGM] This routine is called each time an assignment is made to
1948     * gameInfo.variant during a game, to make sure the board sizes
1949     * are set to match the new variant. If that means adding or deleting
1950     * holdings, we shift the playing board accordingly
1951     * This kludge is needed because in ICS observe mode, we get boards
1952     * of an ongoing game without knowing the variant, and learn about the
1953     * latter only later. This can be because of the move list we requested,
1954     * in which case the game history is refilled from the beginning anyway,
1955     * but also when receiving holdings of a crazyhouse game. In the latter
1956     * case we want to add those holdings to the already received position.
1957     */
1958
1959    
1960    if (appData.debugMode) {
1961      fprintf(debugFP, "Switch board from %s to %s\n",
1962              VariantName(gameInfo.variant), VariantName(newVariant));
1963      setbuf(debugFP, NULL);
1964    }
1965    shuffleOpenings = 0;       /* [HGM] shuffle */
1966    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1967    switch(newVariant) 
1968      {
1969      case VariantShogi:
1970        newWidth = 9;  newHeight = 9;
1971        gameInfo.holdingsSize = 7;
1972      case VariantBughouse:
1973      case VariantCrazyhouse:
1974        newHoldingsWidth = 2; break;
1975      case VariantGreat:
1976        newWidth = 10;
1977      case VariantSuper:
1978        newHoldingsWidth = 2;
1979        gameInfo.holdingsSize = 8;
1980        return;
1981      case VariantGothic:
1982      case VariantCapablanca:
1983      case VariantCapaRandom:
1984        newWidth = 10;
1985      default:
1986        newHoldingsWidth = gameInfo.holdingsSize = 0;
1987      };
1988    
1989    if(newWidth  != gameInfo.boardWidth  ||
1990       newHeight != gameInfo.boardHeight ||
1991       newHoldingsWidth != gameInfo.holdingsWidth ) {
1992      
1993      /* shift position to new playing area, if needed */
1994      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1995        for(i=0; i<BOARD_HEIGHT; i++) 
1996          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1997            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1998              board[i][j];
1999        for(i=0; i<newHeight; i++) {
2000          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2001          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2002        }
2003      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2004        for(i=0; i<BOARD_HEIGHT; i++)
2005          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2006            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2007              board[i][j];
2008      }
2009      gameInfo.boardWidth  = newWidth;
2010      gameInfo.boardHeight = newHeight;
2011      gameInfo.holdingsWidth = newHoldingsWidth;
2012      gameInfo.variant = newVariant;
2013      InitDrawingSizes(-2, 0);
2014      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2015    } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2016    
2017    DrawPosition(TRUE, boards[currentMove]);
2018 }
2019
2020 static int loggedOn = FALSE;
2021
2022 /*-- Game start info cache: --*/
2023 int gs_gamenum;
2024 char gs_kind[MSG_SIZ];
2025 static char player1Name[128] = "";
2026 static char player2Name[128] = "";
2027 static char cont_seq[] = "\n\\   ";
2028 static int player1Rating = -1;
2029 static int player2Rating = -1;
2030 /*----------------------------*/
2031
2032 ColorClass curColor = ColorNormal;
2033 int suppressKibitz = 0;
2034
2035 void
2036 read_from_ics(isr, closure, data, count, error)
2037      InputSourceRef isr;
2038      VOIDSTAR closure;
2039      char *data;
2040      int count;
2041      int error;
2042 {
2043 #define BUF_SIZE 8192
2044 #define STARTED_NONE 0
2045 #define STARTED_MOVES 1
2046 #define STARTED_BOARD 2
2047 #define STARTED_OBSERVE 3
2048 #define STARTED_HOLDINGS 4
2049 #define STARTED_CHATTER 5
2050 #define STARTED_COMMENT 6
2051 #define STARTED_MOVES_NOHIDE 7
2052     
2053     static int started = STARTED_NONE;
2054     static char parse[20000];
2055     static int parse_pos = 0;
2056     static char buf[BUF_SIZE + 1];
2057     static int firstTime = TRUE, intfSet = FALSE;
2058     static ColorClass prevColor = ColorNormal;
2059     static int savingComment = FALSE;
2060     static int cmatch = 0; // continuation sequence match
2061     char *bp;
2062     char str[500];
2063     int i, oldi;
2064     int buf_len;
2065     int next_out;
2066     int tkind;
2067     int backup;    /* [DM] For zippy color lines */
2068     char *p;
2069     char talker[MSG_SIZ]; // [HGM] chat
2070     int channel;
2071
2072     if (appData.debugMode) {
2073       if (!error) {
2074         fprintf(debugFP, "<ICS: ");
2075         show_bytes(debugFP, data, count);
2076         fprintf(debugFP, "\n");
2077       }
2078     }
2079
2080     if (appData.debugMode) { int f = forwardMostMove;
2081         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2082                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2083     }
2084     if (count > 0) {
2085         /* If last read ended with a partial line that we couldn't parse,
2086            prepend it to the new read and try again. */
2087         if (leftover_len > 0) {
2088             for (i=0; i<leftover_len; i++)
2089               buf[i] = buf[leftover_start + i];
2090         }
2091
2092     /* copy new characters into the buffer */
2093     bp = buf + leftover_len;
2094     buf_len=leftover_len;
2095     for (i=0; i<count; i++)
2096     {
2097         // ignore these
2098         if (data[i] == '\r')
2099             continue;
2100
2101         // join lines split by ICS?
2102         if (!appData.noJoin)
2103         {
2104             /*
2105                 Joining just consists of finding matches against the
2106                 continuation sequence, and discarding that sequence
2107                 if found instead of copying it.  So, until a match
2108                 fails, there's nothing to do since it might be the
2109                 complete sequence, and thus, something we don't want
2110                 copied.
2111             */
2112             if (data[i] == cont_seq[cmatch])
2113             {
2114                 cmatch++;
2115                 if (cmatch == strlen(cont_seq))
2116                 {
2117                     cmatch = 0; // complete match.  just reset the counter
2118
2119                     /*
2120                         it's possible for the ICS to not include the space
2121                         at the end of the last word, making our [correct]
2122                         join operation fuse two separate words.  the server
2123                         does this when the space occurs at the width setting.
2124                     */
2125                     if (!buf_len || buf[buf_len-1] != ' ')
2126                     {
2127                         *bp++ = ' ';
2128                         buf_len++;
2129                     }
2130                 }
2131                 continue;
2132             }
2133             else if (cmatch)
2134             {
2135                 /*
2136                     match failed, so we have to copy what matched before
2137                     falling through and copying this character.  In reality,
2138                     this will only ever be just the newline character, but
2139                     it doesn't hurt to be precise.
2140                 */
2141                 strncpy(bp, cont_seq, cmatch);
2142                 bp += cmatch;
2143                 buf_len += cmatch;
2144                 cmatch = 0;
2145             }
2146         }
2147
2148         // copy this char
2149         *bp++ = data[i];
2150         buf_len++;
2151     }
2152
2153         buf[buf_len] = NULLCHAR;
2154         next_out = leftover_len;
2155         leftover_start = 0;
2156         
2157         i = 0;
2158         while (i < buf_len) {
2159             /* Deal with part of the TELNET option negotiation
2160                protocol.  We refuse to do anything beyond the
2161                defaults, except that we allow the WILL ECHO option,
2162                which ICS uses to turn off password echoing when we are
2163                directly connected to it.  We reject this option
2164                if localLineEditing mode is on (always on in xboard)
2165                and we are talking to port 23, which might be a real
2166                telnet server that will try to keep WILL ECHO on permanently.
2167              */
2168             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2169                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2170                 unsigned char option;
2171                 oldi = i;
2172                 switch ((unsigned char) buf[++i]) {
2173                   case TN_WILL:
2174                     if (appData.debugMode)
2175                       fprintf(debugFP, "\n<WILL ");
2176                     switch (option = (unsigned char) buf[++i]) {
2177                       case TN_ECHO:
2178                         if (appData.debugMode)
2179                           fprintf(debugFP, "ECHO ");
2180                         /* Reply only if this is a change, according
2181                            to the protocol rules. */
2182                         if (remoteEchoOption) break;
2183                         if (appData.localLineEditing &&
2184                             atoi(appData.icsPort) == TN_PORT) {
2185                             TelnetRequest(TN_DONT, TN_ECHO);
2186                         } else {
2187                             EchoOff();
2188                             TelnetRequest(TN_DO, TN_ECHO);
2189                             remoteEchoOption = TRUE;
2190                         }
2191                         break;
2192                       default:
2193                         if (appData.debugMode)
2194                           fprintf(debugFP, "%d ", option);
2195                         /* Whatever this is, we don't want it. */
2196                         TelnetRequest(TN_DONT, option);
2197                         break;
2198                     }
2199                     break;
2200                   case TN_WONT:
2201                     if (appData.debugMode)
2202                       fprintf(debugFP, "\n<WONT ");
2203                     switch (option = (unsigned char) buf[++i]) {
2204                       case TN_ECHO:
2205                         if (appData.debugMode)
2206                           fprintf(debugFP, "ECHO ");
2207                         /* Reply only if this is a change, according
2208                            to the protocol rules. */
2209                         if (!remoteEchoOption) break;
2210                         EchoOn();
2211                         TelnetRequest(TN_DONT, TN_ECHO);
2212                         remoteEchoOption = FALSE;
2213                         break;
2214                       default:
2215                         if (appData.debugMode)
2216                           fprintf(debugFP, "%d ", (unsigned char) option);
2217                         /* Whatever this is, it must already be turned
2218                            off, because we never agree to turn on
2219                            anything non-default, so according to the
2220                            protocol rules, we don't reply. */
2221                         break;
2222                     }
2223                     break;
2224                   case TN_DO:
2225                     if (appData.debugMode)
2226                       fprintf(debugFP, "\n<DO ");
2227                     switch (option = (unsigned char) buf[++i]) {
2228                       default:
2229                         /* Whatever this is, we refuse to do it. */
2230                         if (appData.debugMode)
2231                           fprintf(debugFP, "%d ", option);
2232                         TelnetRequest(TN_WONT, option);
2233                         break;
2234                     }
2235                     break;
2236                   case TN_DONT:
2237                     if (appData.debugMode)
2238                       fprintf(debugFP, "\n<DONT ");
2239                     switch (option = (unsigned char) buf[++i]) {
2240                       default:
2241                         if (appData.debugMode)
2242                           fprintf(debugFP, "%d ", option);
2243                         /* Whatever this is, we are already not doing
2244                            it, because we never agree to do anything
2245                            non-default, so according to the protocol
2246                            rules, we don't reply. */
2247                         break;
2248                     }
2249                     break;
2250                   case TN_IAC:
2251                     if (appData.debugMode)
2252                       fprintf(debugFP, "\n<IAC ");
2253                     /* Doubled IAC; pass it through */
2254                     i--;
2255                     break;
2256                   default:
2257                     if (appData.debugMode)
2258                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2259                     /* Drop all other telnet commands on the floor */
2260                     break;
2261                 }
2262                 if (oldi > next_out)
2263                   SendToPlayer(&buf[next_out], oldi - next_out);
2264                 if (++i > next_out)
2265                   next_out = i;
2266                 continue;
2267             }
2268                 
2269             /* OK, this at least will *usually* work */
2270             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2271                 loggedOn = TRUE;
2272             }
2273             
2274             if (loggedOn && !intfSet) {
2275                 if (ics_type == ICS_ICC) {
2276                   sprintf(str,
2277                           "/set-quietly interface %s\n/set-quietly style 12\n",
2278                           programVersion);
2279                 } else if (ics_type == ICS_CHESSNET) {
2280                   sprintf(str, "/style 12\n");
2281                 } else {
2282                   strcpy(str, "alias $ @\n$set interface ");
2283                   strcat(str, programVersion);
2284                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2285 #ifdef WIN32
2286                   strcat(str, "$iset nohighlight 1\n");
2287 #endif
2288                   strcat(str, "$iset lock 1\n$style 12\n");
2289                 }
2290                 SendToICS(str);
2291                 NotifyFrontendLogin();
2292                 intfSet = TRUE;
2293             }
2294
2295             if (started == STARTED_COMMENT) {
2296                 /* Accumulate characters in comment */
2297                 parse[parse_pos++] = buf[i];
2298                 if (buf[i] == '\n') {
2299                     parse[parse_pos] = NULLCHAR;
2300                     if(chattingPartner>=0) {
2301                         char mess[MSG_SIZ];
2302                         sprintf(mess, "%s%s", talker, parse);
2303                         OutputChatMessage(chattingPartner, mess);
2304                         chattingPartner = -1;
2305                     } else
2306                     if(!suppressKibitz) // [HGM] kibitz
2307                         AppendComment(forwardMostMove, StripHighlight(parse));
2308                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2309                         int nrDigit = 0, nrAlph = 0, i;
2310                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2311                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2312                         parse[parse_pos] = NULLCHAR;
2313                         // try to be smart: if it does not look like search info, it should go to
2314                         // ICS interaction window after all, not to engine-output window.
2315                         for(i=0; i<parse_pos; i++) { // count letters and digits
2316                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2317                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2318                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2319                         }
2320                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2321                             int depth=0; float score;
2322                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2323                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2324                                 pvInfoList[forwardMostMove-1].depth = depth;
2325                                 pvInfoList[forwardMostMove-1].score = 100*score;
2326                             }
2327                             OutputKibitz(suppressKibitz, parse);
2328                         } else {
2329                             char tmp[MSG_SIZ];
2330                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2331                             SendToPlayer(tmp, strlen(tmp));
2332                         }
2333                     }
2334                     started = STARTED_NONE;
2335                 } else {
2336                     /* Don't match patterns against characters in chatter */
2337                     i++;
2338                     continue;
2339                 }
2340             }
2341             if (started == STARTED_CHATTER) {
2342                 if (buf[i] != '\n') {
2343                     /* Don't match patterns against characters in chatter */
2344                     i++;
2345                     continue;
2346                 }
2347                 started = STARTED_NONE;
2348             }
2349
2350             /* Kludge to deal with rcmd protocol */
2351             if (firstTime && looking_at(buf, &i, "\001*")) {
2352                 DisplayFatalError(&buf[1], 0, 1);
2353                 continue;
2354             } else {
2355                 firstTime = FALSE;
2356             }
2357
2358             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2359                 ics_type = ICS_ICC;
2360                 ics_prefix = "/";
2361                 if (appData.debugMode)
2362                   fprintf(debugFP, "ics_type %d\n", ics_type);
2363                 continue;
2364             }
2365             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2366                 ics_type = ICS_FICS;
2367                 ics_prefix = "$";
2368                 if (appData.debugMode)
2369                   fprintf(debugFP, "ics_type %d\n", ics_type);
2370                 continue;
2371             }
2372             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2373                 ics_type = ICS_CHESSNET;
2374                 ics_prefix = "/";
2375                 if (appData.debugMode)
2376                   fprintf(debugFP, "ics_type %d\n", ics_type);
2377                 continue;
2378             }
2379
2380             if (!loggedOn &&
2381                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2382                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2383                  looking_at(buf, &i, "will be \"*\""))) {
2384               strcpy(ics_handle, star_match[0]);
2385               continue;
2386             }
2387
2388             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2389               char buf[MSG_SIZ];
2390               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2391               DisplayIcsInteractionTitle(buf);
2392               have_set_title = TRUE;
2393             }
2394
2395             /* skip finger notes */
2396             if (started == STARTED_NONE &&
2397                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2398                  (buf[i] == '1' && buf[i+1] == '0')) &&
2399                 buf[i+2] == ':' && buf[i+3] == ' ') {
2400               started = STARTED_CHATTER;
2401               i += 3;
2402               continue;
2403             }
2404
2405             /* skip formula vars */
2406             if (started == STARTED_NONE &&
2407                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2408               started = STARTED_CHATTER;
2409               i += 3;
2410               continue;
2411             }
2412
2413             oldi = i;
2414             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2415             if (appData.autoKibitz && started == STARTED_NONE && 
2416                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2417                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2418                 if(looking_at(buf, &i, "* kibitzes: ") &&
2419                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2420                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2421                         suppressKibitz = TRUE;
2422                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2423                                 && (gameMode == IcsPlayingWhite)) ||
2424                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2425                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2426                             started = STARTED_CHATTER; // own kibitz we simply discard
2427                         else {
2428                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2429                             parse_pos = 0; parse[0] = NULLCHAR;
2430                             savingComment = TRUE;
2431                             suppressKibitz = gameMode != IcsObserving ? 2 :
2432                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2433                         } 
2434                         continue;
2435                 } else
2436                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2437                     started = STARTED_CHATTER;
2438                     suppressKibitz = TRUE;
2439                 }
2440             } // [HGM] kibitz: end of patch
2441
2442 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2443
2444             // [HGM] chat: intercept tells by users for which we have an open chat window
2445             channel = -1;
2446             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2447                                            looking_at(buf, &i, "* whispers:") ||
2448                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2449                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2450                 int p;
2451                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2452                 chattingPartner = -1;
2453
2454                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2455                 for(p=0; p<MAX_CHAT; p++) {
2456                     if(channel == atoi(chatPartner[p])) {
2457                     talker[0] = '['; strcat(talker, "]");
2458                     chattingPartner = p; break;
2459                     }
2460                 } else
2461                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2462                 for(p=0; p<MAX_CHAT; p++) {
2463                     if(!strcmp("WHISPER", chatPartner[p])) {
2464                         talker[0] = '['; strcat(talker, "]");
2465                         chattingPartner = p; break;
2466                     }
2467                 }
2468                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2469                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2470                     talker[0] = 0;
2471                     chattingPartner = p; break;
2472                 }
2473                 if(chattingPartner<0) i = oldi; else {
2474                     started = STARTED_COMMENT;
2475                     parse_pos = 0; parse[0] = NULLCHAR;
2476                     savingComment = TRUE;
2477                     suppressKibitz = TRUE;
2478                 }
2479             } // [HGM] chat: end of patch
2480
2481             if (appData.zippyTalk || appData.zippyPlay) {
2482                 /* [DM] Backup address for color zippy lines */
2483                 backup = i;
2484 #if ZIPPY
2485        #ifdef WIN32
2486                if (loggedOn == TRUE)
2487                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2488                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2489        #else
2490                 if (ZippyControl(buf, &i) ||
2491                     ZippyConverse(buf, &i) ||
2492                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2493                       loggedOn = TRUE;
2494                       if (!appData.colorize) continue;
2495                 }
2496        #endif
2497 #endif
2498             } // [DM] 'else { ' deleted
2499                 if (
2500                     /* Regular tells and says */
2501                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2502                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2503                     looking_at(buf, &i, "* says: ") ||
2504                     /* Don't color "message" or "messages" output */
2505                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2506                     looking_at(buf, &i, "*. * at *:*: ") ||
2507                     looking_at(buf, &i, "--* (*:*): ") ||
2508                     /* Message notifications (same color as tells) */
2509                     looking_at(buf, &i, "* has left a message ") ||
2510                     looking_at(buf, &i, "* just sent you a message:\n") ||
2511                     /* Whispers and kibitzes */
2512                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2513                     looking_at(buf, &i, "* kibitzes: ") ||
2514                     /* Channel tells */
2515                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2516
2517                   if (tkind == 1 && strchr(star_match[0], ':')) {
2518                       /* Avoid "tells you:" spoofs in channels */
2519                      tkind = 3;
2520                   }
2521                   if (star_match[0][0] == NULLCHAR ||
2522                       strchr(star_match[0], ' ') ||
2523                       (tkind == 3 && strchr(star_match[1], ' '))) {
2524                     /* Reject bogus matches */
2525                     i = oldi;
2526                   } else {
2527                     if (appData.colorize) {
2528                       if (oldi > next_out) {
2529                         SendToPlayer(&buf[next_out], oldi - next_out);
2530                         next_out = oldi;
2531                       }
2532                       switch (tkind) {
2533                       case 1:
2534                         Colorize(ColorTell, FALSE);
2535                         curColor = ColorTell;
2536                         break;
2537                       case 2:
2538                         Colorize(ColorKibitz, FALSE);
2539                         curColor = ColorKibitz;
2540                         break;
2541                       case 3:
2542                         p = strrchr(star_match[1], '(');
2543                         if (p == NULL) {
2544                           p = star_match[1];
2545                         } else {
2546                           p++;
2547                         }
2548                         if (atoi(p) == 1) {
2549                           Colorize(ColorChannel1, FALSE);
2550                           curColor = ColorChannel1;
2551                         } else {
2552                           Colorize(ColorChannel, FALSE);
2553                           curColor = ColorChannel;
2554                         }
2555                         break;
2556                       case 5:
2557                         curColor = ColorNormal;
2558                         break;
2559                       }
2560                     }
2561                     if (started == STARTED_NONE && appData.autoComment &&
2562                         (gameMode == IcsObserving ||
2563                          gameMode == IcsPlayingWhite ||
2564                          gameMode == IcsPlayingBlack)) {
2565                       parse_pos = i - oldi;
2566                       memcpy(parse, &buf[oldi], parse_pos);
2567                       parse[parse_pos] = NULLCHAR;
2568                       started = STARTED_COMMENT;
2569                       savingComment = TRUE;
2570                     } else {
2571                       started = STARTED_CHATTER;
2572                       savingComment = FALSE;
2573                     }
2574                     loggedOn = TRUE;
2575                     continue;
2576                   }
2577                 }
2578
2579                 if (looking_at(buf, &i, "* s-shouts: ") ||
2580                     looking_at(buf, &i, "* c-shouts: ")) {
2581                     if (appData.colorize) {
2582                         if (oldi > next_out) {
2583                             SendToPlayer(&buf[next_out], oldi - next_out);
2584                             next_out = oldi;
2585                         }
2586                         Colorize(ColorSShout, FALSE);
2587                         curColor = ColorSShout;
2588                     }
2589                     loggedOn = TRUE;
2590                     started = STARTED_CHATTER;
2591                     continue;
2592                 }
2593
2594                 if (looking_at(buf, &i, "--->")) {
2595                     loggedOn = TRUE;
2596                     continue;
2597                 }
2598
2599                 if (looking_at(buf, &i, "* shouts: ") ||
2600                     looking_at(buf, &i, "--> ")) {
2601                     if (appData.colorize) {
2602                         if (oldi > next_out) {
2603                             SendToPlayer(&buf[next_out], oldi - next_out);
2604                             next_out = oldi;
2605                         }
2606                         Colorize(ColorShout, FALSE);
2607                         curColor = ColorShout;
2608                     }
2609                     loggedOn = TRUE;
2610                     started = STARTED_CHATTER;
2611                     continue;
2612                 }
2613
2614                 if (looking_at( buf, &i, "Challenge:")) {
2615                     if (appData.colorize) {
2616                         if (oldi > next_out) {
2617                             SendToPlayer(&buf[next_out], oldi - next_out);
2618                             next_out = oldi;
2619                         }
2620                         Colorize(ColorChallenge, FALSE);
2621                         curColor = ColorChallenge;
2622                     }
2623                     loggedOn = TRUE;
2624                     continue;
2625                 }
2626
2627                 if (looking_at(buf, &i, "* offers you") ||
2628                     looking_at(buf, &i, "* offers to be") ||
2629                     looking_at(buf, &i, "* would like to") ||
2630                     looking_at(buf, &i, "* requests to") ||
2631                     looking_at(buf, &i, "Your opponent offers") ||
2632                     looking_at(buf, &i, "Your opponent requests")) {
2633
2634                     if (appData.colorize) {
2635                         if (oldi > next_out) {
2636                             SendToPlayer(&buf[next_out], oldi - next_out);
2637                             next_out = oldi;
2638                         }
2639                         Colorize(ColorRequest, FALSE);
2640                         curColor = ColorRequest;
2641                     }
2642                     continue;
2643                 }
2644
2645                 if (looking_at(buf, &i, "* (*) seeking")) {
2646                     if (appData.colorize) {
2647                         if (oldi > next_out) {
2648                             SendToPlayer(&buf[next_out], oldi - next_out);
2649                             next_out = oldi;
2650                         }
2651                         Colorize(ColorSeek, FALSE);
2652                         curColor = ColorSeek;
2653                     }
2654                     continue;
2655             }
2656
2657             if (looking_at(buf, &i, "\\   ")) {
2658                 if (prevColor != ColorNormal) {
2659                     if (oldi > next_out) {
2660                         SendToPlayer(&buf[next_out], oldi - next_out);
2661                         next_out = oldi;
2662                     }
2663                     Colorize(prevColor, TRUE);
2664                     curColor = prevColor;
2665                 }
2666                 if (savingComment) {
2667                     parse_pos = i - oldi;
2668                     memcpy(parse, &buf[oldi], parse_pos);
2669                     parse[parse_pos] = NULLCHAR;
2670                     started = STARTED_COMMENT;
2671                 } else {
2672                     started = STARTED_CHATTER;
2673                 }
2674                 continue;
2675             }
2676
2677             if (looking_at(buf, &i, "Black Strength :") ||
2678                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2679                 looking_at(buf, &i, "<10>") ||
2680                 looking_at(buf, &i, "#@#")) {
2681                 /* Wrong board style */
2682                 loggedOn = TRUE;
2683                 SendToICS(ics_prefix);
2684                 SendToICS("set style 12\n");
2685                 SendToICS(ics_prefix);
2686                 SendToICS("refresh\n");
2687                 continue;
2688             }
2689             
2690             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2691                 ICSInitScript();
2692                 have_sent_ICS_logon = 1;
2693                 continue;
2694             }
2695               
2696             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2697                 (looking_at(buf, &i, "\n<12> ") ||
2698                  looking_at(buf, &i, "<12> "))) {
2699                 loggedOn = TRUE;
2700                 if (oldi > next_out) {
2701                     SendToPlayer(&buf[next_out], oldi - next_out);
2702                 }
2703                 next_out = i;
2704                 started = STARTED_BOARD;
2705                 parse_pos = 0;
2706                 continue;
2707             }
2708
2709             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2710                 looking_at(buf, &i, "<b1> ")) {
2711                 if (oldi > next_out) {
2712                     SendToPlayer(&buf[next_out], oldi - next_out);
2713                 }
2714                 next_out = i;
2715                 started = STARTED_HOLDINGS;
2716                 parse_pos = 0;
2717                 continue;
2718             }
2719
2720             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2721                 loggedOn = TRUE;
2722                 /* Header for a move list -- first line */
2723
2724                 switch (ics_getting_history) {
2725                   case H_FALSE:
2726                     switch (gameMode) {
2727                       case IcsIdle:
2728                       case BeginningOfGame:
2729                         /* User typed "moves" or "oldmoves" while we
2730                            were idle.  Pretend we asked for these
2731                            moves and soak them up so user can step
2732                            through them and/or save them.
2733                            */
2734                         Reset(FALSE, TRUE);
2735                         gameMode = IcsObserving;
2736                         ModeHighlight();
2737                         ics_gamenum = -1;
2738                         ics_getting_history = H_GOT_UNREQ_HEADER;
2739                         break;
2740                       case EditGame: /*?*/
2741                       case EditPosition: /*?*/
2742                         /* Should above feature work in these modes too? */
2743                         /* For now it doesn't */
2744                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2745                         break;
2746                       default:
2747                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2748                         break;
2749                     }
2750                     break;
2751                   case H_REQUESTED:
2752                     /* Is this the right one? */
2753                     if (gameInfo.white && gameInfo.black &&
2754                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2755                         strcmp(gameInfo.black, star_match[2]) == 0) {
2756                         /* All is well */
2757                         ics_getting_history = H_GOT_REQ_HEADER;
2758                     }
2759                     break;
2760                   case H_GOT_REQ_HEADER:
2761                   case H_GOT_UNREQ_HEADER:
2762                   case H_GOT_UNWANTED_HEADER:
2763                   case H_GETTING_MOVES:
2764                     /* Should not happen */
2765                     DisplayError(_("Error gathering move list: two headers"), 0);
2766                     ics_getting_history = H_FALSE;
2767                     break;
2768                 }
2769
2770                 /* Save player ratings into gameInfo if needed */
2771                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2772                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2773                     (gameInfo.whiteRating == -1 ||
2774                      gameInfo.blackRating == -1)) {
2775
2776                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2777                     gameInfo.blackRating = string_to_rating(star_match[3]);
2778                     if (appData.debugMode)
2779                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2780                               gameInfo.whiteRating, gameInfo.blackRating);
2781                 }
2782                 continue;
2783             }
2784
2785             if (looking_at(buf, &i,
2786               "* * match, initial time: * minute*, increment: * second")) {
2787                 /* Header for a move list -- second line */
2788                 /* Initial board will follow if this is a wild game */
2789                 if (gameInfo.event != NULL) free(gameInfo.event);
2790                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2791                 gameInfo.event = StrSave(str);
2792                 /* [HGM] we switched variant. Translate boards if needed. */
2793                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2794                 continue;
2795             }
2796
2797             if (looking_at(buf, &i, "Move  ")) {
2798                 /* Beginning of a move list */
2799                 switch (ics_getting_history) {
2800                   case H_FALSE:
2801                     /* Normally should not happen */
2802                     /* Maybe user hit reset while we were parsing */
2803                     break;
2804                   case H_REQUESTED:
2805                     /* Happens if we are ignoring a move list that is not
2806                      * the one we just requested.  Common if the user
2807                      * tries to observe two games without turning off
2808                      * getMoveList */
2809                     break;
2810                   case H_GETTING_MOVES:
2811                     /* Should not happen */
2812                     DisplayError(_("Error gathering move list: nested"), 0);
2813                     ics_getting_history = H_FALSE;
2814                     break;
2815                   case H_GOT_REQ_HEADER:
2816                     ics_getting_history = H_GETTING_MOVES;
2817                     started = STARTED_MOVES;
2818                     parse_pos = 0;
2819                     if (oldi > next_out) {
2820                         SendToPlayer(&buf[next_out], oldi - next_out);
2821                     }
2822                     break;
2823                   case H_GOT_UNREQ_HEADER:
2824                     ics_getting_history = H_GETTING_MOVES;
2825                     started = STARTED_MOVES_NOHIDE;
2826                     parse_pos = 0;
2827                     break;
2828                   case H_GOT_UNWANTED_HEADER:
2829                     ics_getting_history = H_FALSE;
2830                     break;
2831                 }
2832                 continue;
2833             }                           
2834             
2835             if (looking_at(buf, &i, "% ") ||
2836                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2837                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2838                 savingComment = FALSE;
2839                 switch (started) {
2840                   case STARTED_MOVES:
2841                   case STARTED_MOVES_NOHIDE:
2842                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2843                     parse[parse_pos + i - oldi] = NULLCHAR;
2844                     ParseGameHistory(parse);
2845 #if ZIPPY
2846                     if (appData.zippyPlay && first.initDone) {
2847                         FeedMovesToProgram(&first, forwardMostMove);
2848                         if (gameMode == IcsPlayingWhite) {
2849                             if (WhiteOnMove(forwardMostMove)) {
2850                                 if (first.sendTime) {
2851                                   if (first.useColors) {
2852                                     SendToProgram("black\n", &first); 
2853                                   }
2854                                   SendTimeRemaining(&first, TRUE);
2855                                 }
2856                                 if (first.useColors) {
2857                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2858                                 }
2859                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2860                                 first.maybeThinking = TRUE;
2861                             } else {
2862                                 if (first.usePlayother) {
2863                                   if (first.sendTime) {
2864                                     SendTimeRemaining(&first, TRUE);
2865                                   }
2866                                   SendToProgram("playother\n", &first);
2867                                   firstMove = FALSE;
2868                                 } else {
2869                                   firstMove = TRUE;
2870                                 }
2871                             }
2872                         } else if (gameMode == IcsPlayingBlack) {
2873                             if (!WhiteOnMove(forwardMostMove)) {
2874                                 if (first.sendTime) {
2875                                   if (first.useColors) {
2876                                     SendToProgram("white\n", &first);
2877                                   }
2878                                   SendTimeRemaining(&first, FALSE);
2879                                 }
2880                                 if (first.useColors) {
2881                                   SendToProgram("black\n", &first);
2882                                 }
2883                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2884                                 first.maybeThinking = TRUE;
2885                             } else {
2886                                 if (first.usePlayother) {
2887                                   if (first.sendTime) {
2888                                     SendTimeRemaining(&first, FALSE);
2889                                   }
2890                                   SendToProgram("playother\n", &first);
2891                                   firstMove = FALSE;
2892                                 } else {
2893                                   firstMove = TRUE;
2894                                 }
2895                             }
2896                         }                       
2897                     }
2898 #endif
2899                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2900                         /* Moves came from oldmoves or moves command
2901                            while we weren't doing anything else.
2902                            */
2903                         currentMove = forwardMostMove;
2904                         ClearHighlights();/*!!could figure this out*/
2905                         flipView = appData.flipView;
2906                         DrawPosition(FALSE, boards[currentMove]);
2907                         DisplayBothClocks();
2908                         sprintf(str, "%s vs. %s",
2909                                 gameInfo.white, gameInfo.black);
2910                         DisplayTitle(str);
2911                         gameMode = IcsIdle;
2912                     } else {
2913                         /* Moves were history of an active game */
2914                         if (gameInfo.resultDetails != NULL) {
2915                             free(gameInfo.resultDetails);
2916                             gameInfo.resultDetails = NULL;
2917                         }
2918                     }
2919                     HistorySet(parseList, backwardMostMove,
2920                                forwardMostMove, currentMove-1);
2921                     DisplayMove(currentMove - 1);
2922                     if (started == STARTED_MOVES) next_out = i;
2923                     started = STARTED_NONE;
2924                     ics_getting_history = H_FALSE;
2925                     break;
2926
2927                   case STARTED_OBSERVE:
2928                     started = STARTED_NONE;
2929                     SendToICS(ics_prefix);
2930                     SendToICS("refresh\n");
2931                     break;
2932
2933                   default:
2934                     break;
2935                 }
2936                 if(bookHit) { // [HGM] book: simulate book reply
2937                     static char bookMove[MSG_SIZ]; // a bit generous?
2938
2939                     programStats.nodes = programStats.depth = programStats.time = 
2940                     programStats.score = programStats.got_only_move = 0;
2941                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2942
2943                     strcpy(bookMove, "move ");
2944                     strcat(bookMove, bookHit);
2945                     HandleMachineMove(bookMove, &first);
2946                 }
2947                 continue;
2948             }
2949             
2950             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2951                  started == STARTED_HOLDINGS ||
2952                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2953                 /* Accumulate characters in move list or board */
2954                 parse[parse_pos++] = buf[i];
2955             }
2956             
2957             /* Start of game messages.  Mostly we detect start of game
2958                when the first board image arrives.  On some versions
2959                of the ICS, though, we need to do a "refresh" after starting
2960                to observe in order to get the current board right away. */
2961             if (looking_at(buf, &i, "Adding game * to observation list")) {
2962                 started = STARTED_OBSERVE;
2963                 continue;
2964             }
2965
2966             /* Handle auto-observe */
2967             if (appData.autoObserve &&
2968                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2969                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2970                 char *player;
2971                 /* Choose the player that was highlighted, if any. */
2972                 if (star_match[0][0] == '\033' ||
2973                     star_match[1][0] != '\033') {
2974                     player = star_match[0];
2975                 } else {
2976                     player = star_match[2];
2977                 }
2978                 sprintf(str, "%sobserve %s\n",
2979                         ics_prefix, StripHighlightAndTitle(player));
2980                 SendToICS(str);
2981
2982                 /* Save ratings from notify string */
2983                 strcpy(player1Name, star_match[0]);
2984                 player1Rating = string_to_rating(star_match[1]);
2985                 strcpy(player2Name, star_match[2]);
2986                 player2Rating = string_to_rating(star_match[3]);
2987
2988                 if (appData.debugMode)
2989                   fprintf(debugFP, 
2990                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2991                           player1Name, player1Rating,
2992                           player2Name, player2Rating);
2993
2994                 continue;
2995             }
2996
2997             /* Deal with automatic examine mode after a game,
2998                and with IcsObserving -> IcsExamining transition */
2999             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3000                 looking_at(buf, &i, "has made you an examiner of game *")) {
3001
3002                 int gamenum = atoi(star_match[0]);
3003                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3004                     gamenum == ics_gamenum) {
3005                     /* We were already playing or observing this game;
3006                        no need to refetch history */
3007                     gameMode = IcsExamining;
3008                     if (pausing) {
3009                         pauseExamForwardMostMove = forwardMostMove;
3010                     } else if (currentMove < forwardMostMove) {
3011                         ForwardInner(forwardMostMove);
3012                     }
3013                 } else {
3014                     /* I don't think this case really can happen */
3015                     SendToICS(ics_prefix);
3016                     SendToICS("refresh\n");
3017                 }
3018                 continue;
3019             }    
3020             
3021             /* Error messages */
3022 //          if (ics_user_moved) {
3023             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3024                 if (looking_at(buf, &i, "Illegal move") ||
3025                     looking_at(buf, &i, "Not a legal move") ||
3026                     looking_at(buf, &i, "Your king is in check") ||
3027                     looking_at(buf, &i, "It isn't your turn") ||
3028                     looking_at(buf, &i, "It is not your move")) {
3029                     /* Illegal move */
3030                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3031                         currentMove = --forwardMostMove;
3032                         DisplayMove(currentMove - 1); /* before DMError */
3033                         DrawPosition(FALSE, boards[currentMove]);
3034                         SwitchClocks();
3035                         DisplayBothClocks();
3036                     }
3037                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3038                     ics_user_moved = 0;
3039                     continue;
3040                 }
3041             }
3042
3043             if (looking_at(buf, &i, "still have time") ||
3044                 looking_at(buf, &i, "not out of time") ||
3045                 looking_at(buf, &i, "either player is out of time") ||
3046                 looking_at(buf, &i, "has timeseal; checking")) {
3047                 /* We must have called his flag a little too soon */
3048                 whiteFlag = blackFlag = FALSE;
3049                 continue;
3050             }
3051
3052             if (looking_at(buf, &i, "added * seconds to") ||
3053                 looking_at(buf, &i, "seconds were added to")) {
3054                 /* Update the clocks */
3055                 SendToICS(ics_prefix);
3056                 SendToICS("refresh\n");
3057                 continue;
3058             }
3059
3060             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3061                 ics_clock_paused = TRUE;
3062                 StopClocks();
3063                 continue;
3064             }
3065
3066             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3067                 ics_clock_paused = FALSE;
3068                 StartClocks();
3069                 continue;
3070             }
3071
3072             /* Grab player ratings from the Creating: message.
3073                Note we have to check for the special case when
3074                the ICS inserts things like [white] or [black]. */
3075             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3076                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3077                 /* star_matches:
3078                    0    player 1 name (not necessarily white)
3079                    1    player 1 rating
3080                    2    empty, white, or black (IGNORED)
3081                    3    player 2 name (not necessarily black)
3082                    4    player 2 rating
3083                    
3084                    The names/ratings are sorted out when the game
3085                    actually starts (below).
3086                 */
3087                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3088                 player1Rating = string_to_rating(star_match[1]);
3089                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3090                 player2Rating = string_to_rating(star_match[4]);
3091
3092                 if (appData.debugMode)
3093                   fprintf(debugFP, 
3094                           "Ratings from 'Creating:' %s %d, %s %d\n",
3095                           player1Name, player1Rating,
3096                           player2Name, player2Rating);
3097
3098                 continue;
3099             }
3100             
3101             /* Improved generic start/end-of-game messages */
3102             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3103                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3104                 /* If tkind == 0: */
3105                 /* star_match[0] is the game number */
3106                 /*           [1] is the white player's name */
3107                 /*           [2] is the black player's name */
3108                 /* For end-of-game: */
3109                 /*           [3] is the reason for the game end */
3110                 /*           [4] is a PGN end game-token, preceded by " " */
3111                 /* For start-of-game: */
3112                 /*           [3] begins with "Creating" or "Continuing" */
3113                 /*           [4] is " *" or empty (don't care). */
3114                 int gamenum = atoi(star_match[0]);
3115                 char *whitename, *blackname, *why, *endtoken;
3116                 ChessMove endtype = (ChessMove) 0;
3117
3118                 if (tkind == 0) {
3119                   whitename = star_match[1];
3120                   blackname = star_match[2];
3121                   why = star_match[3];
3122                   endtoken = star_match[4];
3123                 } else {
3124                   whitename = star_match[1];
3125                   blackname = star_match[3];
3126                   why = star_match[5];
3127                   endtoken = star_match[6];
3128                 }
3129
3130                 /* Game start messages */
3131                 if (strncmp(why, "Creating ", 9) == 0 ||
3132                     strncmp(why, "Continuing ", 11) == 0) {
3133                     gs_gamenum = gamenum;
3134                     strcpy(gs_kind, strchr(why, ' ') + 1);
3135 #if ZIPPY
3136                     if (appData.zippyPlay) {
3137                         ZippyGameStart(whitename, blackname);
3138                     }
3139 #endif /*ZIPPY*/
3140                     continue;
3141                 }
3142
3143                 /* Game end messages */
3144                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3145                     ics_gamenum != gamenum) {
3146                     continue;
3147                 }
3148                 while (endtoken[0] == ' ') endtoken++;
3149                 switch (endtoken[0]) {
3150                   case '*':
3151                   default:
3152                     endtype = GameUnfinished;
3153                     break;
3154                   case '0':
3155                     endtype = BlackWins;
3156                     break;
3157                   case '1':
3158                     if (endtoken[1] == '/')
3159                       endtype = GameIsDrawn;
3160                     else
3161                       endtype = WhiteWins;
3162                     break;
3163                 }
3164                 GameEnds(endtype, why, GE_ICS);
3165 #if ZIPPY
3166                 if (appData.zippyPlay && first.initDone) {
3167                     ZippyGameEnd(endtype, why);
3168                     if (first.pr == NULL) {
3169                       /* Start the next process early so that we'll
3170                          be ready for the next challenge */
3171                       StartChessProgram(&first);
3172                     }
3173                     /* Send "new" early, in case this command takes
3174                        a long time to finish, so that we'll be ready
3175                        for the next challenge. */
3176                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3177                     Reset(TRUE, TRUE);
3178                 }
3179 #endif /*ZIPPY*/
3180                 continue;
3181             }
3182
3183             if (looking_at(buf, &i, "Removing game * from observation") ||
3184                 looking_at(buf, &i, "no longer observing game *") ||
3185                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3186                 if (gameMode == IcsObserving &&
3187                     atoi(star_match[0]) == ics_gamenum)
3188                   {
3189                       /* icsEngineAnalyze */
3190                       if (appData.icsEngineAnalyze) {
3191                             ExitAnalyzeMode();
3192                             ModeHighlight();
3193                       }
3194                       StopClocks();
3195                       gameMode = IcsIdle;
3196                       ics_gamenum = -1;
3197                       ics_user_moved = FALSE;
3198                   }
3199                 continue;
3200             }
3201
3202             if (looking_at(buf, &i, "no longer examining game *")) {
3203                 if (gameMode == IcsExamining &&
3204                     atoi(star_match[0]) == ics_gamenum)
3205                   {
3206                       gameMode = IcsIdle;
3207                       ics_gamenum = -1;
3208                       ics_user_moved = FALSE;
3209                   }
3210                 continue;
3211             }
3212
3213             /* Advance leftover_start past any newlines we find,
3214                so only partial lines can get reparsed */
3215             if (looking_at(buf, &i, "\n")) {
3216                 prevColor = curColor;
3217                 if (curColor != ColorNormal) {
3218                     if (oldi > next_out) {
3219                         SendToPlayer(&buf[next_out], oldi - next_out);
3220                         next_out = oldi;
3221                     }
3222                     Colorize(ColorNormal, FALSE);
3223                     curColor = ColorNormal;
3224                 }
3225                 if (started == STARTED_BOARD) {
3226                     started = STARTED_NONE;
3227                     parse[parse_pos] = NULLCHAR;
3228                     ParseBoard12(parse);
3229                     ics_user_moved = 0;
3230
3231                     /* Send premove here */
3232                     if (appData.premove) {
3233                       char str[MSG_SIZ];
3234                       if (currentMove == 0 &&
3235                           gameMode == IcsPlayingWhite &&
3236                           appData.premoveWhite) {
3237                         sprintf(str, "%s%s\n", ics_prefix,
3238                                 appData.premoveWhiteText);
3239                         if (appData.debugMode)
3240                           fprintf(debugFP, "Sending premove:\n");
3241                         SendToICS(str);
3242                       } else if (currentMove == 1 &&
3243                                  gameMode == IcsPlayingBlack &&
3244                                  appData.premoveBlack) {
3245                         sprintf(str, "%s%s\n", ics_prefix,
3246                                 appData.premoveBlackText);
3247                         if (appData.debugMode)
3248                           fprintf(debugFP, "Sending premove:\n");
3249                         SendToICS(str);
3250                       } else if (gotPremove) {
3251                         gotPremove = 0;
3252                         ClearPremoveHighlights();
3253                         if (appData.debugMode)
3254                           fprintf(debugFP, "Sending premove:\n");
3255                           UserMoveEvent(premoveFromX, premoveFromY, 
3256                                         premoveToX, premoveToY, 
3257                                         premovePromoChar);
3258                       }
3259                     }
3260
3261                     /* Usually suppress following prompt */
3262                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3263                         if (looking_at(buf, &i, "*% ")) {
3264                             savingComment = FALSE;
3265                         }
3266                     }
3267                     next_out = i;
3268                 } else if (started == STARTED_HOLDINGS) {
3269                     int gamenum;
3270                     char new_piece[MSG_SIZ];
3271                     started = STARTED_NONE;
3272                     parse[parse_pos] = NULLCHAR;
3273                     if (appData.debugMode)
3274                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3275                                                         parse, currentMove);
3276                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3277                         gamenum == ics_gamenum) {
3278                         if (gameInfo.variant == VariantNormal) {
3279                           /* [HGM] We seem to switch variant during a game!
3280                            * Presumably no holdings were displayed, so we have
3281                            * to move the position two files to the right to
3282                            * create room for them!
3283                            */
3284                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3285                           /* Get a move list just to see the header, which
3286                              will tell us whether this is really bug or zh */
3287                           if (ics_getting_history == H_FALSE) {
3288                             ics_getting_history = H_REQUESTED;
3289                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3290                             SendToICS(str);
3291                           }
3292                         }
3293                         new_piece[0] = NULLCHAR;
3294                         sscanf(parse, "game %d white [%s black [%s <- %s",
3295                                &gamenum, white_holding, black_holding,
3296                                new_piece);
3297                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3298                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3299                         /* [HGM] copy holdings to board holdings area */
3300                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3301                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3302 #if ZIPPY
3303                         if (appData.zippyPlay && first.initDone) {
3304                             ZippyHoldings(white_holding, black_holding,
3305                                           new_piece);
3306                         }
3307 #endif /*ZIPPY*/
3308                         if (tinyLayout || smallLayout) {
3309                             char wh[16], bh[16];
3310                             PackHolding(wh, white_holding);
3311                             PackHolding(bh, black_holding);
3312                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3313                                     gameInfo.white, gameInfo.black);
3314                         } else {
3315                             sprintf(str, "%s [%s] vs. %s [%s]",
3316                                     gameInfo.white, white_holding,
3317                                     gameInfo.black, black_holding);
3318                         }
3319
3320                         DrawPosition(FALSE, boards[currentMove]);
3321                         DisplayTitle(str);
3322                     }
3323                     /* Suppress following prompt */
3324                     if (looking_at(buf, &i, "*% ")) {
3325                         savingComment = FALSE;
3326                     }
3327                     next_out = i;
3328                 }
3329                 continue;
3330             }
3331
3332             i++;                /* skip unparsed character and loop back */
3333         }
3334         
3335         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3336             started != STARTED_HOLDINGS && i > next_out) {
3337             SendToPlayer(&buf[next_out], i - next_out);
3338             next_out = i;
3339         }
3340         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3341         
3342         leftover_len = buf_len - leftover_start;
3343         /* if buffer ends with something we couldn't parse,
3344            reparse it after appending the next read */
3345         
3346     } else if (count == 0) {
3347         RemoveInputSource(isr);
3348         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3349     } else {
3350         DisplayFatalError(_("Error reading from ICS"), error, 1);
3351     }
3352 }
3353
3354
3355 /* Board style 12 looks like this:
3356    
3357    <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
3358    
3359  * The "<12> " is stripped before it gets to this routine.  The two
3360  * trailing 0's (flip state and clock ticking) are later addition, and
3361  * some chess servers may not have them, or may have only the first.
3362  * Additional trailing fields may be added in the future.  
3363  */
3364
3365 #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"
3366
3367 #define RELATION_OBSERVING_PLAYED    0
3368 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3369 #define RELATION_PLAYING_MYMOVE      1
3370 #define RELATION_PLAYING_NOTMYMOVE  -1
3371 #define RELATION_EXAMINING           2
3372 #define RELATION_ISOLATED_BOARD     -3
3373 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3374
3375 void
3376 ParseBoard12(string)
3377      char *string;
3378
3379     GameMode newGameMode;
3380     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3381     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3382     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3383     char to_play, board_chars[200];
3384     char move_str[500], str[500], elapsed_time[500];
3385     char black[32], white[32];
3386     Board board;
3387     int prevMove = currentMove;
3388     int ticking = 2;
3389     ChessMove moveType;
3390     int fromX, fromY, toX, toY;
3391     char promoChar;
3392     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3393     char *bookHit = NULL; // [HGM] book
3394
3395     fromX = fromY = toX = toY = -1;
3396     
3397     newGame = FALSE;
3398
3399     if (appData.debugMode)
3400       fprintf(debugFP, _("Parsing board: %s\n"), string);
3401
3402     move_str[0] = NULLCHAR;
3403     elapsed_time[0] = NULLCHAR;
3404     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3405         int  i = 0, j;
3406         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3407             if(string[i] == ' ') { ranks++; files = 0; }
3408             else files++;
3409             i++;
3410         }
3411         for(j = 0; j <i; j++) board_chars[j] = string[j];
3412         board_chars[i] = '\0';
3413         string += i + 1;
3414     }
3415     n = sscanf(string, PATTERN, &to_play, &double_push,
3416                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3417                &gamenum, white, black, &relation, &basetime, &increment,
3418                &white_stren, &black_stren, &white_time, &black_time,
3419                &moveNum, str, elapsed_time, move_str, &ics_flip,
3420                &ticking);
3421
3422     if (n < 21) {
3423         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3424         DisplayError(str, 0);
3425         return;
3426     }
3427
3428     /* Convert the move number to internal form */
3429     moveNum = (moveNum - 1) * 2;
3430     if (to_play == 'B') moveNum++;
3431     if (moveNum >= MAX_MOVES) {
3432       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3433                         0, 1);
3434       return;
3435     }
3436     
3437     switch (relation) {
3438       case RELATION_OBSERVING_PLAYED:
3439       case RELATION_OBSERVING_STATIC:
3440         if (gamenum == -1) {
3441             /* Old ICC buglet */
3442             relation = RELATION_OBSERVING_STATIC;
3443         }
3444         newGameMode = IcsObserving;
3445         break;
3446       case RELATION_PLAYING_MYMOVE:
3447       case RELATION_PLAYING_NOTMYMOVE:
3448         newGameMode =
3449           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3450             IcsPlayingWhite : IcsPlayingBlack;
3451         break;
3452       case RELATION_EXAMINING:
3453         newGameMode = IcsExamining;
3454         break;
3455       case RELATION_ISOLATED_BOARD:
3456       default:
3457         /* Just display this board.  If user was doing something else,
3458            we will forget about it until the next board comes. */ 
3459         newGameMode = IcsIdle;
3460         break;
3461       case RELATION_STARTING_POSITION:
3462         newGameMode = gameMode;
3463         break;
3464     }
3465     
3466     /* Modify behavior for initial board display on move listing
3467        of wild games.
3468        */
3469     switch (ics_getting_history) {
3470       case H_FALSE:
3471       case H_REQUESTED:
3472         break;
3473       case H_GOT_REQ_HEADER:
3474       case H_GOT_UNREQ_HEADER:
3475         /* This is the initial position of the current game */
3476         gamenum = ics_gamenum;
3477         moveNum = 0;            /* old ICS bug workaround */
3478         if (to_play == 'B') {
3479           startedFromSetupPosition = TRUE;
3480           blackPlaysFirst = TRUE;
3481           moveNum = 1;
3482           if (forwardMostMove == 0) forwardMostMove = 1;
3483           if (backwardMostMove == 0) backwardMostMove = 1;
3484           if (currentMove == 0) currentMove = 1;
3485         }
3486         newGameMode = gameMode;
3487         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3488         break;
3489       case H_GOT_UNWANTED_HEADER:
3490         /* This is an initial board that we don't want */
3491         return;
3492       case H_GETTING_MOVES:
3493         /* Should not happen */
3494         DisplayError(_("Error gathering move list: extra board"), 0);
3495         ics_getting_history = H_FALSE;
3496         return;
3497     }
3498     
3499     /* Take action if this is the first board of a new game, or of a
3500        different game than is currently being displayed.  */
3501     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3502         relation == RELATION_ISOLATED_BOARD) {
3503         
3504         /* Forget the old game and get the history (if any) of the new one */
3505         if (gameMode != BeginningOfGame) {
3506           Reset(FALSE, TRUE);
3507         }
3508         newGame = TRUE;
3509         if (appData.autoRaiseBoard) BoardToTop();
3510         prevMove = -3;
3511         if (gamenum == -1) {
3512             newGameMode = IcsIdle;
3513         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3514                    appData.getMoveList) {
3515             /* Need to get game history */
3516             ics_getting_history = H_REQUESTED;
3517             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3518             SendToICS(str);
3519         }
3520         
3521         /* Initially flip the board to have black on the bottom if playing
3522            black or if the ICS flip flag is set, but let the user change
3523            it with the Flip View button. */
3524         flipView = appData.autoFlipView ? 
3525           (newGameMode == IcsPlayingBlack) || ics_flip :
3526           appData.flipView;
3527         
3528         /* Done with values from previous mode; copy in new ones */
3529         gameMode = newGameMode;
3530         ModeHighlight();
3531         ics_gamenum = gamenum;
3532         if (gamenum == gs_gamenum) {
3533             int klen = strlen(gs_kind);
3534             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3535             sprintf(str, "ICS %s", gs_kind);
3536             gameInfo.event = StrSave(str);
3537         } else {
3538             gameInfo.event = StrSave("ICS game");
3539         }
3540         gameInfo.site = StrSave(appData.icsHost);
3541         gameInfo.date = PGNDate();
3542         gameInfo.round = StrSave("-");
3543         gameInfo.white = StrSave(white);
3544         gameInfo.black = StrSave(black);
3545         timeControl = basetime * 60 * 1000;
3546         timeControl_2 = 0;
3547         timeIncrement = increment * 1000;
3548         movesPerSession = 0;
3549         gameInfo.timeControl = TimeControlTagValue();
3550         VariantSwitch(board, StringToVariant(gameInfo.event) );
3551   if (appData.debugMode) {
3552     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3553     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3554     setbuf(debugFP, NULL);
3555   }
3556
3557         gameInfo.outOfBook = NULL;
3558         
3559         /* Do we have the ratings? */
3560         if (strcmp(player1Name, white) == 0 &&
3561             strcmp(player2Name, black) == 0) {
3562             if (appData.debugMode)
3563               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3564                       player1Rating, player2Rating);
3565             gameInfo.whiteRating = player1Rating;
3566             gameInfo.blackRating = player2Rating;
3567         } else if (strcmp(player2Name, white) == 0 &&
3568                    strcmp(player1Name, black) == 0) {
3569             if (appData.debugMode)
3570               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3571                       player2Rating, player1Rating);
3572             gameInfo.whiteRating = player2Rating;
3573             gameInfo.blackRating = player1Rating;
3574         }
3575         player1Name[0] = player2Name[0] = NULLCHAR;
3576
3577         /* Silence shouts if requested */
3578         if (appData.quietPlay &&
3579             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3580             SendToICS(ics_prefix);
3581             SendToICS("set shout 0\n");
3582         }
3583     }
3584     
3585     /* Deal with midgame name changes */
3586     if (!newGame) {
3587         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3588             if (gameInfo.white) free(gameInfo.white);
3589             gameInfo.white = StrSave(white);
3590         }
3591         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3592             if (gameInfo.black) free(gameInfo.black);
3593             gameInfo.black = StrSave(black);
3594         }
3595     }
3596     
3597     /* Throw away game result if anything actually changes in examine mode */
3598     if (gameMode == IcsExamining && !newGame) {
3599         gameInfo.result = GameUnfinished;
3600         if (gameInfo.resultDetails != NULL) {
3601             free(gameInfo.resultDetails);
3602             gameInfo.resultDetails = NULL;
3603         }
3604     }
3605     
3606     /* In pausing && IcsExamining mode, we ignore boards coming
3607        in if they are in a different variation than we are. */
3608     if (pauseExamInvalid) return;
3609     if (pausing && gameMode == IcsExamining) {
3610         if (moveNum <= pauseExamForwardMostMove) {
3611             pauseExamInvalid = TRUE;
3612             forwardMostMove = pauseExamForwardMostMove;
3613             return;
3614         }
3615     }
3616     
3617   if (appData.debugMode) {
3618     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3619   }
3620     /* Parse the board */
3621     for (k = 0; k < ranks; k++) {
3622       for (j = 0; j < files; j++)
3623         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3624       if(gameInfo.holdingsWidth > 1) {
3625            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3626            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3627       }
3628     }
3629     CopyBoard(boards[moveNum], board);
3630     if (moveNum == 0) {
3631         startedFromSetupPosition =
3632           !CompareBoards(board, initialPosition);
3633         if(startedFromSetupPosition)
3634             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3635     }
3636
3637     /* [HGM] Set castling rights. Take the outermost Rooks,
3638        to make it also work for FRC opening positions. Note that board12
3639        is really defective for later FRC positions, as it has no way to
3640        indicate which Rook can castle if they are on the same side of King.
3641        For the initial position we grant rights to the outermost Rooks,
3642        and remember thos rights, and we then copy them on positions
3643        later in an FRC game. This means WB might not recognize castlings with
3644        Rooks that have moved back to their original position as illegal,
3645        but in ICS mode that is not its job anyway.
3646     */
3647     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3648     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3649
3650         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3651             if(board[0][i] == WhiteRook) j = i;
3652         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3653         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3654             if(board[0][i] == WhiteRook) j = i;
3655         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3656         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3657             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3658         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3659         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3660             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3661         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3662
3663         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3664         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3665             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3666         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3667             if(board[BOARD_HEIGHT-1][k] == bKing)
3668                 initialRights[5] = castlingRights[moveNum][5] = k;
3669     } else { int r;
3670         r = castlingRights[moveNum][0] = initialRights[0];
3671         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3672         r = castlingRights[moveNum][1] = initialRights[1];
3673         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3674         r = castlingRights[moveNum][3] = initialRights[3];
3675         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3676         r = castlingRights[moveNum][4] = initialRights[4];
3677         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3678         /* wildcastle kludge: always assume King has rights */
3679         r = castlingRights[moveNum][2] = initialRights[2];
3680         r = castlingRights[moveNum][5] = initialRights[5];
3681     }
3682     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3683     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3684
3685     
3686     if (ics_getting_history == H_GOT_REQ_HEADER ||
3687         ics_getting_history == H_GOT_UNREQ_HEADER) {
3688         /* This was an initial position from a move list, not
3689            the current position */
3690         return;
3691     }
3692     
3693     /* Update currentMove and known move number limits */
3694     newMove = newGame || moveNum > forwardMostMove;
3695
3696     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3697     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3698         takeback = forwardMostMove - moveNum;
3699         for (i = 0; i < takeback; i++) {
3700              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3701              SendToProgram("undo\n", &first);
3702         }
3703     }
3704
3705     if (newGame) {
3706         forwardMostMove = backwardMostMove = currentMove = moveNum;
3707         if (gameMode == IcsExamining && moveNum == 0) {
3708           /* Workaround for ICS limitation: we are not told the wild
3709              type when starting to examine a game.  But if we ask for
3710              the move list, the move list header will tell us */
3711             ics_getting_history = H_REQUESTED;
3712             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3713             SendToICS(str);
3714         }
3715     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3716                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3717         forwardMostMove = moveNum;
3718         if (!pausing || currentMove > forwardMostMove)
3719           currentMove = forwardMostMove;
3720     } else {
3721         /* New part of history that is not contiguous with old part */ 
3722         if (pausing && gameMode == IcsExamining) {
3723             pauseExamInvalid = TRUE;
3724             forwardMostMove = pauseExamForwardMostMove;
3725             return;
3726         }
3727         forwardMostMove = backwardMostMove = currentMove = moveNum;
3728         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3729             ics_getting_history = H_REQUESTED;
3730             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3731             SendToICS(str);
3732         }
3733     }
3734     
3735     /* Update the clocks */
3736     if (strchr(elapsed_time, '.')) {
3737       /* Time is in ms */
3738       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3739       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3740     } else {
3741       /* Time is in seconds */
3742       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3743       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3744     }
3745       
3746
3747 #if ZIPPY
3748     if (appData.zippyPlay && newGame &&
3749         gameMode != IcsObserving && gameMode != IcsIdle &&
3750         gameMode != IcsExamining)
3751       ZippyFirstBoard(moveNum, basetime, increment);
3752 #endif
3753     
3754     /* Put the move on the move list, first converting
3755        to canonical algebraic form. */
3756     if (moveNum > 0) {
3757   if (appData.debugMode) {
3758     if (appData.debugMode) { int f = forwardMostMove;
3759         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3760                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3761     }
3762     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3763     fprintf(debugFP, "moveNum = %d\n", moveNum);
3764     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3765     setbuf(debugFP, NULL);
3766   }
3767         if (moveNum <= backwardMostMove) {
3768             /* We don't know what the board looked like before
3769                this move.  Punt. */
3770             strcpy(parseList[moveNum - 1], move_str);
3771             strcat(parseList[moveNum - 1], " ");
3772             strcat(parseList[moveNum - 1], elapsed_time);
3773             moveList[moveNum - 1][0] = NULLCHAR;
3774         } else if (strcmp(move_str, "none") == 0) {
3775             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3776             /* Again, we don't know what the board looked like;
3777                this is really the start of the game. */
3778             parseList[moveNum - 1][0] = NULLCHAR;
3779             moveList[moveNum - 1][0] = NULLCHAR;
3780             backwardMostMove = moveNum;
3781             startedFromSetupPosition = TRUE;
3782             fromX = fromY = toX = toY = -1;
3783         } else {
3784           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3785           //                 So we parse the long-algebraic move string in stead of the SAN move
3786           int valid; char buf[MSG_SIZ], *prom;
3787
3788           // str looks something like "Q/a1-a2"; kill the slash
3789           if(str[1] == '/') 
3790                 sprintf(buf, "%c%s", str[0], str+2);
3791           else  strcpy(buf, str); // might be castling
3792           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3793                 strcat(buf, prom); // long move lacks promo specification!
3794           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3795                 if(appData.debugMode) 
3796                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3797                 strcpy(move_str, buf);
3798           }
3799           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3800                                 &fromX, &fromY, &toX, &toY, &promoChar)
3801                || ParseOneMove(buf, moveNum - 1, &moveType,
3802                                 &fromX, &fromY, &toX, &toY, &promoChar);
3803           // end of long SAN patch
3804           if (valid) {
3805             (void) CoordsToAlgebraic(boards[moveNum - 1],
3806                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3807                                      fromY, fromX, toY, toX, promoChar,
3808                                      parseList[moveNum-1]);
3809             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3810                              castlingRights[moveNum]) ) {
3811               case MT_NONE:
3812               case MT_STALEMATE:
3813               default:
3814                 break;
3815               case MT_CHECK:
3816                 if(gameInfo.variant != VariantShogi)
3817                     strcat(parseList[moveNum - 1], "+");
3818                 break;
3819               case MT_CHECKMATE:
3820               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3821                 strcat(parseList[moveNum - 1], "#");
3822                 break;
3823             }
3824             strcat(parseList[moveNum - 1], " ");
3825             strcat(parseList[moveNum - 1], elapsed_time);
3826             /* currentMoveString is set as a side-effect of ParseOneMove */
3827             strcpy(moveList[moveNum - 1], currentMoveString);
3828             strcat(moveList[moveNum - 1], "\n");
3829           } else {
3830             /* Move from ICS was illegal!?  Punt. */
3831   if (appData.debugMode) {
3832     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3833     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3834   }
3835             strcpy(parseList[moveNum - 1], move_str);
3836             strcat(parseList[moveNum - 1], " ");
3837             strcat(parseList[moveNum - 1], elapsed_time);
3838             moveList[moveNum - 1][0] = NULLCHAR;
3839             fromX = fromY = toX = toY = -1;
3840           }
3841         }
3842   if (appData.debugMode) {
3843     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3844     setbuf(debugFP, NULL);
3845   }
3846
3847 #if ZIPPY
3848         /* Send move to chess program (BEFORE animating it). */
3849         if (appData.zippyPlay && !newGame && newMove && 
3850            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3851
3852             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3853                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3854                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3855                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3856                             move_str);
3857                     DisplayError(str, 0);
3858                 } else {
3859                     if (first.sendTime) {
3860                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3861                     }
3862                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3863                     if (firstMove && !bookHit) {
3864                         firstMove = FALSE;
3865                         if (first.useColors) {
3866                           SendToProgram(gameMode == IcsPlayingWhite ?
3867                                         "white\ngo\n" :
3868                                         "black\ngo\n", &first);
3869                         } else {
3870                           SendToProgram("go\n", &first);
3871                         }
3872                         first.maybeThinking = TRUE;
3873                     }
3874                 }
3875             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3876               if (moveList[moveNum - 1][0] == NULLCHAR) {
3877                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3878                 DisplayError(str, 0);
3879               } else {
3880                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3881                 SendMoveToProgram(moveNum - 1, &first);
3882               }
3883             }
3884         }
3885 #endif
3886     }
3887
3888     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3889         /* If move comes from a remote source, animate it.  If it
3890            isn't remote, it will have already been animated. */
3891         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3892             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3893         }
3894         if (!pausing && appData.highlightLastMove) {
3895             SetHighlights(fromX, fromY, toX, toY);
3896         }
3897     }
3898     
3899     /* Start the clocks */
3900     whiteFlag = blackFlag = FALSE;
3901     appData.clockMode = !(basetime == 0 && increment == 0);
3902     if (ticking == 0) {
3903       ics_clock_paused = TRUE;
3904       StopClocks();
3905     } else if (ticking == 1) {
3906       ics_clock_paused = FALSE;
3907     }
3908     if (gameMode == IcsIdle ||
3909         relation == RELATION_OBSERVING_STATIC ||
3910         relation == RELATION_EXAMINING ||
3911         ics_clock_paused)
3912       DisplayBothClocks();
3913     else
3914       StartClocks();
3915     
3916     /* Display opponents and material strengths */
3917     if (gameInfo.variant != VariantBughouse &&
3918         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3919         if (tinyLayout || smallLayout) {
3920             if(gameInfo.variant == VariantNormal)
3921                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3922                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3923                     basetime, increment);
3924             else
3925                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3926                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3927                     basetime, increment, (int) gameInfo.variant);
3928         } else {
3929             if(gameInfo.variant == VariantNormal)
3930                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3931                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3932                     basetime, increment);
3933             else
3934                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3935                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3936                     basetime, increment, VariantName(gameInfo.variant));
3937         }
3938         DisplayTitle(str);
3939   if (appData.debugMode) {
3940     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3941   }
3942     }
3943
3944    
3945     /* Display the board */
3946     if (!pausing && !appData.noGUI) {
3947       
3948       if (appData.premove)
3949           if (!gotPremove || 
3950              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3951              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3952               ClearPremoveHighlights();
3953
3954       DrawPosition(FALSE, boards[currentMove]);
3955       DisplayMove(moveNum - 1);
3956       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3957             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3958               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3959         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3960       }
3961     }
3962
3963     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3964 #if ZIPPY
3965     if(bookHit) { // [HGM] book: simulate book reply
3966         static char bookMove[MSG_SIZ]; // a bit generous?
3967
3968         programStats.nodes = programStats.depth = programStats.time = 
3969         programStats.score = programStats.got_only_move = 0;
3970         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3971
3972         strcpy(bookMove, "move ");
3973         strcat(bookMove, bookHit);
3974         HandleMachineMove(bookMove, &first);
3975     }
3976 #endif
3977 }
3978
3979 void
3980 GetMoveListEvent()
3981 {
3982     char buf[MSG_SIZ];
3983     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3984         ics_getting_history = H_REQUESTED;
3985         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3986         SendToICS(buf);
3987     }
3988 }
3989
3990 void
3991 AnalysisPeriodicEvent(force)
3992      int force;
3993 {
3994     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3995          && !force) || !appData.periodicUpdates)
3996       return;
3997
3998     /* Send . command to Crafty to collect stats */
3999     SendToProgram(".\n", &first);
4000
4001     /* Don't send another until we get a response (this makes
4002        us stop sending to old Crafty's which don't understand
4003        the "." command (sending illegal cmds resets node count & time,
4004        which looks bad)) */
4005     programStats.ok_to_send = 0;
4006 }
4007
4008 void ics_update_width(new_width)
4009         int new_width;
4010 {
4011         ics_printf("set width %d\n", new_width);
4012 }
4013
4014 void
4015 SendMoveToProgram(moveNum, cps)
4016      int moveNum;
4017      ChessProgramState *cps;
4018 {
4019     char buf[MSG_SIZ];
4020
4021     if (cps->useUsermove) {
4022       SendToProgram("usermove ", cps);
4023     }
4024     if (cps->useSAN) {
4025       char *space;
4026       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4027         int len = space - parseList[moveNum];
4028         memcpy(buf, parseList[moveNum], len);
4029         buf[len++] = '\n';
4030         buf[len] = NULLCHAR;
4031       } else {
4032         sprintf(buf, "%s\n", parseList[moveNum]);
4033       }
4034       SendToProgram(buf, cps);
4035     } else {
4036       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4037         AlphaRank(moveList[moveNum], 4);
4038         SendToProgram(moveList[moveNum], cps);
4039         AlphaRank(moveList[moveNum], 4); // and back
4040       } else
4041       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4042        * the engine. It would be nice to have a better way to identify castle 
4043        * moves here. */
4044       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4045                                                                          && cps->useOOCastle) {
4046         int fromX = moveList[moveNum][0] - AAA; 
4047         int fromY = moveList[moveNum][1] - ONE;
4048         int toX = moveList[moveNum][2] - AAA; 
4049         int toY = moveList[moveNum][3] - ONE;
4050         if((boards[moveNum][fromY][fromX] == WhiteKing 
4051             && boards[moveNum][toY][toX] == WhiteRook)
4052            || (boards[moveNum][fromY][fromX] == BlackKing 
4053                && boards[moveNum][toY][toX] == BlackRook)) {
4054           if(toX > fromX) SendToProgram("O-O\n", cps);
4055           else SendToProgram("O-O-O\n", cps);
4056         }
4057         else SendToProgram(moveList[moveNum], cps);
4058       }
4059       else SendToProgram(moveList[moveNum], cps);
4060       /* End of additions by Tord */
4061     }
4062
4063     /* [HGM] setting up the opening has brought engine in force mode! */
4064     /*       Send 'go' if we are in a mode where machine should play. */
4065     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4066         (gameMode == TwoMachinesPlay   ||
4067 #ifdef ZIPPY
4068          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4069 #endif
4070          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4071         SendToProgram("go\n", cps);
4072   if (appData.debugMode) {
4073     fprintf(debugFP, "(extra)\n");
4074   }
4075     }
4076     setboardSpoiledMachineBlack = 0;
4077 }
4078
4079 void
4080 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4081      ChessMove moveType;
4082      int fromX, fromY, toX, toY;
4083 {
4084     char user_move[MSG_SIZ];
4085
4086     switch (moveType) {
4087       default:
4088         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4089                 (int)moveType, fromX, fromY, toX, toY);
4090         DisplayError(user_move + strlen("say "), 0);
4091         break;
4092       case WhiteKingSideCastle:
4093       case BlackKingSideCastle:
4094       case WhiteQueenSideCastleWild:
4095       case BlackQueenSideCastleWild:
4096       /* PUSH Fabien */
4097       case WhiteHSideCastleFR:
4098       case BlackHSideCastleFR:
4099       /* POP Fabien */
4100         sprintf(user_move, "o-o\n");
4101         break;
4102       case WhiteQueenSideCastle:
4103       case BlackQueenSideCastle:
4104       case WhiteKingSideCastleWild:
4105       case BlackKingSideCastleWild:
4106       /* PUSH Fabien */
4107       case WhiteASideCastleFR:
4108       case BlackASideCastleFR:
4109       /* POP Fabien */
4110         sprintf(user_move, "o-o-o\n");
4111         break;
4112       case WhitePromotionQueen:
4113       case BlackPromotionQueen:
4114       case WhitePromotionRook:
4115       case BlackPromotionRook:
4116       case WhitePromotionBishop:
4117       case BlackPromotionBishop:
4118       case WhitePromotionKnight:
4119       case BlackPromotionKnight:
4120       case WhitePromotionKing:
4121       case BlackPromotionKing:
4122       case WhitePromotionChancellor:
4123       case BlackPromotionChancellor:
4124       case WhitePromotionArchbishop:
4125       case BlackPromotionArchbishop:
4126         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4127             sprintf(user_move, "%c%c%c%c=%c\n",
4128                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4129                 PieceToChar(WhiteFerz));
4130         else if(gameInfo.variant == VariantGreat)
4131             sprintf(user_move, "%c%c%c%c=%c\n",
4132                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4133                 PieceToChar(WhiteMan));
4134         else
4135             sprintf(user_move, "%c%c%c%c=%c\n",
4136                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4137                 PieceToChar(PromoPiece(moveType)));
4138         break;
4139       case WhiteDrop:
4140       case BlackDrop:
4141         sprintf(user_move, "%c@%c%c\n",
4142                 ToUpper(PieceToChar((ChessSquare) fromX)),
4143                 AAA + toX, ONE + toY);
4144         break;
4145       case NormalMove:
4146       case WhiteCapturesEnPassant:
4147       case BlackCapturesEnPassant:
4148       case IllegalMove:  /* could be a variant we don't quite understand */
4149         sprintf(user_move, "%c%c%c%c\n",
4150                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4151         break;
4152     }
4153     SendToICS(user_move);
4154     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4155         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4156 }
4157
4158 void
4159 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4160      int rf, ff, rt, ft;
4161      char promoChar;
4162      char move[7];
4163 {
4164     if (rf == DROP_RANK) {
4165         sprintf(move, "%c@%c%c\n",
4166                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4167     } else {
4168         if (promoChar == 'x' || promoChar == NULLCHAR) {
4169             sprintf(move, "%c%c%c%c\n",
4170                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4171         } else {
4172             sprintf(move, "%c%c%c%c%c\n",
4173                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4174         }
4175     }
4176 }
4177
4178 void
4179 ProcessICSInitScript(f)
4180      FILE *f;
4181 {
4182     char buf[MSG_SIZ];
4183
4184     while (fgets(buf, MSG_SIZ, f)) {
4185         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4186     }
4187
4188     fclose(f);
4189 }
4190
4191
4192 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4193 void
4194 AlphaRank(char *move, int n)
4195 {
4196 //    char *p = move, c; int x, y;
4197
4198     if (appData.debugMode) {
4199         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4200     }
4201
4202     if(move[1]=='*' && 
4203        move[2]>='0' && move[2]<='9' &&
4204        move[3]>='a' && move[3]<='x'    ) {
4205         move[1] = '@';
4206         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4207         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4208     } else
4209     if(move[0]>='0' && move[0]<='9' &&
4210        move[1]>='a' && move[1]<='x' &&
4211        move[2]>='0' && move[2]<='9' &&
4212        move[3]>='a' && move[3]<='x'    ) {
4213         /* input move, Shogi -> normal */
4214         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4215         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4216         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4217         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4218     } else
4219     if(move[1]=='@' &&
4220        move[3]>='0' && move[3]<='9' &&
4221        move[2]>='a' && move[2]<='x'    ) {
4222         move[1] = '*';
4223         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4224         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4225     } else
4226     if(
4227        move[0]>='a' && move[0]<='x' &&
4228        move[3]>='0' && move[3]<='9' &&
4229        move[2]>='a' && move[2]<='x'    ) {
4230          /* output move, normal -> Shogi */
4231         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4232         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4233         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4234         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4235         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4236     }
4237     if (appData.debugMode) {
4238         fprintf(debugFP, "   out = '%s'\n", move);
4239     }
4240 }
4241
4242 /* Parser for moves from gnuchess, ICS, or user typein box */
4243 Boolean
4244 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4245      char *move;
4246      int moveNum;
4247      ChessMove *moveType;
4248      int *fromX, *fromY, *toX, *toY;
4249      char *promoChar;
4250 {       
4251     if (appData.debugMode) {
4252         fprintf(debugFP, "move to parse: %s\n", move);
4253     }
4254     *moveType = yylexstr(moveNum, move);
4255
4256     switch (*moveType) {
4257       case WhitePromotionChancellor:
4258       case BlackPromotionChancellor:
4259       case WhitePromotionArchbishop:
4260       case BlackPromotionArchbishop:
4261       case WhitePromotionQueen:
4262       case BlackPromotionQueen:
4263       case WhitePromotionRook:
4264       case BlackPromotionRook:
4265       case WhitePromotionBishop:
4266       case BlackPromotionBishop:
4267       case WhitePromotionKnight:
4268       case BlackPromotionKnight:
4269       case WhitePromotionKing:
4270       case BlackPromotionKing:
4271       case NormalMove:
4272       case WhiteCapturesEnPassant:
4273       case BlackCapturesEnPassant:
4274       case WhiteKingSideCastle:
4275       case WhiteQueenSideCastle:
4276       case BlackKingSideCastle:
4277       case BlackQueenSideCastle:
4278       case WhiteKingSideCastleWild:
4279       case WhiteQueenSideCastleWild:
4280       case BlackKingSideCastleWild:
4281       case BlackQueenSideCastleWild:
4282       /* Code added by Tord: */
4283       case WhiteHSideCastleFR:
4284       case WhiteASideCastleFR:
4285       case BlackHSideCastleFR:
4286       case BlackASideCastleFR:
4287       /* End of code added by Tord */
4288       case IllegalMove:         /* bug or odd chess variant */
4289         *fromX = currentMoveString[0] - AAA;
4290         *fromY = currentMoveString[1] - ONE;
4291         *toX = currentMoveString[2] - AAA;
4292         *toY = currentMoveString[3] - ONE;
4293         *promoChar = currentMoveString[4];
4294         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4295             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4296     if (appData.debugMode) {
4297         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4298     }
4299             *fromX = *fromY = *toX = *toY = 0;
4300             return FALSE;
4301         }
4302         if (appData.testLegality) {
4303           return (*moveType != IllegalMove);
4304         } else {
4305           return !(fromX == fromY && toX == toY);
4306         }
4307
4308       case WhiteDrop:
4309       case BlackDrop:
4310         *fromX = *moveType == WhiteDrop ?
4311           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4312           (int) CharToPiece(ToLower(currentMoveString[0]));
4313         *fromY = DROP_RANK;
4314         *toX = currentMoveString[2] - AAA;
4315         *toY = currentMoveString[3] - ONE;
4316         *promoChar = NULLCHAR;
4317         return TRUE;
4318
4319       case AmbiguousMove:
4320       case ImpossibleMove:
4321       case (ChessMove) 0:       /* end of file */
4322       case ElapsedTime:
4323       case Comment:
4324       case PGNTag:
4325       case NAG:
4326       case WhiteWins:
4327       case BlackWins:
4328       case GameIsDrawn:
4329       default:
4330     if (appData.debugMode) {
4331         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4332     }
4333         /* bug? */
4334         *fromX = *fromY = *toX = *toY = 0;
4335         *promoChar = NULLCHAR;
4336         return FALSE;
4337     }
4338 }
4339
4340 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4341 // All positions will have equal probability, but the current method will not provide a unique
4342 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4343 #define DARK 1
4344 #define LITE 2
4345 #define ANY 3
4346
4347 int squaresLeft[4];
4348 int piecesLeft[(int)BlackPawn];
4349 int seed, nrOfShuffles;
4350
4351 void GetPositionNumber()
4352 {       // sets global variable seed
4353         int i;
4354
4355         seed = appData.defaultFrcPosition;
4356         if(seed < 0) { // randomize based on time for negative FRC position numbers
4357                 for(i=0; i<50; i++) seed += random();
4358                 seed = random() ^ random() >> 8 ^ random() << 8;
4359                 if(seed<0) seed = -seed;
4360         }
4361 }
4362
4363 int put(Board board, int pieceType, int rank, int n, int shade)
4364 // put the piece on the (n-1)-th empty squares of the given shade
4365 {
4366         int i;
4367
4368         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4369                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4370                         board[rank][i] = (ChessSquare) pieceType;
4371                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4372                         squaresLeft[ANY]--;
4373                         piecesLeft[pieceType]--; 
4374                         return i;
4375                 }
4376         }
4377         return -1;
4378 }
4379
4380
4381 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4382 // calculate where the next piece goes, (any empty square), and put it there
4383 {
4384         int i;
4385
4386         i = seed % squaresLeft[shade];
4387         nrOfShuffles *= squaresLeft[shade];
4388         seed /= squaresLeft[shade];
4389         put(board, pieceType, rank, i, shade);
4390 }
4391
4392 void AddTwoPieces(Board board, int pieceType, int rank)
4393 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4394 {
4395         int i, n=squaresLeft[ANY], j=n-1, k;
4396
4397         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4398         i = seed % k;  // pick one
4399         nrOfShuffles *= k;
4400         seed /= k;
4401         while(i >= j) i -= j--;
4402         j = n - 1 - j; i += j;
4403         put(board, pieceType, rank, j, ANY);
4404         put(board, pieceType, rank, i, ANY);
4405 }
4406
4407 void SetUpShuffle(Board board, int number)
4408 {
4409         int i, p, first=1;
4410
4411         GetPositionNumber(); nrOfShuffles = 1;
4412
4413         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4414         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4415         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4416
4417         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4418
4419         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4420             p = (int) board[0][i];
4421             if(p < (int) BlackPawn) piecesLeft[p] ++;
4422             board[0][i] = EmptySquare;
4423         }
4424
4425         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4426             // shuffles restricted to allow normal castling put KRR first
4427             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4428                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4429             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4430                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4431             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4432                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4433             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4434                 put(board, WhiteRook, 0, 0, ANY);
4435             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4436         }
4437
4438         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4439             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4440             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4441                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4442                 while(piecesLeft[p] >= 2) {
4443                     AddOnePiece(board, p, 0, LITE);
4444                     AddOnePiece(board, p, 0, DARK);
4445                 }
4446                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4447             }
4448
4449         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4450             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4451             // but we leave King and Rooks for last, to possibly obey FRC restriction
4452             if(p == (int)WhiteRook) continue;
4453             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4454             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4455         }
4456
4457         // now everything is placed, except perhaps King (Unicorn) and Rooks
4458
4459         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4460             // Last King gets castling rights
4461             while(piecesLeft[(int)WhiteUnicorn]) {
4462                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4463                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4464             }
4465
4466             while(piecesLeft[(int)WhiteKing]) {
4467                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4468                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4469             }
4470
4471
4472         } else {
4473             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4474             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4475         }
4476
4477         // Only Rooks can be left; simply place them all
4478         while(piecesLeft[(int)WhiteRook]) {
4479                 i = put(board, WhiteRook, 0, 0, ANY);
4480                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4481                         if(first) {
4482                                 first=0;
4483                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4484                         }
4485                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4486                 }
4487         }
4488         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4489             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4490         }
4491
4492         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4493 }
4494
4495 int SetCharTable( char *table, const char * map )
4496 /* [HGM] moved here from winboard.c because of its general usefulness */
4497 /*       Basically a safe strcpy that uses the last character as King */
4498 {
4499     int result = FALSE; int NrPieces;
4500
4501     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4502                     && NrPieces >= 12 && !(NrPieces&1)) {
4503         int i; /* [HGM] Accept even length from 12 to 34 */
4504
4505         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4506         for( i=0; i<NrPieces/2-1; i++ ) {
4507             table[i] = map[i];
4508             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4509         }
4510         table[(int) WhiteKing]  = map[NrPieces/2-1];
4511         table[(int) BlackKing]  = map[NrPieces-1];
4512
4513         result = TRUE;
4514     }
4515
4516     return result;
4517 }
4518
4519 void Prelude(Board board)
4520 {       // [HGM] superchess: random selection of exo-pieces
4521         int i, j, k; ChessSquare p; 
4522         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4523
4524         GetPositionNumber(); // use FRC position number
4525
4526         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4527             SetCharTable(pieceToChar, appData.pieceToCharTable);
4528             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4529                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4530         }
4531
4532         j = seed%4;                 seed /= 4; 
4533         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4534         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4535         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4536         j = seed%3 + (seed%3 >= j); seed /= 3; 
4537         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4538         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4539         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4540         j = seed%3;                 seed /= 3; 
4541         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4542         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4543         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4544         j = seed%2 + (seed%2 >= j); seed /= 2; 
4545         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4546         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4547         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4548         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4549         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4550         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4551         put(board, exoPieces[0],    0, 0, ANY);
4552         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4553 }
4554
4555 void
4556 InitPosition(redraw)
4557      int redraw;
4558 {
4559     ChessSquare (* pieces)[BOARD_SIZE];
4560     int i, j, pawnRow, overrule,
4561     oldx = gameInfo.boardWidth,
4562     oldy = gameInfo.boardHeight,
4563     oldh = gameInfo.holdingsWidth,
4564     oldv = gameInfo.variant;
4565
4566     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4567
4568     /* [AS] Initialize pv info list [HGM] and game status */
4569     {
4570         for( i=0; i<MAX_MOVES; i++ ) {
4571             pvInfoList[i].depth = 0;
4572             epStatus[i]=EP_NONE;
4573             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4574         }
4575
4576         initialRulePlies = 0; /* 50-move counter start */
4577
4578         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4579         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4580     }
4581
4582     
4583     /* [HGM] logic here is completely changed. In stead of full positions */
4584     /* the initialized data only consist of the two backranks. The switch */
4585     /* selects which one we will use, which is than copied to the Board   */
4586     /* initialPosition, which for the rest is initialized by Pawns and    */
4587     /* empty squares. This initial position is then copied to boards[0],  */
4588     /* possibly after shuffling, so that it remains available.            */
4589
4590     gameInfo.holdingsWidth = 0; /* default board sizes */
4591     gameInfo.boardWidth    = 8;
4592     gameInfo.boardHeight   = 8;
4593     gameInfo.holdingsSize  = 0;
4594     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4595     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4596     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4597
4598     switch (gameInfo.variant) {
4599     case VariantFischeRandom:
4600       shuffleOpenings = TRUE;
4601     default:
4602       pieces = FIDEArray;
4603       break;
4604     case VariantShatranj:
4605       pieces = ShatranjArray;
4606       nrCastlingRights = 0;
4607       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4608       break;
4609     case VariantTwoKings:
4610       pieces = twoKingsArray;
4611       break;
4612     case VariantCapaRandom:
4613       shuffleOpenings = TRUE;
4614     case VariantCapablanca:
4615       pieces = CapablancaArray;
4616       gameInfo.boardWidth = 10;
4617       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4618       break;
4619     case VariantGothic:
4620       pieces = GothicArray;
4621       gameInfo.boardWidth = 10;
4622       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4623       break;
4624     case VariantJanus:
4625       pieces = JanusArray;
4626       gameInfo.boardWidth = 10;
4627       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4628       nrCastlingRights = 6;
4629         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4630         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4631         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4632         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4633         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4634         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4635       break;
4636     case VariantFalcon:
4637       pieces = FalconArray;
4638       gameInfo.boardWidth = 10;
4639       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4640       break;
4641     case VariantXiangqi:
4642       pieces = XiangqiArray;
4643       gameInfo.boardWidth  = 9;
4644       gameInfo.boardHeight = 10;
4645       nrCastlingRights = 0;
4646       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4647       break;
4648     case VariantShogi:
4649       pieces = ShogiArray;
4650       gameInfo.boardWidth  = 9;
4651       gameInfo.boardHeight = 9;
4652       gameInfo.holdingsSize = 7;
4653       nrCastlingRights = 0;
4654       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4655       break;
4656     case VariantCourier:
4657       pieces = CourierArray;
4658       gameInfo.boardWidth  = 12;
4659       nrCastlingRights = 0;
4660       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4661       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4662       break;
4663     case VariantKnightmate:
4664       pieces = KnightmateArray;
4665       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4666       break;
4667     case VariantFairy:
4668       pieces = fairyArray;
4669       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4670       break;
4671     case VariantGreat:
4672       pieces = GreatArray;
4673       gameInfo.boardWidth = 10;
4674       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4675       gameInfo.holdingsSize = 8;
4676       break;
4677     case VariantSuper:
4678       pieces = FIDEArray;
4679       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4680       gameInfo.holdingsSize = 8;
4681       startedFromSetupPosition = TRUE;
4682       break;
4683     case VariantCrazyhouse:
4684     case VariantBughouse:
4685       pieces = FIDEArray;
4686       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4687       gameInfo.holdingsSize = 5;
4688       break;
4689     case VariantWildCastle:
4690       pieces = FIDEArray;
4691       /* !!?shuffle with kings guaranteed to be on d or e file */
4692       shuffleOpenings = 1;
4693       break;
4694     case VariantNoCastle:
4695       pieces = FIDEArray;
4696       nrCastlingRights = 0;
4697       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4698       /* !!?unconstrained back-rank shuffle */
4699       shuffleOpenings = 1;
4700       break;
4701     }
4702
4703     overrule = 0;
4704     if(appData.NrFiles >= 0) {
4705         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4706         gameInfo.boardWidth = appData.NrFiles;
4707     }
4708     if(appData.NrRanks >= 0) {
4709         gameInfo.boardHeight = appData.NrRanks;
4710     }
4711     if(appData.holdingsSize >= 0) {
4712         i = appData.holdingsSize;
4713         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4714         gameInfo.holdingsSize = i;
4715     }
4716     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4717     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4718         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4719
4720     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4721     if(pawnRow < 1) pawnRow = 1;
4722
4723     /* User pieceToChar list overrules defaults */
4724     if(appData.pieceToCharTable != NULL)
4725         SetCharTable(pieceToChar, appData.pieceToCharTable);
4726
4727     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4728
4729         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4730             s = (ChessSquare) 0; /* account holding counts in guard band */
4731         for( i=0; i<BOARD_HEIGHT; i++ )
4732             initialPosition[i][j] = s;
4733
4734         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4735         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4736         initialPosition[pawnRow][j] = WhitePawn;
4737         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4738         if(gameInfo.variant == VariantXiangqi) {
4739             if(j&1) {
4740                 initialPosition[pawnRow][j] = 
4741                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4742                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4743                    initialPosition[2][j] = WhiteCannon;
4744                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4745                 }
4746             }
4747         }
4748         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4749     }
4750     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4751
4752             j=BOARD_LEFT+1;
4753             initialPosition[1][j] = WhiteBishop;
4754             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4755             j=BOARD_RGHT-2;
4756             initialPosition[1][j] = WhiteRook;
4757             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4758     }
4759
4760     if( nrCastlingRights == -1) {
4761         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4762         /*       This sets default castling rights from none to normal corners   */
4763         /* Variants with other castling rights must set them themselves above    */
4764         nrCastlingRights = 6;
4765        
4766         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4767         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4768         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4769         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4770         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4771         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4772      }
4773
4774      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4775      if(gameInfo.variant == VariantGreat) { // promotion commoners
4776         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4777         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4778         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4779         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4780      }
4781   if (appData.debugMode) {
4782     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4783   }
4784     if(shuffleOpenings) {
4785         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4786         startedFromSetupPosition = TRUE;
4787     }
4788     if(startedFromPositionFile) {
4789       /* [HGM] loadPos: use PositionFile for every new game */
4790       CopyBoard(initialPosition, filePosition);
4791       for(i=0; i<nrCastlingRights; i++)
4792           castlingRights[0][i] = initialRights[i] = fileRights[i];
4793       startedFromSetupPosition = TRUE;
4794     }
4795
4796     CopyBoard(boards[0], initialPosition);
4797
4798     if(oldx != gameInfo.boardWidth ||
4799        oldy != gameInfo.boardHeight ||
4800        oldh != gameInfo.holdingsWidth
4801 #ifdef GOTHIC
4802        || oldv == VariantGothic ||        // For licensing popups
4803        gameInfo.variant == VariantGothic
4804 #endif
4805 #ifdef FALCON
4806        || oldv == VariantFalcon ||
4807        gameInfo.variant == VariantFalcon
4808 #endif
4809                                          )
4810             InitDrawingSizes(-2 ,0);
4811
4812     if (redraw)
4813       DrawPosition(TRUE, boards[currentMove]);
4814 }
4815
4816 void
4817 SendBoard(cps, moveNum)
4818      ChessProgramState *cps;
4819      int moveNum;
4820 {
4821     char message[MSG_SIZ];
4822     
4823     if (cps->useSetboard) {
4824       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4825       sprintf(message, "setboard %s\n", fen);
4826       SendToProgram(message, cps);
4827       free(fen);
4828
4829     } else {
4830       ChessSquare *bp;
4831       int i, j;
4832       /* Kludge to set black to move, avoiding the troublesome and now
4833        * deprecated "black" command.
4834        */
4835       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4836
4837       SendToProgram("edit\n", cps);
4838       SendToProgram("#\n", cps);
4839       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4840         bp = &boards[moveNum][i][BOARD_LEFT];
4841         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4842           if ((int) *bp < (int) BlackPawn) {
4843             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4844                     AAA + j, ONE + i);
4845             if(message[0] == '+' || message[0] == '~') {
4846                 sprintf(message, "%c%c%c+\n",
4847                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4848                         AAA + j, ONE + i);
4849             }
4850             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4851                 message[1] = BOARD_RGHT   - 1 - j + '1';
4852                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4853             }
4854             SendToProgram(message, cps);
4855           }
4856         }
4857       }
4858     
4859       SendToProgram("c\n", cps);
4860       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4861         bp = &boards[moveNum][i][BOARD_LEFT];
4862         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4863           if (((int) *bp != (int) EmptySquare)
4864               && ((int) *bp >= (int) BlackPawn)) {
4865             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4866                     AAA + j, ONE + i);
4867             if(message[0] == '+' || message[0] == '~') {
4868                 sprintf(message, "%c%c%c+\n",
4869                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4870                         AAA + j, ONE + i);
4871             }
4872             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4873                 message[1] = BOARD_RGHT   - 1 - j + '1';
4874                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4875             }
4876             SendToProgram(message, cps);
4877           }
4878         }
4879       }
4880     
4881       SendToProgram(".\n", cps);
4882     }
4883     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4884 }
4885
4886 int
4887 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4888 {
4889     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4890     /* [HGM] add Shogi promotions */
4891     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4892     ChessSquare piece;
4893     ChessMove moveType;
4894     Boolean premove;
4895
4896     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4897     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4898
4899     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4900       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4901         return FALSE;
4902
4903     piece = boards[currentMove][fromY][fromX];
4904     if(gameInfo.variant == VariantShogi) {
4905         promotionZoneSize = 3;
4906         highestPromotingPiece = (int)WhiteFerz;
4907     }
4908
4909     // next weed out all moves that do not touch the promotion zone at all
4910     if((int)piece >= BlackPawn) {
4911         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4912              return FALSE;
4913         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4914     } else {
4915         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4916            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4917     }
4918
4919     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4920
4921     // weed out mandatory Shogi promotions
4922     if(gameInfo.variant == VariantShogi) {
4923         if(piece >= BlackPawn) {
4924             if(toY == 0 && piece == BlackPawn ||
4925                toY == 0 && piece == BlackQueen ||
4926                toY <= 1 && piece == BlackKnight) {
4927                 *promoChoice = '+';
4928                 return FALSE;
4929             }
4930         } else {
4931             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4932                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4933                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4934                 *promoChoice = '+';
4935                 return FALSE;
4936             }
4937         }
4938     }
4939
4940     // weed out obviously illegal Pawn moves
4941     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4942         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4943         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4944         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4945         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4946         // note we are not allowed to test for valid (non-)capture, due to premove
4947     }
4948
4949     // we either have a choice what to promote to, or (in Shogi) whether to promote
4950     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4951         *promoChoice = PieceToChar(BlackFerz);  // no choice
4952         return FALSE;
4953     }
4954     if(appData.alwaysPromoteToQueen) { // predetermined
4955         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
4956              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
4957         else *promoChoice = PieceToChar(BlackQueen);
4958         return FALSE;
4959     }
4960
4961     // suppress promotion popup on illegal moves that are not premoves
4962     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
4963               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
4964     if(appData.testLegality && !premove) {
4965         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
4966                         epStatus[currentMove], castlingRights[currentMove],
4967                         fromY, fromX, toY, toX, NULLCHAR);
4968         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
4969            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
4970             return FALSE;
4971     }
4972
4973     return TRUE;
4974 }
4975
4976 int
4977 InPalace(row, column)
4978      int row, column;
4979 {   /* [HGM] for Xiangqi */
4980     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4981          column < (BOARD_WIDTH + 4)/2 &&
4982          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4983     return FALSE;
4984 }
4985
4986 int
4987 PieceForSquare (x, y)
4988      int x;
4989      int y;
4990 {
4991   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4992      return -1;
4993   else
4994      return boards[currentMove][y][x];
4995 }
4996
4997 int
4998 OKToStartUserMove(x, y)
4999      int x, y;
5000 {
5001     ChessSquare from_piece;
5002     int white_piece;
5003
5004     if (matchMode) return FALSE;
5005     if (gameMode == EditPosition) return TRUE;
5006
5007     if (x >= 0 && y >= 0)
5008       from_piece = boards[currentMove][y][x];
5009     else
5010       from_piece = EmptySquare;
5011
5012     if (from_piece == EmptySquare) return FALSE;
5013
5014     white_piece = (int)from_piece >= (int)WhitePawn &&
5015       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5016
5017     switch (gameMode) {
5018       case PlayFromGameFile:
5019       case AnalyzeFile:
5020       case TwoMachinesPlay:
5021       case EndOfGame:
5022         return FALSE;
5023
5024       case IcsObserving:
5025       case IcsIdle:
5026         return FALSE;
5027
5028       case MachinePlaysWhite:
5029       case IcsPlayingBlack:
5030         if (appData.zippyPlay) return FALSE;
5031         if (white_piece) {
5032             DisplayMoveError(_("You are playing Black"));
5033             return FALSE;
5034         }
5035         break;
5036
5037       case MachinePlaysBlack:
5038       case IcsPlayingWhite:
5039         if (appData.zippyPlay) return FALSE;
5040         if (!white_piece) {
5041             DisplayMoveError(_("You are playing White"));
5042             return FALSE;
5043         }
5044         break;
5045
5046       case EditGame:
5047         if (!white_piece && WhiteOnMove(currentMove)) {
5048             DisplayMoveError(_("It is White's turn"));
5049             return FALSE;
5050         }           
5051         if (white_piece && !WhiteOnMove(currentMove)) {
5052             DisplayMoveError(_("It is Black's turn"));
5053             return FALSE;
5054         }           
5055         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5056             /* Editing correspondence game history */
5057             /* Could disallow this or prompt for confirmation */
5058             cmailOldMove = -1;
5059         }
5060         if (currentMove < forwardMostMove) {
5061             /* Discarding moves */
5062             /* Could prompt for confirmation here,
5063                but I don't think that's such a good idea */
5064             forwardMostMove = currentMove;
5065         }
5066         break;
5067
5068       case BeginningOfGame:
5069         if (appData.icsActive) return FALSE;
5070         if (!appData.noChessProgram) {
5071             if (!white_piece) {
5072                 DisplayMoveError(_("You are playing White"));
5073                 return FALSE;
5074             }
5075         }
5076         break;
5077         
5078       case Training:
5079         if (!white_piece && WhiteOnMove(currentMove)) {
5080             DisplayMoveError(_("It is White's turn"));
5081             return FALSE;
5082         }           
5083         if (white_piece && !WhiteOnMove(currentMove)) {
5084             DisplayMoveError(_("It is Black's turn"));
5085             return FALSE;
5086         }           
5087         break;
5088
5089       default:
5090       case IcsExamining:
5091         break;
5092     }
5093     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5094         && gameMode != AnalyzeFile && gameMode != Training) {
5095         DisplayMoveError(_("Displayed position is not current"));
5096         return FALSE;
5097     }
5098     return TRUE;
5099 }
5100
5101 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5102 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5103 int lastLoadGameUseList = FALSE;
5104 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5105 ChessMove lastLoadGameStart = (ChessMove) 0;
5106
5107 ChessMove
5108 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5109      int fromX, fromY, toX, toY;
5110      int promoChar;
5111      Boolean captureOwn;
5112 {
5113     ChessMove moveType;
5114     ChessSquare pdown, pup;
5115
5116     /* Check if the user is playing in turn.  This is complicated because we
5117        let the user "pick up" a piece before it is his turn.  So the piece he
5118        tried to pick up may have been captured by the time he puts it down!
5119        Therefore we use the color the user is supposed to be playing in this
5120        test, not the color of the piece that is currently on the starting
5121        square---except in EditGame mode, where the user is playing both
5122        sides; fortunately there the capture race can't happen.  (It can
5123        now happen in IcsExamining mode, but that's just too bad.  The user
5124        will get a somewhat confusing message in that case.)
5125        */
5126
5127     switch (gameMode) {
5128       case PlayFromGameFile:
5129       case AnalyzeFile:
5130       case TwoMachinesPlay:
5131       case EndOfGame:
5132       case IcsObserving:
5133       case IcsIdle:
5134         /* We switched into a game mode where moves are not accepted,
5135            perhaps while the mouse button was down. */
5136         return ImpossibleMove;
5137
5138       case MachinePlaysWhite:
5139         /* User is moving for Black */
5140         if (WhiteOnMove(currentMove)) {
5141             DisplayMoveError(_("It is White's turn"));
5142             return ImpossibleMove;
5143         }
5144         break;
5145
5146       case MachinePlaysBlack:
5147         /* User is moving for White */
5148         if (!WhiteOnMove(currentMove)) {
5149             DisplayMoveError(_("It is Black's turn"));
5150             return ImpossibleMove;
5151         }
5152         break;
5153
5154       case EditGame:
5155       case IcsExamining:
5156       case BeginningOfGame:
5157       case AnalyzeMode:
5158       case Training:
5159         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5160             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5161             /* User is moving for Black */
5162             if (WhiteOnMove(currentMove)) {
5163                 DisplayMoveError(_("It is White's turn"));
5164                 return ImpossibleMove;
5165             }
5166         } else {
5167             /* User is moving for White */
5168             if (!WhiteOnMove(currentMove)) {
5169                 DisplayMoveError(_("It is Black's turn"));
5170                 return ImpossibleMove;
5171             }
5172         }
5173         break;
5174
5175       case IcsPlayingBlack:
5176         /* User is moving for Black */
5177         if (WhiteOnMove(currentMove)) {
5178             if (!appData.premove) {
5179                 DisplayMoveError(_("It is White's turn"));
5180             } else if (toX >= 0 && toY >= 0) {
5181                 premoveToX = toX;
5182                 premoveToY = toY;
5183                 premoveFromX = fromX;
5184                 premoveFromY = fromY;
5185                 premovePromoChar = promoChar;
5186                 gotPremove = 1;
5187                 if (appData.debugMode) 
5188                     fprintf(debugFP, "Got premove: fromX %d,"
5189                             "fromY %d, toX %d, toY %d\n",
5190                             fromX, fromY, toX, toY);
5191             }
5192             return ImpossibleMove;
5193         }
5194         break;
5195
5196       case IcsPlayingWhite:
5197         /* User is moving for White */
5198         if (!WhiteOnMove(currentMove)) {
5199             if (!appData.premove) {
5200                 DisplayMoveError(_("It is Black's turn"));
5201             } else if (toX >= 0 && toY >= 0) {
5202                 premoveToX = toX;
5203                 premoveToY = toY;
5204                 premoveFromX = fromX;
5205                 premoveFromY = fromY;
5206                 premovePromoChar = promoChar;
5207                 gotPremove = 1;
5208                 if (appData.debugMode) 
5209                     fprintf(debugFP, "Got premove: fromX %d,"
5210                             "fromY %d, toX %d, toY %d\n",
5211                             fromX, fromY, toX, toY);
5212             }
5213             return ImpossibleMove;
5214         }
5215         break;
5216
5217       default:
5218         break;
5219
5220       case EditPosition:
5221         /* EditPosition, empty square, or different color piece;
5222            click-click move is possible */
5223         if (toX == -2 || toY == -2) {
5224             boards[0][fromY][fromX] = EmptySquare;
5225             return AmbiguousMove;
5226         } else if (toX >= 0 && toY >= 0) {
5227             boards[0][toY][toX] = boards[0][fromY][fromX];
5228             boards[0][fromY][fromX] = EmptySquare;
5229             return AmbiguousMove;
5230         }
5231         return ImpossibleMove;
5232     }
5233
5234     pdown = boards[currentMove][fromY][fromX];
5235     pup = boards[currentMove][toY][toX];
5236
5237     /* [HGM] If move started in holdings, it means a drop */
5238     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5239          if( pup != EmptySquare ) return ImpossibleMove;
5240          if(appData.testLegality) {
5241              /* it would be more logical if LegalityTest() also figured out
5242               * which drops are legal. For now we forbid pawns on back rank.
5243               * Shogi is on its own here...
5244               */
5245              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5246                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5247                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5248          }
5249          return WhiteDrop; /* Not needed to specify white or black yet */
5250     }
5251
5252     userOfferedDraw = FALSE;
5253         
5254     /* [HGM] always test for legality, to get promotion info */
5255     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5256                           epStatus[currentMove], castlingRights[currentMove],
5257                                          fromY, fromX, toY, toX, promoChar);
5258     /* [HGM] but possibly ignore an IllegalMove result */
5259     if (appData.testLegality) {
5260         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5261             DisplayMoveError(_("Illegal move"));
5262             return ImpossibleMove;
5263         }
5264     }
5265 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5266     return moveType;
5267     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5268        function is made into one that returns an OK move type if FinishMove
5269        should be called. This to give the calling driver routine the
5270        opportunity to finish the userMove input with a promotion popup,
5271        without bothering the user with this for invalid or illegal moves */
5272
5273 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5274 }
5275
5276 /* Common tail of UserMoveEvent and DropMenuEvent */
5277 int
5278 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5279      ChessMove moveType;
5280      int fromX, fromY, toX, toY;
5281      /*char*/int promoChar;
5282 {
5283     char *bookHit = 0;
5284 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5285     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5286         // [HGM] superchess: suppress promotions to non-available piece
5287         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5288         if(WhiteOnMove(currentMove)) {
5289             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5290         } else {
5291             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5292         }
5293     }
5294
5295     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5296        move type in caller when we know the move is a legal promotion */
5297     if(moveType == NormalMove && promoChar)
5298         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5299 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5300     /* [HGM] convert drag-and-drop piece drops to standard form */
5301     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5302          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5303            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5304                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5305 //         fromX = boards[currentMove][fromY][fromX];
5306            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5307            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5308            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5309            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5310          fromY = DROP_RANK;
5311     }
5312
5313     /* [HGM] <popupFix> The following if has been moved here from
5314        UserMoveEvent(). Because it seemed to belon here (why not allow
5315        piece drops in training games?), and because it can only be
5316        performed after it is known to what we promote. */
5317     if (gameMode == Training) {
5318       /* compare the move played on the board to the next move in the
5319        * game. If they match, display the move and the opponent's response. 
5320        * If they don't match, display an error message.
5321        */
5322       int saveAnimate;
5323       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5324       CopyBoard(testBoard, boards[currentMove]);
5325       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5326
5327       if (CompareBoards(testBoard, boards[currentMove+1])) {
5328         ForwardInner(currentMove+1);
5329
5330         /* Autoplay the opponent's response.
5331          * if appData.animate was TRUE when Training mode was entered,
5332          * the response will be animated.
5333          */
5334         saveAnimate = appData.animate;
5335         appData.animate = animateTraining;
5336         ForwardInner(currentMove+1);
5337         appData.animate = saveAnimate;
5338
5339         /* check for the end of the game */
5340         if (currentMove >= forwardMostMove) {
5341           gameMode = PlayFromGameFile;
5342           ModeHighlight();
5343           SetTrainingModeOff();
5344           DisplayInformation(_("End of game"));
5345         }
5346       } else {
5347         DisplayError(_("Incorrect move"), 0);
5348       }
5349       return 1;
5350     }
5351
5352   /* Ok, now we know that the move is good, so we can kill
5353      the previous line in Analysis Mode */
5354   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5355     forwardMostMove = currentMove;
5356   }
5357
5358   /* If we need the chess program but it's dead, restart it */
5359   ResurrectChessProgram();
5360
5361   /* A user move restarts a paused game*/
5362   if (pausing)
5363     PauseEvent();
5364
5365   thinkOutput[0] = NULLCHAR;
5366
5367   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5368
5369   if (gameMode == BeginningOfGame) {
5370     if (appData.noChessProgram) {
5371       gameMode = EditGame;
5372       SetGameInfo();
5373     } else {
5374       char buf[MSG_SIZ];
5375       gameMode = MachinePlaysBlack;
5376       StartClocks();
5377       SetGameInfo();
5378       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5379       DisplayTitle(buf);
5380       if (first.sendName) {
5381         sprintf(buf, "name %s\n", gameInfo.white);
5382         SendToProgram(buf, &first);
5383       }
5384       StartClocks();
5385     }
5386     ModeHighlight();
5387   }
5388 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5389   /* Relay move to ICS or chess engine */
5390   if (appData.icsActive) {
5391     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5392         gameMode == IcsExamining) {
5393       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5394       ics_user_moved = 1;
5395     }
5396   } else {
5397     if (first.sendTime && (gameMode == BeginningOfGame ||
5398                            gameMode == MachinePlaysWhite ||
5399                            gameMode == MachinePlaysBlack)) {
5400       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5401     }
5402     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5403          // [HGM] book: if program might be playing, let it use book
5404         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5405         first.maybeThinking = TRUE;
5406     } else SendMoveToProgram(forwardMostMove-1, &first);
5407     if (currentMove == cmailOldMove + 1) {
5408       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5409     }
5410   }
5411
5412   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5413
5414   switch (gameMode) {
5415   case EditGame:
5416     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5417                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5418     case MT_NONE:
5419     case MT_CHECK:
5420       break;
5421     case MT_CHECKMATE:
5422     case MT_STAINMATE:
5423       if (WhiteOnMove(currentMove)) {
5424         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5425       } else {
5426         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5427       }
5428       break;
5429     case MT_STALEMATE:
5430       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5431       break;
5432     }
5433     break;
5434     
5435   case MachinePlaysBlack:
5436   case MachinePlaysWhite:
5437     /* disable certain menu options while machine is thinking */
5438     SetMachineThinkingEnables();
5439     break;
5440
5441   default:
5442     break;
5443   }
5444
5445   if(bookHit) { // [HGM] book: simulate book reply
5446         static char bookMove[MSG_SIZ]; // a bit generous?
5447
5448         programStats.nodes = programStats.depth = programStats.time = 
5449         programStats.score = programStats.got_only_move = 0;
5450         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5451
5452         strcpy(bookMove, "move ");
5453         strcat(bookMove, bookHit);
5454         HandleMachineMove(bookMove, &first);
5455   }
5456   return 1;
5457 }
5458
5459 void
5460 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5461      int fromX, fromY, toX, toY;
5462      int promoChar;
5463 {
5464     /* [HGM] This routine was added to allow calling of its two logical
5465        parts from other modules in the old way. Before, UserMoveEvent()
5466        automatically called FinishMove() if the move was OK, and returned
5467        otherwise. I separated the two, in order to make it possible to
5468        slip a promotion popup in between. But that it always needs two
5469        calls, to the first part, (now called UserMoveTest() ), and to
5470        FinishMove if the first part succeeded. Calls that do not need
5471        to do anything in between, can call this routine the old way. 
5472     */
5473     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5474 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5475     if(moveType == AmbiguousMove)
5476         DrawPosition(FALSE, boards[currentMove]);
5477     else if(moveType != ImpossibleMove && moveType != Comment)
5478         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5479 }
5480
5481 void LeftClick(ClickType clickType, int xPix, int yPix)
5482 {
5483     int x, y;
5484     Boolean saveAnimate;
5485     static int second = 0, promotionChoice = 0;
5486     char promoChoice = NULLCHAR;
5487
5488     if (clickType == Press) ErrorPopDown();
5489
5490     x = EventToSquare(xPix, BOARD_WIDTH);
5491     y = EventToSquare(yPix, BOARD_HEIGHT);
5492     if (!flipView && y >= 0) {
5493         y = BOARD_HEIGHT - 1 - y;
5494     }
5495     if (flipView && x >= 0) {
5496         x = BOARD_WIDTH - 1 - x;
5497     }
5498
5499     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5500         if(clickType == Release) return; // ignore upclick of click-click destination
5501         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5502         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5503         if(gameInfo.holdingsWidth && 
5504                 (WhiteOnMove(currentMove) 
5505                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5506                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5507             // click in right holdings, for determining promotion piece
5508             ChessSquare p = boards[currentMove][y][x];
5509             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5510             if(p != EmptySquare) {
5511                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5512                 fromX = fromY = -1;
5513                 return;
5514             }
5515         }
5516         DrawPosition(FALSE, boards[currentMove]);
5517         return;
5518     }
5519
5520     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5521     if(clickType == Press
5522             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5523               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5524               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5525         return;
5526
5527     if (fromX == -1) {
5528         if (clickType == Press) {
5529             /* First square */
5530             if (OKToStartUserMove(x, y)) {
5531                 fromX = x;
5532                 fromY = y;
5533                 second = 0;
5534                 DragPieceBegin(xPix, yPix);
5535                 if (appData.highlightDragging) {
5536                     SetHighlights(x, y, -1, -1);
5537                 }
5538             }
5539         }
5540         return;
5541     }
5542
5543     /* fromX != -1 */
5544     if (clickType == Press && gameMode != EditPosition) {
5545         ChessSquare fromP;
5546         ChessSquare toP;
5547         int frc;
5548
5549         // ignore off-board to clicks
5550         if(y < 0 || x < 0) return;
5551
5552         /* Check if clicking again on the same color piece */
5553         fromP = boards[currentMove][fromY][fromX];
5554         toP = boards[currentMove][y][x];
5555         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5556         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5557              WhitePawn <= toP && toP <= WhiteKing &&
5558              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5559              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5560             (BlackPawn <= fromP && fromP <= BlackKing && 
5561              BlackPawn <= toP && toP <= BlackKing &&
5562              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5563              !(fromP == BlackKing && toP == BlackRook && frc))) {
5564             /* Clicked again on same color piece -- changed his mind */
5565             second = (x == fromX && y == fromY);
5566             if (appData.highlightDragging) {
5567                 SetHighlights(x, y, -1, -1);
5568             } else {
5569                 ClearHighlights();
5570             }
5571             if (OKToStartUserMove(x, y)) {
5572                 fromX = x;
5573                 fromY = y;
5574                 DragPieceBegin(xPix, yPix);
5575             }
5576             return;
5577         }
5578         // ignore to-clicks in holdings
5579         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5580     }
5581
5582     if (clickType == Release && (x == fromX && y == fromY ||
5583         x < BOARD_LEFT || x >= BOARD_RGHT)) {
5584
5585         // treat drags into holding as click on start square
5586         x = fromX; y = fromY;
5587
5588         DragPieceEnd(xPix, yPix);
5589         if (appData.animateDragging) {
5590             /* Undo animation damage if any */
5591             DrawPosition(FALSE, NULL);
5592         }
5593         if (second) {
5594             /* Second up/down in same square; just abort move */
5595             second = 0;
5596             fromX = fromY = -1;
5597             ClearHighlights();
5598             gotPremove = 0;
5599             ClearPremoveHighlights();
5600         } else {
5601             /* First upclick in same square; start click-click mode */
5602             SetHighlights(x, y, -1, -1);
5603         }
5604         return;
5605     }
5606
5607     /* we now have a different from- and to-square */
5608     /* Completed move */
5609     toX = x;
5610     toY = y;
5611     saveAnimate = appData.animate;
5612     if (clickType == Press) {
5613         /* Finish clickclick move */
5614         if (appData.animate || appData.highlightLastMove) {
5615             SetHighlights(fromX, fromY, toX, toY);
5616         } else {
5617             ClearHighlights();
5618         }
5619     } else {
5620         /* Finish drag move */
5621         if (appData.highlightLastMove) {
5622             SetHighlights(fromX, fromY, toX, toY);
5623         } else {
5624             ClearHighlights();
5625         }
5626         DragPieceEnd(xPix, yPix);
5627         /* Don't animate move and drag both */
5628         appData.animate = FALSE;
5629     }
5630     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5631         SetHighlights(fromX, fromY, toX, toY);
5632         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5633             // [HGM] super: promotion to captured piece selected from holdings
5634             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5635             promotionChoice = TRUE;
5636             // kludge follows to temporarily execute move on display, without promoting yet
5637             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5638             boards[currentMove][toY][toX] = p;
5639             DrawPosition(FALSE, boards[currentMove]);
5640             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5641             boards[currentMove][toY][toX] = q;
5642             DisplayMessage("Click in holdings to choose piece", "");
5643             return;
5644         }
5645         PromotionPopUp();
5646     } else {
5647         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5648         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5649         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5650         fromX = fromY = -1;
5651     }
5652     appData.animate = saveAnimate;
5653     if (appData.animate || appData.animateDragging) {
5654         /* Undo animation damage if needed */
5655         DrawPosition(FALSE, NULL);
5656     }
5657 }
5658
5659 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5660 {
5661 //    char * hint = lastHint;
5662     FrontEndProgramStats stats;
5663
5664     stats.which = cps == &first ? 0 : 1;
5665     stats.depth = cpstats->depth;
5666     stats.nodes = cpstats->nodes;
5667     stats.score = cpstats->score;
5668     stats.time = cpstats->time;
5669     stats.pv = cpstats->movelist;
5670     stats.hint = lastHint;
5671     stats.an_move_index = 0;
5672     stats.an_move_count = 0;
5673
5674     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5675         stats.hint = cpstats->move_name;
5676         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5677         stats.an_move_count = cpstats->nr_moves;
5678     }
5679
5680     SetProgramStats( &stats );
5681 }
5682
5683 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5684 {   // [HGM] book: this routine intercepts moves to simulate book replies
5685     char *bookHit = NULL;
5686
5687     //first determine if the incoming move brings opponent into his book
5688     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5689         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5690     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5691     if(bookHit != NULL && !cps->bookSuspend) {
5692         // make sure opponent is not going to reply after receiving move to book position
5693         SendToProgram("force\n", cps);
5694         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5695     }
5696     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5697     // now arrange restart after book miss
5698     if(bookHit) {
5699         // after a book hit we never send 'go', and the code after the call to this routine
5700         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5701         char buf[MSG_SIZ];
5702         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5703         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5704         SendToProgram(buf, cps);
5705         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5706     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5707         SendToProgram("go\n", cps);
5708         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5709     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5710         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5711             SendToProgram("go\n", cps); 
5712         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5713     }
5714     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5715 }
5716
5717 char *savedMessage;
5718 ChessProgramState *savedState;
5719 void DeferredBookMove(void)
5720 {
5721         if(savedState->lastPing != savedState->lastPong)
5722                     ScheduleDelayedEvent(DeferredBookMove, 10);
5723         else
5724         HandleMachineMove(savedMessage, savedState);
5725 }
5726
5727 void
5728 HandleMachineMove(message, cps)
5729      char *message;
5730      ChessProgramState *cps;
5731 {
5732     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5733     char realname[MSG_SIZ];
5734     int fromX, fromY, toX, toY;
5735     ChessMove moveType;
5736     char promoChar;
5737     char *p;
5738     int machineWhite;
5739     char *bookHit;
5740
5741 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5742     /*
5743      * Kludge to ignore BEL characters
5744      */
5745     while (*message == '\007') message++;
5746
5747     /*
5748      * [HGM] engine debug message: ignore lines starting with '#' character
5749      */
5750     if(cps->debug && *message == '#') return;
5751
5752     /*
5753      * Look for book output
5754      */
5755     if (cps == &first && bookRequested) {
5756         if (message[0] == '\t' || message[0] == ' ') {
5757             /* Part of the book output is here; append it */
5758             strcat(bookOutput, message);
5759             strcat(bookOutput, "  \n");
5760             return;
5761         } else if (bookOutput[0] != NULLCHAR) {
5762             /* All of book output has arrived; display it */
5763             char *p = bookOutput;
5764             while (*p != NULLCHAR) {
5765                 if (*p == '\t') *p = ' ';
5766                 p++;
5767             }
5768             DisplayInformation(bookOutput);
5769             bookRequested = FALSE;
5770             /* Fall through to parse the current output */
5771         }
5772     }
5773
5774     /*
5775      * Look for machine move.
5776      */
5777     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5778         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5779     {
5780         /* This method is only useful on engines that support ping */
5781         if (cps->lastPing != cps->lastPong) {
5782           if (gameMode == BeginningOfGame) {
5783             /* Extra move from before last new; ignore */
5784             if (appData.debugMode) {
5785                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5786             }
5787           } else {
5788             if (appData.debugMode) {
5789                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5790                         cps->which, gameMode);
5791             }
5792
5793             SendToProgram("undo\n", cps);
5794           }
5795           return;
5796         }
5797
5798         switch (gameMode) {
5799           case BeginningOfGame:
5800             /* Extra move from before last reset; ignore */
5801             if (appData.debugMode) {
5802                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5803             }
5804             return;
5805
5806           case EndOfGame:
5807           case IcsIdle:
5808           default:
5809             /* Extra move after we tried to stop.  The mode test is
5810                not a reliable way of detecting this problem, but it's
5811                the best we can do on engines that don't support ping.
5812             */
5813             if (appData.debugMode) {
5814                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5815                         cps->which, gameMode);
5816             }
5817             SendToProgram("undo\n", cps);
5818             return;
5819
5820           case MachinePlaysWhite:
5821           case IcsPlayingWhite:
5822             machineWhite = TRUE;
5823             break;
5824
5825           case MachinePlaysBlack:
5826           case IcsPlayingBlack:
5827             machineWhite = FALSE;
5828             break;
5829
5830           case TwoMachinesPlay:
5831             machineWhite = (cps->twoMachinesColor[0] == 'w');
5832             break;
5833         }
5834         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5835             if (appData.debugMode) {
5836                 fprintf(debugFP,
5837                         "Ignoring move out of turn by %s, gameMode %d"
5838                         ", forwardMost %d\n",
5839                         cps->which, gameMode, forwardMostMove);
5840             }
5841             return;
5842         }
5843
5844     if (appData.debugMode) { int f = forwardMostMove;
5845         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5846                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5847     }
5848         if(cps->alphaRank) AlphaRank(machineMove, 4);
5849         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5850                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5851             /* Machine move could not be parsed; ignore it. */
5852             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5853                     machineMove, cps->which);
5854             DisplayError(buf1, 0);
5855             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5856                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5857             if (gameMode == TwoMachinesPlay) {
5858               GameEnds(machineWhite ? BlackWins : WhiteWins,
5859                        buf1, GE_XBOARD);
5860             }
5861             return;
5862         }
5863
5864         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5865         /* So we have to redo legality test with true e.p. status here,  */
5866         /* to make sure an illegal e.p. capture does not slip through,   */
5867         /* to cause a forfeit on a justified illegal-move complaint      */
5868         /* of the opponent.                                              */
5869         if( gameMode==TwoMachinesPlay && appData.testLegality
5870             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5871                                                               ) {
5872            ChessMove moveType;
5873            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5874                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5875                              fromY, fromX, toY, toX, promoChar);
5876             if (appData.debugMode) {
5877                 int i;
5878                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5879                     castlingRights[forwardMostMove][i], castlingRank[i]);
5880                 fprintf(debugFP, "castling rights\n");
5881             }
5882             if(moveType == IllegalMove) {
5883                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5884                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5885                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5886                            buf1, GE_XBOARD);
5887                 return;
5888            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5889            /* [HGM] Kludge to handle engines that send FRC-style castling
5890               when they shouldn't (like TSCP-Gothic) */
5891            switch(moveType) {
5892              case WhiteASideCastleFR:
5893              case BlackASideCastleFR:
5894                toX+=2;
5895                currentMoveString[2]++;
5896                break;
5897              case WhiteHSideCastleFR:
5898              case BlackHSideCastleFR:
5899                toX--;
5900                currentMoveString[2]--;
5901                break;
5902              default: ; // nothing to do, but suppresses warning of pedantic compilers
5903            }
5904         }
5905         hintRequested = FALSE;
5906         lastHint[0] = NULLCHAR;
5907         bookRequested = FALSE;
5908         /* Program may be pondering now */
5909         cps->maybeThinking = TRUE;
5910         if (cps->sendTime == 2) cps->sendTime = 1;
5911         if (cps->offeredDraw) cps->offeredDraw--;
5912
5913 #if ZIPPY
5914         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5915             first.initDone) {
5916           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5917           ics_user_moved = 1;
5918           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5919                 char buf[3*MSG_SIZ];
5920
5921                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5922                         programStats.score / 100.,
5923                         programStats.depth,
5924                         programStats.time / 100.,
5925                         (unsigned int)programStats.nodes,
5926                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5927                         programStats.movelist);
5928                 SendToICS(buf);
5929 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5930           }
5931         }
5932 #endif
5933         /* currentMoveString is set as a side-effect of ParseOneMove */
5934         strcpy(machineMove, currentMoveString);
5935         strcat(machineMove, "\n");
5936         strcpy(moveList[forwardMostMove], machineMove);
5937
5938         /* [AS] Save move info and clear stats for next move */
5939         pvInfoList[ forwardMostMove ].score = programStats.score;
5940         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5941         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5942         ClearProgramStats();
5943         thinkOutput[0] = NULLCHAR;
5944         hiddenThinkOutputState = 0;
5945
5946         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5947
5948         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5949         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5950             int count = 0;
5951
5952             while( count < adjudicateLossPlies ) {
5953                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5954
5955                 if( count & 1 ) {
5956                     score = -score; /* Flip score for winning side */
5957                 }
5958
5959                 if( score > adjudicateLossThreshold ) {
5960                     break;
5961                 }
5962
5963                 count++;
5964             }
5965
5966             if( count >= adjudicateLossPlies ) {
5967                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5968
5969                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5970                     "Xboard adjudication", 
5971                     GE_XBOARD );
5972
5973                 return;
5974             }
5975         }
5976
5977         if( gameMode == TwoMachinesPlay ) {
5978           // [HGM] some adjudications useful with buggy engines
5979             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5980           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5981
5982
5983             if( appData.testLegality )
5984             {   /* [HGM] Some more adjudications for obstinate engines */
5985                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5986                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5987                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5988                 static int moveCount = 6;
5989                 ChessMove result;
5990                 char *reason = NULL;
5991
5992                 /* Count what is on board. */
5993                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5994                 {   ChessSquare p = boards[forwardMostMove][i][j];
5995                     int m=i;
5996
5997                     switch((int) p)
5998                     {   /* count B,N,R and other of each side */
5999                         case WhiteKing:
6000                         case BlackKing:
6001                              NrK++; break; // [HGM] atomic: count Kings
6002                         case WhiteKnight:
6003                              NrWN++; break;
6004                         case WhiteBishop:
6005                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6006                              bishopsColor |= 1 << ((i^j)&1);
6007                              NrWB++; break;
6008                         case BlackKnight:
6009                              NrBN++; break;
6010                         case BlackBishop:
6011                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6012                              bishopsColor |= 1 << ((i^j)&1);
6013                              NrBB++; break;
6014                         case WhiteRook:
6015                              NrWR++; break;
6016                         case BlackRook:
6017                              NrBR++; break;
6018                         case WhiteQueen:
6019                              NrWQ++; break;
6020                         case BlackQueen:
6021                              NrBQ++; break;
6022                         case EmptySquare: 
6023                              break;
6024                         case BlackPawn:
6025                              m = 7-i;
6026                         case WhitePawn:
6027                              PawnAdvance += m; NrPawns++;
6028                     }
6029                     NrPieces += (p != EmptySquare);
6030                     NrW += ((int)p < (int)BlackPawn);
6031                     if(gameInfo.variant == VariantXiangqi && 
6032                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6033                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6034                         NrW -= ((int)p < (int)BlackPawn);
6035                     }
6036                 }
6037
6038                 /* Some material-based adjudications that have to be made before stalemate test */
6039                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6040                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6041                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6042                      if(appData.checkMates) {
6043                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6044                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6045                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6046                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6047                          return;
6048                      }
6049                 }
6050
6051                 /* Bare King in Shatranj (loses) or Losers (wins) */
6052                 if( NrW == 1 || NrPieces - NrW == 1) {
6053                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6054                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6055                      if(appData.checkMates) {
6056                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6057                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6058                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6059                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6060                          return;
6061                      }
6062                   } else
6063                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6064                   {    /* bare King */
6065                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6066                         if(appData.checkMates) {
6067                             /* but only adjudicate if adjudication enabled */
6068                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6069                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6070                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6071                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6072                             return;
6073                         }
6074                   }
6075                 } else bare = 1;
6076
6077
6078             // don't wait for engine to announce game end if we can judge ourselves
6079             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6080                                        castlingRights[forwardMostMove]) ) {
6081               case MT_CHECK:
6082                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6083                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6084                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6085                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6086                             checkCnt++;
6087                         if(checkCnt >= 2) {
6088                             reason = "Xboard adjudication: 3rd check";
6089                             epStatus[forwardMostMove] = EP_CHECKMATE;
6090                             break;
6091                         }
6092                     }
6093                 }
6094               case MT_NONE:
6095               default:
6096                 break;
6097               case MT_STALEMATE:
6098               case MT_STAINMATE:
6099                 reason = "Xboard adjudication: Stalemate";
6100                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6101                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6102                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6103                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6104                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6105                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6106                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6107                                                                         EP_CHECKMATE : EP_WINS);
6108                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6109                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6110                 }
6111                 break;
6112               case MT_CHECKMATE:
6113                 reason = "Xboard adjudication: Checkmate";
6114                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6115                 break;
6116             }
6117
6118                 switch(i = epStatus[forwardMostMove]) {
6119                     case EP_STALEMATE:
6120                         result = GameIsDrawn; break;
6121                     case EP_CHECKMATE:
6122                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6123                     case EP_WINS:
6124                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6125                     default:
6126                         result = (ChessMove) 0;
6127                 }
6128                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6129                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6130                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6131                     GameEnds( result, reason, GE_XBOARD );
6132                     return;
6133                 }
6134
6135                 /* Next absolutely insufficient mating material. */
6136                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6137                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6138                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6139                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6140                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6141
6142                      /* always flag draws, for judging claims */
6143                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6144
6145                      if(appData.materialDraws) {
6146                          /* but only adjudicate them if adjudication enabled */
6147                          SendToProgram("force\n", cps->other); // suppress reply
6148                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6149                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6150                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6151                          return;
6152                      }
6153                 }
6154
6155                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6156                 if(NrPieces == 4 && 
6157                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6158                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6159                    || NrWN==2 || NrBN==2     /* KNNK */
6160                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6161                   ) ) {
6162                      if(--moveCount < 0 && appData.trivialDraws)
6163                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6164                           SendToProgram("force\n", cps->other); // suppress reply
6165                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6166                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6167                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6168                           return;
6169                      }
6170                 } else moveCount = 6;
6171             }
6172           }
6173           
6174           if (appData.debugMode) { int i;
6175             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6176                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6177                     appData.drawRepeats);
6178             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6179               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6180             
6181           }
6182
6183                 /* Check for rep-draws */
6184                 count = 0;
6185                 for(k = forwardMostMove-2;
6186                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6187                         epStatus[k] < EP_UNKNOWN &&
6188                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6189                     k-=2)
6190                 {   int rights=0;
6191                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6192                         /* compare castling rights */
6193                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6194                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6195                                 rights++; /* King lost rights, while rook still had them */
6196                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6197                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6198                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6199                                    rights++; /* but at least one rook lost them */
6200                         }
6201                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6202                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6203                                 rights++; 
6204                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6205                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6206                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6207                                    rights++;
6208                         }
6209                         if( rights == 0 && ++count > appData.drawRepeats-2
6210                             && appData.drawRepeats > 1) {
6211                              /* adjudicate after user-specified nr of repeats */
6212                              SendToProgram("force\n", cps->other); // suppress reply
6213                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6214                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6215                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6216                                 // [HGM] xiangqi: check for forbidden perpetuals
6217                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6218                                 for(m=forwardMostMove; m>k; m-=2) {
6219                                     if(MateTest(boards[m], PosFlags(m), 
6220                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6221                                         ourPerpetual = 0; // the current mover did not always check
6222                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6223                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6224                                         hisPerpetual = 0; // the opponent did not always check
6225                                 }
6226                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6227                                                                         ourPerpetual, hisPerpetual);
6228                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6229                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6230                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6231                                     return;
6232                                 }
6233                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6234                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6235                                 // Now check for perpetual chases
6236                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6237                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6238                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6239                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6240                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6241                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6242                                         return;
6243                                     }
6244                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6245                                         break; // Abort repetition-checking loop.
6246                                 }
6247                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6248                              }
6249                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6250                              return;
6251                         }
6252                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6253                              epStatus[forwardMostMove] = EP_REP_DRAW;
6254                     }
6255                 }
6256
6257                 /* Now we test for 50-move draws. Determine ply count */
6258                 count = forwardMostMove;
6259                 /* look for last irreversble move */
6260                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6261                     count--;
6262                 /* if we hit starting position, add initial plies */
6263                 if( count == backwardMostMove )
6264                     count -= initialRulePlies;
6265                 count = forwardMostMove - count; 
6266                 if( count >= 100)
6267                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6268                          /* this is used to judge if draw claims are legal */
6269                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6270                          SendToProgram("force\n", cps->other); // suppress reply
6271                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6272                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6273                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6274                          return;
6275                 }
6276
6277                 /* if draw offer is pending, treat it as a draw claim
6278                  * when draw condition present, to allow engines a way to
6279                  * claim draws before making their move to avoid a race
6280                  * condition occurring after their move
6281                  */
6282                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6283                          char *p = NULL;
6284                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6285                              p = "Draw claim: 50-move rule";
6286                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6287                              p = "Draw claim: 3-fold repetition";
6288                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6289                              p = "Draw claim: insufficient mating material";
6290                          if( p != NULL ) {
6291                              SendToProgram("force\n", cps->other); // suppress reply
6292                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6293                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6294                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6295                              return;
6296                          }
6297                 }
6298
6299
6300                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6301                     SendToProgram("force\n", cps->other); // suppress reply
6302                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6303                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6304
6305                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6306
6307                     return;
6308                 }
6309         }
6310
6311         bookHit = NULL;
6312         if (gameMode == TwoMachinesPlay) {
6313             /* [HGM] relaying draw offers moved to after reception of move */
6314             /* and interpreting offer as claim if it brings draw condition */
6315             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6316                 SendToProgram("draw\n", cps->other);
6317             }
6318             if (cps->other->sendTime) {
6319                 SendTimeRemaining(cps->other,
6320                                   cps->other->twoMachinesColor[0] == 'w');
6321             }
6322             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6323             if (firstMove && !bookHit) {
6324                 firstMove = FALSE;
6325                 if (cps->other->useColors) {
6326                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6327                 }
6328                 SendToProgram("go\n", cps->other);
6329             }
6330             cps->other->maybeThinking = TRUE;
6331         }
6332
6333         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6334         
6335         if (!pausing && appData.ringBellAfterMoves) {
6336             RingBell();
6337         }
6338
6339         /* 
6340          * Reenable menu items that were disabled while
6341          * machine was thinking
6342          */
6343         if (gameMode != TwoMachinesPlay)
6344             SetUserThinkingEnables();
6345
6346         // [HGM] book: after book hit opponent has received move and is now in force mode
6347         // force the book reply into it, and then fake that it outputted this move by jumping
6348         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6349         if(bookHit) {
6350                 static char bookMove[MSG_SIZ]; // a bit generous?
6351
6352                 strcpy(bookMove, "move ");
6353                 strcat(bookMove, bookHit);
6354                 message = bookMove;
6355                 cps = cps->other;
6356                 programStats.nodes = programStats.depth = programStats.time = 
6357                 programStats.score = programStats.got_only_move = 0;
6358                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6359
6360                 if(cps->lastPing != cps->lastPong) {
6361                     savedMessage = message; // args for deferred call
6362                     savedState = cps;
6363                     ScheduleDelayedEvent(DeferredBookMove, 10);
6364                     return;
6365                 }
6366                 goto FakeBookMove;
6367         }
6368
6369         return;
6370     }
6371
6372     /* Set special modes for chess engines.  Later something general
6373      *  could be added here; for now there is just one kludge feature,
6374      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6375      *  when "xboard" is given as an interactive command.
6376      */
6377     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6378         cps->useSigint = FALSE;
6379         cps->useSigterm = FALSE;
6380     }
6381     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6382       ParseFeatures(message+8, cps);
6383       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6384     }
6385
6386     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6387      * want this, I was asked to put it in, and obliged.
6388      */
6389     if (!strncmp(message, "setboard ", 9)) {
6390         Board initial_position; int i;
6391
6392         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6393
6394         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6395             DisplayError(_("Bad FEN received from engine"), 0);
6396             return ;
6397         } else {
6398            Reset(FALSE, FALSE);
6399            CopyBoard(boards[0], initial_position);
6400            initialRulePlies = FENrulePlies;
6401            epStatus[0] = FENepStatus;
6402            for( i=0; i<nrCastlingRights; i++ )
6403                 castlingRights[0][i] = FENcastlingRights[i];
6404            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6405            else gameMode = MachinePlaysBlack;                 
6406            DrawPosition(FALSE, boards[currentMove]);
6407         }
6408         return;
6409     }
6410
6411     /*
6412      * Look for communication commands
6413      */
6414     if (!strncmp(message, "telluser ", 9)) {
6415         DisplayNote(message + 9);
6416         return;
6417     }
6418     if (!strncmp(message, "tellusererror ", 14)) {
6419         DisplayError(message + 14, 0);
6420         return;
6421     }
6422     if (!strncmp(message, "tellopponent ", 13)) {
6423       if (appData.icsActive) {
6424         if (loggedOn) {
6425           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6426           SendToICS(buf1);
6427         }
6428       } else {
6429         DisplayNote(message + 13);
6430       }
6431       return;
6432     }
6433     if (!strncmp(message, "tellothers ", 11)) {
6434       if (appData.icsActive) {
6435         if (loggedOn) {
6436           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6437           SendToICS(buf1);
6438         }
6439       }
6440       return;
6441     }
6442     if (!strncmp(message, "tellall ", 8)) {
6443       if (appData.icsActive) {
6444         if (loggedOn) {
6445           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6446           SendToICS(buf1);
6447         }
6448       } else {
6449         DisplayNote(message + 8);
6450       }
6451       return;
6452     }
6453     if (strncmp(message, "warning", 7) == 0) {
6454         /* Undocumented feature, use tellusererror in new code */
6455         DisplayError(message, 0);
6456         return;
6457     }
6458     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6459         strcpy(realname, cps->tidy);
6460         strcat(realname, " query");
6461         AskQuestion(realname, buf2, buf1, cps->pr);
6462         return;
6463     }
6464     /* Commands from the engine directly to ICS.  We don't allow these to be 
6465      *  sent until we are logged on. Crafty kibitzes have been known to 
6466      *  interfere with the login process.
6467      */
6468     if (loggedOn) {
6469         if (!strncmp(message, "tellics ", 8)) {
6470             SendToICS(message + 8);
6471             SendToICS("\n");
6472             return;
6473         }
6474         if (!strncmp(message, "tellicsnoalias ", 15)) {
6475             SendToICS(ics_prefix);
6476             SendToICS(message + 15);
6477             SendToICS("\n");
6478             return;
6479         }
6480         /* The following are for backward compatibility only */
6481         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6482             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6483             SendToICS(ics_prefix);
6484             SendToICS(message);
6485             SendToICS("\n");
6486             return;
6487         }
6488     }
6489     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6490         return;
6491     }
6492     /*
6493      * If the move is illegal, cancel it and redraw the board.
6494      * Also deal with other error cases.  Matching is rather loose
6495      * here to accommodate engines written before the spec.
6496      */
6497     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6498         strncmp(message, "Error", 5) == 0) {
6499         if (StrStr(message, "name") || 
6500             StrStr(message, "rating") || StrStr(message, "?") ||
6501             StrStr(message, "result") || StrStr(message, "board") ||
6502             StrStr(message, "bk") || StrStr(message, "computer") ||
6503             StrStr(message, "variant") || StrStr(message, "hint") ||
6504             StrStr(message, "random") || StrStr(message, "depth") ||
6505             StrStr(message, "accepted")) {
6506             return;
6507         }
6508         if (StrStr(message, "protover")) {
6509           /* Program is responding to input, so it's apparently done
6510              initializing, and this error message indicates it is
6511              protocol version 1.  So we don't need to wait any longer
6512              for it to initialize and send feature commands. */
6513           FeatureDone(cps, 1);
6514           cps->protocolVersion = 1;
6515           return;
6516         }
6517         cps->maybeThinking = FALSE;
6518
6519         if (StrStr(message, "draw")) {
6520             /* Program doesn't have "draw" command */
6521             cps->sendDrawOffers = 0;
6522             return;
6523         }
6524         if (cps->sendTime != 1 &&
6525             (StrStr(message, "time") || StrStr(message, "otim"))) {
6526           /* Program apparently doesn't have "time" or "otim" command */
6527           cps->sendTime = 0;
6528           return;
6529         }
6530         if (StrStr(message, "analyze")) {
6531             cps->analysisSupport = FALSE;
6532             cps->analyzing = FALSE;
6533             Reset(FALSE, TRUE);
6534             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6535             DisplayError(buf2, 0);
6536             return;
6537         }
6538         if (StrStr(message, "(no matching move)st")) {
6539           /* Special kludge for GNU Chess 4 only */
6540           cps->stKludge = TRUE;
6541           SendTimeControl(cps, movesPerSession, timeControl,
6542                           timeIncrement, appData.searchDepth,
6543                           searchTime);
6544           return;
6545         }
6546         if (StrStr(message, "(no matching move)sd")) {
6547           /* Special kludge for GNU Chess 4 only */
6548           cps->sdKludge = TRUE;
6549           SendTimeControl(cps, movesPerSession, timeControl,
6550                           timeIncrement, appData.searchDepth,
6551                           searchTime);
6552           return;
6553         }
6554         if (!StrStr(message, "llegal")) {
6555             return;
6556         }
6557         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6558             gameMode == IcsIdle) return;
6559         if (forwardMostMove <= backwardMostMove) return;
6560         if (pausing) PauseEvent();
6561       if(appData.forceIllegal) {
6562             // [HGM] illegal: machine refused move; force position after move into it
6563           SendToProgram("force\n", cps);
6564           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6565                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6566                 // when black is to move, while there might be nothing on a2 or black
6567                 // might already have the move. So send the board as if white has the move.
6568                 // But first we must change the stm of the engine, as it refused the last move
6569                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6570                 if(WhiteOnMove(forwardMostMove)) {
6571                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6572                     SendBoard(cps, forwardMostMove); // kludgeless board
6573                 } else {
6574                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6575                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6576                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6577                 }
6578           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6579             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6580                  gameMode == TwoMachinesPlay)
6581               SendToProgram("go\n", cps);
6582             return;
6583       } else
6584         if (gameMode == PlayFromGameFile) {
6585             /* Stop reading this game file */
6586             gameMode = EditGame;
6587             ModeHighlight();
6588         }
6589         currentMove = --forwardMostMove;
6590         DisplayMove(currentMove-1); /* before DisplayMoveError */
6591         SwitchClocks();
6592         DisplayBothClocks();
6593         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6594                 parseList[currentMove], cps->which);
6595         DisplayMoveError(buf1);
6596         DrawPosition(FALSE, boards[currentMove]);
6597
6598         /* [HGM] illegal-move claim should forfeit game when Xboard */
6599         /* only passes fully legal moves                            */
6600         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6601             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6602                                 "False illegal-move claim", GE_XBOARD );
6603         }
6604         return;
6605     }
6606     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6607         /* Program has a broken "time" command that
6608            outputs a string not ending in newline.
6609            Don't use it. */
6610         cps->sendTime = 0;
6611     }
6612     
6613     /*
6614      * If chess program startup fails, exit with an error message.
6615      * Attempts to recover here are futile.
6616      */
6617     if ((StrStr(message, "unknown host") != NULL)
6618         || (StrStr(message, "No remote directory") != NULL)
6619         || (StrStr(message, "not found") != NULL)
6620         || (StrStr(message, "No such file") != NULL)
6621         || (StrStr(message, "can't alloc") != NULL)
6622         || (StrStr(message, "Permission denied") != NULL)) {
6623
6624         cps->maybeThinking = FALSE;
6625         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6626                 cps->which, cps->program, cps->host, message);
6627         RemoveInputSource(cps->isr);
6628         DisplayFatalError(buf1, 0, 1);
6629         return;
6630     }
6631     
6632     /* 
6633      * Look for hint output
6634      */
6635     if (sscanf(message, "Hint: %s", buf1) == 1) {
6636         if (cps == &first && hintRequested) {
6637             hintRequested = FALSE;
6638             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6639                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6640                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6641                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6642                                     fromY, fromX, toY, toX, promoChar, buf1);
6643                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6644                 DisplayInformation(buf2);
6645             } else {
6646                 /* Hint move could not be parsed!? */
6647               snprintf(buf2, sizeof(buf2),
6648                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6649                         buf1, cps->which);
6650                 DisplayError(buf2, 0);
6651             }
6652         } else {
6653             strcpy(lastHint, buf1);
6654         }
6655         return;
6656     }
6657
6658     /*
6659      * Ignore other messages if game is not in progress
6660      */
6661     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6662         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6663
6664     /*
6665      * look for win, lose, draw, or draw offer
6666      */
6667     if (strncmp(message, "1-0", 3) == 0) {
6668         char *p, *q, *r = "";
6669         p = strchr(message, '{');
6670         if (p) {
6671             q = strchr(p, '}');
6672             if (q) {
6673                 *q = NULLCHAR;
6674                 r = p + 1;
6675             }
6676         }
6677         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6678         return;
6679     } else if (strncmp(message, "0-1", 3) == 0) {
6680         char *p, *q, *r = "";
6681         p = strchr(message, '{');
6682         if (p) {
6683             q = strchr(p, '}');
6684             if (q) {
6685                 *q = NULLCHAR;
6686                 r = p + 1;
6687             }
6688         }
6689         /* Kludge for Arasan 4.1 bug */
6690         if (strcmp(r, "Black resigns") == 0) {
6691             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6692             return;
6693         }
6694         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6695         return;
6696     } else if (strncmp(message, "1/2", 3) == 0) {
6697         char *p, *q, *r = "";
6698         p = strchr(message, '{');
6699         if (p) {
6700             q = strchr(p, '}');
6701             if (q) {
6702                 *q = NULLCHAR;
6703                 r = p + 1;
6704             }
6705         }
6706             
6707         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6708         return;
6709
6710     } else if (strncmp(message, "White resign", 12) == 0) {
6711         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6712         return;
6713     } else if (strncmp(message, "Black resign", 12) == 0) {
6714         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6715         return;
6716     } else if (strncmp(message, "White matches", 13) == 0 ||
6717                strncmp(message, "Black matches", 13) == 0   ) {
6718         /* [HGM] ignore GNUShogi noises */
6719         return;
6720     } else if (strncmp(message, "White", 5) == 0 &&
6721                message[5] != '(' &&
6722                StrStr(message, "Black") == NULL) {
6723         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6724         return;
6725     } else if (strncmp(message, "Black", 5) == 0 &&
6726                message[5] != '(') {
6727         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6728         return;
6729     } else if (strcmp(message, "resign") == 0 ||
6730                strcmp(message, "computer resigns") == 0) {
6731         switch (gameMode) {
6732           case MachinePlaysBlack:
6733           case IcsPlayingBlack:
6734             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6735             break;
6736           case MachinePlaysWhite:
6737           case IcsPlayingWhite:
6738             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6739             break;
6740           case TwoMachinesPlay:
6741             if (cps->twoMachinesColor[0] == 'w')
6742               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6743             else
6744               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6745             break;
6746           default:
6747             /* can't happen */
6748             break;
6749         }
6750         return;
6751     } else if (strncmp(message, "opponent mates", 14) == 0) {
6752         switch (gameMode) {
6753           case MachinePlaysBlack:
6754           case IcsPlayingBlack:
6755             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6756             break;
6757           case MachinePlaysWhite:
6758           case IcsPlayingWhite:
6759             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6760             break;
6761           case TwoMachinesPlay:
6762             if (cps->twoMachinesColor[0] == 'w')
6763               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6764             else
6765               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6766             break;
6767           default:
6768             /* can't happen */
6769             break;
6770         }
6771         return;
6772     } else if (strncmp(message, "computer mates", 14) == 0) {
6773         switch (gameMode) {
6774           case MachinePlaysBlack:
6775           case IcsPlayingBlack:
6776             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6777             break;
6778           case MachinePlaysWhite:
6779           case IcsPlayingWhite:
6780             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6781             break;
6782           case TwoMachinesPlay:
6783             if (cps->twoMachinesColor[0] == 'w')
6784               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6785             else
6786               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6787             break;
6788           default:
6789             /* can't happen */
6790             break;
6791         }
6792         return;
6793     } else if (strncmp(message, "checkmate", 9) == 0) {
6794         if (WhiteOnMove(forwardMostMove)) {
6795             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6796         } else {
6797             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6798         }
6799         return;
6800     } else if (strstr(message, "Draw") != NULL ||
6801                strstr(message, "game is a draw") != NULL) {
6802         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6803         return;
6804     } else if (strstr(message, "offer") != NULL &&
6805                strstr(message, "draw") != NULL) {
6806 #if ZIPPY
6807         if (appData.zippyPlay && first.initDone) {
6808             /* Relay offer to ICS */
6809             SendToICS(ics_prefix);
6810             SendToICS("draw\n");
6811         }
6812 #endif
6813         cps->offeredDraw = 2; /* valid until this engine moves twice */
6814         if (gameMode == TwoMachinesPlay) {
6815             if (cps->other->offeredDraw) {
6816                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6817             /* [HGM] in two-machine mode we delay relaying draw offer      */
6818             /* until after we also have move, to see if it is really claim */
6819             }
6820         } else if (gameMode == MachinePlaysWhite ||
6821                    gameMode == MachinePlaysBlack) {
6822           if (userOfferedDraw) {
6823             DisplayInformation(_("Machine accepts your draw offer"));
6824             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6825           } else {
6826             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6827           }
6828         }
6829     }
6830
6831     
6832     /*
6833      * Look for thinking output
6834      */
6835     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6836           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6837                                 ) {
6838         int plylev, mvleft, mvtot, curscore, time;
6839         char mvname[MOVE_LEN];
6840         u64 nodes; // [DM]
6841         char plyext;
6842         int ignore = FALSE;
6843         int prefixHint = FALSE;
6844         mvname[0] = NULLCHAR;
6845
6846         switch (gameMode) {
6847           case MachinePlaysBlack:
6848           case IcsPlayingBlack:
6849             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6850             break;
6851           case MachinePlaysWhite:
6852           case IcsPlayingWhite:
6853             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6854             break;
6855           case AnalyzeMode:
6856           case AnalyzeFile:
6857             break;
6858           case IcsObserving: /* [DM] icsEngineAnalyze */
6859             if (!appData.icsEngineAnalyze) ignore = TRUE;
6860             break;
6861           case TwoMachinesPlay:
6862             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6863                 ignore = TRUE;
6864             }
6865             break;
6866           default:
6867             ignore = TRUE;
6868             break;
6869         }
6870
6871         if (!ignore) {
6872             buf1[0] = NULLCHAR;
6873             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6874                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6875
6876                 if (plyext != ' ' && plyext != '\t') {
6877                     time *= 100;
6878                 }
6879
6880                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6881                 if( cps->scoreIsAbsolute && 
6882                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6883                 {
6884                     curscore = -curscore;
6885                 }
6886
6887
6888                 programStats.depth = plylev;
6889                 programStats.nodes = nodes;
6890                 programStats.time = time;
6891                 programStats.score = curscore;
6892                 programStats.got_only_move = 0;
6893
6894                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6895                         int ticklen;
6896
6897                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6898                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6899                         if(WhiteOnMove(forwardMostMove)) 
6900                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6901                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6902                 }
6903
6904                 /* Buffer overflow protection */
6905                 if (buf1[0] != NULLCHAR) {
6906                     if (strlen(buf1) >= sizeof(programStats.movelist)
6907                         && appData.debugMode) {
6908                         fprintf(debugFP,
6909                                 "PV is too long; using the first %d bytes.\n",
6910                                 sizeof(programStats.movelist) - 1);
6911                     }
6912
6913                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6914                 } else {
6915                     sprintf(programStats.movelist, " no PV\n");
6916                 }
6917
6918                 if (programStats.seen_stat) {
6919                     programStats.ok_to_send = 1;
6920                 }
6921
6922                 if (strchr(programStats.movelist, '(') != NULL) {
6923                     programStats.line_is_book = 1;
6924                     programStats.nr_moves = 0;
6925                     programStats.moves_left = 0;
6926                 } else {
6927                     programStats.line_is_book = 0;
6928                 }
6929
6930                 SendProgramStatsToFrontend( cps, &programStats );
6931
6932                 /* 
6933                     [AS] Protect the thinkOutput buffer from overflow... this
6934                     is only useful if buf1 hasn't overflowed first!
6935                 */
6936                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6937                         plylev, 
6938                         (gameMode == TwoMachinesPlay ?
6939                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6940                         ((double) curscore) / 100.0,
6941                         prefixHint ? lastHint : "",
6942                         prefixHint ? " " : "" );
6943
6944                 if( buf1[0] != NULLCHAR ) {
6945                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6946
6947                     if( strlen(buf1) > max_len ) {
6948                         if( appData.debugMode) {
6949                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6950                         }
6951                         buf1[max_len+1] = '\0';
6952                     }
6953
6954                     strcat( thinkOutput, buf1 );
6955                 }
6956
6957                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6958                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6959                     DisplayMove(currentMove - 1);
6960                 }
6961                 return;
6962
6963             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6964                 /* crafty (9.25+) says "(only move) <move>"
6965                  * if there is only 1 legal move
6966                  */
6967                 sscanf(p, "(only move) %s", buf1);
6968                 sprintf(thinkOutput, "%s (only move)", buf1);
6969                 sprintf(programStats.movelist, "%s (only move)", buf1);
6970                 programStats.depth = 1;
6971                 programStats.nr_moves = 1;
6972                 programStats.moves_left = 1;
6973                 programStats.nodes = 1;
6974                 programStats.time = 1;
6975                 programStats.got_only_move = 1;
6976
6977                 /* Not really, but we also use this member to
6978                    mean "line isn't going to change" (Crafty
6979                    isn't searching, so stats won't change) */
6980                 programStats.line_is_book = 1;
6981
6982                 SendProgramStatsToFrontend( cps, &programStats );
6983                 
6984                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6985                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6986                     DisplayMove(currentMove - 1);
6987                 }
6988                 return;
6989             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6990                               &time, &nodes, &plylev, &mvleft,
6991                               &mvtot, mvname) >= 5) {
6992                 /* The stat01: line is from Crafty (9.29+) in response
6993                    to the "." command */
6994                 programStats.seen_stat = 1;
6995                 cps->maybeThinking = TRUE;
6996
6997                 if (programStats.got_only_move || !appData.periodicUpdates)
6998                   return;
6999
7000                 programStats.depth = plylev;
7001                 programStats.time = time;
7002                 programStats.nodes = nodes;
7003                 programStats.moves_left = mvleft;
7004                 programStats.nr_moves = mvtot;
7005                 strcpy(programStats.move_name, mvname);
7006                 programStats.ok_to_send = 1;
7007                 programStats.movelist[0] = '\0';
7008
7009                 SendProgramStatsToFrontend( cps, &programStats );
7010
7011                 return;
7012
7013             } else if (strncmp(message,"++",2) == 0) {
7014                 /* Crafty 9.29+ outputs this */
7015                 programStats.got_fail = 2;
7016                 return;
7017
7018             } else if (strncmp(message,"--",2) == 0) {
7019                 /* Crafty 9.29+ outputs this */
7020                 programStats.got_fail = 1;
7021                 return;
7022
7023             } else if (thinkOutput[0] != NULLCHAR &&
7024                        strncmp(message, "    ", 4) == 0) {
7025                 unsigned message_len;
7026
7027                 p = message;
7028                 while (*p && *p == ' ') p++;
7029
7030                 message_len = strlen( p );
7031
7032                 /* [AS] Avoid buffer overflow */
7033                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7034                     strcat(thinkOutput, " ");
7035                     strcat(thinkOutput, p);
7036                 }
7037
7038                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7039                     strcat(programStats.movelist, " ");
7040                     strcat(programStats.movelist, p);
7041                 }
7042
7043                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7044                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7045                     DisplayMove(currentMove - 1);
7046                 }
7047                 return;
7048             }
7049         }
7050         else {
7051             buf1[0] = NULLCHAR;
7052
7053             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7054                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7055             {
7056                 ChessProgramStats cpstats;
7057
7058                 if (plyext != ' ' && plyext != '\t') {
7059                     time *= 100;
7060                 }
7061
7062                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7063                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7064                     curscore = -curscore;
7065                 }
7066
7067                 cpstats.depth = plylev;
7068                 cpstats.nodes = nodes;
7069                 cpstats.time = time;
7070                 cpstats.score = curscore;
7071                 cpstats.got_only_move = 0;
7072                 cpstats.movelist[0] = '\0';
7073
7074                 if (buf1[0] != NULLCHAR) {
7075                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7076                 }
7077
7078                 cpstats.ok_to_send = 0;
7079                 cpstats.line_is_book = 0;
7080                 cpstats.nr_moves = 0;
7081                 cpstats.moves_left = 0;
7082
7083                 SendProgramStatsToFrontend( cps, &cpstats );
7084             }
7085         }
7086     }
7087 }
7088
7089
7090 /* Parse a game score from the character string "game", and
7091    record it as the history of the current game.  The game
7092    score is NOT assumed to start from the standard position. 
7093    The display is not updated in any way.
7094    */
7095 void
7096 ParseGameHistory(game)
7097      char *game;
7098 {
7099     ChessMove moveType;
7100     int fromX, fromY, toX, toY, boardIndex;
7101     char promoChar;
7102     char *p, *q;
7103     char buf[MSG_SIZ];
7104
7105     if (appData.debugMode)
7106       fprintf(debugFP, "Parsing game history: %s\n", game);
7107
7108     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7109     gameInfo.site = StrSave(appData.icsHost);
7110     gameInfo.date = PGNDate();
7111     gameInfo.round = StrSave("-");
7112
7113     /* Parse out names of players */
7114     while (*game == ' ') game++;
7115     p = buf;
7116     while (*game != ' ') *p++ = *game++;
7117     *p = NULLCHAR;
7118     gameInfo.white = StrSave(buf);
7119     while (*game == ' ') game++;
7120     p = buf;
7121     while (*game != ' ' && *game != '\n') *p++ = *game++;
7122     *p = NULLCHAR;
7123     gameInfo.black = StrSave(buf);
7124
7125     /* Parse moves */
7126     boardIndex = blackPlaysFirst ? 1 : 0;
7127     yynewstr(game);
7128     for (;;) {
7129         yyboardindex = boardIndex;
7130         moveType = (ChessMove) yylex();
7131         switch (moveType) {
7132           case IllegalMove:             /* maybe suicide chess, etc. */
7133   if (appData.debugMode) {
7134     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7135     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7136     setbuf(debugFP, NULL);
7137   }
7138           case WhitePromotionChancellor:
7139           case BlackPromotionChancellor:
7140           case WhitePromotionArchbishop:
7141           case BlackPromotionArchbishop:
7142           case WhitePromotionQueen:
7143           case BlackPromotionQueen:
7144           case WhitePromotionRook:
7145           case BlackPromotionRook:
7146           case WhitePromotionBishop:
7147           case BlackPromotionBishop:
7148           case WhitePromotionKnight:
7149           case BlackPromotionKnight:
7150           case WhitePromotionKing:
7151           case BlackPromotionKing:
7152           case NormalMove:
7153           case WhiteCapturesEnPassant:
7154           case BlackCapturesEnPassant:
7155           case WhiteKingSideCastle:
7156           case WhiteQueenSideCastle:
7157           case BlackKingSideCastle:
7158           case BlackQueenSideCastle:
7159           case WhiteKingSideCastleWild:
7160           case WhiteQueenSideCastleWild:
7161           case BlackKingSideCastleWild:
7162           case BlackQueenSideCastleWild:
7163           /* PUSH Fabien */
7164           case WhiteHSideCastleFR:
7165           case WhiteASideCastleFR:
7166           case BlackHSideCastleFR:
7167           case BlackASideCastleFR:
7168           /* POP Fabien */
7169             fromX = currentMoveString[0] - AAA;
7170             fromY = currentMoveString[1] - ONE;
7171             toX = currentMoveString[2] - AAA;
7172             toY = currentMoveString[3] - ONE;
7173             promoChar = currentMoveString[4];
7174             break;
7175           case WhiteDrop:
7176           case BlackDrop:
7177             fromX = moveType == WhiteDrop ?
7178               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7179             (int) CharToPiece(ToLower(currentMoveString[0]));
7180             fromY = DROP_RANK;
7181             toX = currentMoveString[2] - AAA;
7182             toY = currentMoveString[3] - ONE;
7183             promoChar = NULLCHAR;
7184             break;
7185           case AmbiguousMove:
7186             /* bug? */
7187             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7188   if (appData.debugMode) {
7189     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7190     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7191     setbuf(debugFP, NULL);
7192   }
7193             DisplayError(buf, 0);
7194             return;
7195           case ImpossibleMove:
7196             /* bug? */
7197             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7198   if (appData.debugMode) {
7199     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7200     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7201     setbuf(debugFP, NULL);
7202   }
7203             DisplayError(buf, 0);
7204             return;
7205           case (ChessMove) 0:   /* end of file */
7206             if (boardIndex < backwardMostMove) {
7207                 /* Oops, gap.  How did that happen? */
7208                 DisplayError(_("Gap in move list"), 0);
7209                 return;
7210             }
7211             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7212             if (boardIndex > forwardMostMove) {
7213                 forwardMostMove = boardIndex;
7214             }
7215             return;
7216           case ElapsedTime:
7217             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7218                 strcat(parseList[boardIndex-1], " ");
7219                 strcat(parseList[boardIndex-1], yy_text);
7220             }
7221             continue;
7222           case Comment:
7223           case PGNTag:
7224           case NAG:
7225           default:
7226             /* ignore */
7227             continue;
7228           case WhiteWins:
7229           case BlackWins:
7230           case GameIsDrawn:
7231           case GameUnfinished:
7232             if (gameMode == IcsExamining) {
7233                 if (boardIndex < backwardMostMove) {
7234                     /* Oops, gap.  How did that happen? */
7235                     return;
7236                 }
7237                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7238                 return;
7239             }
7240             gameInfo.result = moveType;
7241             p = strchr(yy_text, '{');
7242             if (p == NULL) p = strchr(yy_text, '(');
7243             if (p == NULL) {
7244                 p = yy_text;
7245                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7246             } else {
7247                 q = strchr(p, *p == '{' ? '}' : ')');
7248                 if (q != NULL) *q = NULLCHAR;
7249                 p++;
7250             }
7251             gameInfo.resultDetails = StrSave(p);
7252             continue;
7253         }
7254         if (boardIndex >= forwardMostMove &&
7255             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7256             backwardMostMove = blackPlaysFirst ? 1 : 0;
7257             return;
7258         }
7259         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7260                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7261                                  parseList[boardIndex]);
7262         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7263         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7264         /* currentMoveString is set as a side-effect of yylex */
7265         strcpy(moveList[boardIndex], currentMoveString);
7266         strcat(moveList[boardIndex], "\n");
7267         boardIndex++;
7268         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7269                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7270         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7271                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7272           case MT_NONE:
7273           case MT_STALEMATE:
7274           default:
7275             break;
7276           case MT_CHECK:
7277             if(gameInfo.variant != VariantShogi)
7278                 strcat(parseList[boardIndex - 1], "+");
7279             break;
7280           case MT_CHECKMATE:
7281           case MT_STAINMATE:
7282             strcat(parseList[boardIndex - 1], "#");
7283             break;
7284         }
7285     }
7286 }
7287
7288
7289 /* Apply a move to the given board  */
7290 void
7291 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7292      int fromX, fromY, toX, toY;
7293      int promoChar;
7294      Board board;
7295      char *castling;
7296      char *ep;
7297 {
7298   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7299
7300     /* [HGM] compute & store e.p. status and castling rights for new position */
7301     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7302     { int i;
7303
7304       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7305       oldEP = *ep;
7306       *ep = EP_NONE;
7307
7308       if( board[toY][toX] != EmptySquare ) 
7309            *ep = EP_CAPTURE;  
7310
7311       if( board[fromY][fromX] == WhitePawn ) {
7312            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7313                *ep = EP_PAWN_MOVE;
7314            if( toY-fromY==2) {
7315                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7316                         gameInfo.variant != VariantBerolina || toX < fromX)
7317                       *ep = toX | berolina;
7318                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7319                         gameInfo.variant != VariantBerolina || toX > fromX) 
7320                       *ep = toX;
7321            }
7322       } else 
7323       if( board[fromY][fromX] == BlackPawn ) {
7324            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7325                *ep = EP_PAWN_MOVE; 
7326            if( toY-fromY== -2) {
7327                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7328                         gameInfo.variant != VariantBerolina || toX < fromX)
7329                       *ep = toX | berolina;
7330                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7331                         gameInfo.variant != VariantBerolina || toX > fromX) 
7332                       *ep = toX;
7333            }
7334        }
7335
7336        for(i=0; i<nrCastlingRights; i++) {
7337            if(castling[i] == fromX && castlingRank[i] == fromY ||
7338               castling[i] == toX   && castlingRank[i] == toY   
7339              ) castling[i] = -1; // revoke for moved or captured piece
7340        }
7341
7342     }
7343
7344   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7345   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7346        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7347          
7348   if (fromX == toX && fromY == toY) return;
7349
7350   if (fromY == DROP_RANK) {
7351         /* must be first */
7352         piece = board[toY][toX] = (ChessSquare) fromX;
7353   } else {
7354      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7355      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7356      if(gameInfo.variant == VariantKnightmate)
7357          king += (int) WhiteUnicorn - (int) WhiteKing;
7358
7359     /* Code added by Tord: */
7360     /* FRC castling assumed when king captures friendly rook. */
7361     if (board[fromY][fromX] == WhiteKing &&
7362              board[toY][toX] == WhiteRook) {
7363       board[fromY][fromX] = EmptySquare;
7364       board[toY][toX] = EmptySquare;
7365       if(toX > fromX) {
7366         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7367       } else {
7368         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7369       }
7370     } else if (board[fromY][fromX] == BlackKing &&
7371                board[toY][toX] == BlackRook) {
7372       board[fromY][fromX] = EmptySquare;
7373       board[toY][toX] = EmptySquare;
7374       if(toX > fromX) {
7375         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7376       } else {
7377         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7378       }
7379     /* End of code added by Tord */
7380
7381     } else if (board[fromY][fromX] == king
7382         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7383         && toY == fromY && toX > fromX+1) {
7384         board[fromY][fromX] = EmptySquare;
7385         board[toY][toX] = king;
7386         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7387         board[fromY][BOARD_RGHT-1] = EmptySquare;
7388     } else if (board[fromY][fromX] == king
7389         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7390                && toY == fromY && toX < fromX-1) {
7391         board[fromY][fromX] = EmptySquare;
7392         board[toY][toX] = king;
7393         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7394         board[fromY][BOARD_LEFT] = EmptySquare;
7395     } else if (board[fromY][fromX] == WhitePawn
7396                && toY == BOARD_HEIGHT-1
7397                && gameInfo.variant != VariantXiangqi
7398                ) {
7399         /* white pawn promotion */
7400         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7401         if (board[toY][toX] == EmptySquare) {
7402             board[toY][toX] = WhiteQueen;
7403         }
7404         if(gameInfo.variant==VariantBughouse ||
7405            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7406             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7407         board[fromY][fromX] = EmptySquare;
7408     } else if ((fromY == BOARD_HEIGHT-4)
7409                && (toX != fromX)
7410                && gameInfo.variant != VariantXiangqi
7411                && gameInfo.variant != VariantBerolina
7412                && (board[fromY][fromX] == WhitePawn)
7413                && (board[toY][toX] == EmptySquare)) {
7414         board[fromY][fromX] = EmptySquare;
7415         board[toY][toX] = WhitePawn;
7416         captured = board[toY - 1][toX];
7417         board[toY - 1][toX] = EmptySquare;
7418     } else if ((fromY == BOARD_HEIGHT-4)
7419                && (toX == fromX)
7420                && gameInfo.variant == VariantBerolina
7421                && (board[fromY][fromX] == WhitePawn)
7422                && (board[toY][toX] == EmptySquare)) {
7423         board[fromY][fromX] = EmptySquare;
7424         board[toY][toX] = WhitePawn;
7425         if(oldEP & EP_BEROLIN_A) {
7426                 captured = board[fromY][fromX-1];
7427                 board[fromY][fromX-1] = EmptySquare;
7428         }else{  captured = board[fromY][fromX+1];
7429                 board[fromY][fromX+1] = EmptySquare;
7430         }
7431     } else if (board[fromY][fromX] == king
7432         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7433                && toY == fromY && toX > fromX+1) {
7434         board[fromY][fromX] = EmptySquare;
7435         board[toY][toX] = king;
7436         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7437         board[fromY][BOARD_RGHT-1] = EmptySquare;
7438     } else if (board[fromY][fromX] == king
7439         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7440                && toY == fromY && toX < fromX-1) {
7441         board[fromY][fromX] = EmptySquare;
7442         board[toY][toX] = king;
7443         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7444         board[fromY][BOARD_LEFT] = EmptySquare;
7445     } else if (fromY == 7 && fromX == 3
7446                && board[fromY][fromX] == BlackKing
7447                && toY == 7 && toX == 5) {
7448         board[fromY][fromX] = EmptySquare;
7449         board[toY][toX] = BlackKing;
7450         board[fromY][7] = EmptySquare;
7451         board[toY][4] = BlackRook;
7452     } else if (fromY == 7 && fromX == 3
7453                && board[fromY][fromX] == BlackKing
7454                && toY == 7 && toX == 1) {
7455         board[fromY][fromX] = EmptySquare;
7456         board[toY][toX] = BlackKing;
7457         board[fromY][0] = EmptySquare;
7458         board[toY][2] = BlackRook;
7459     } else if (board[fromY][fromX] == BlackPawn
7460                && toY == 0
7461                && gameInfo.variant != VariantXiangqi
7462                ) {
7463         /* black pawn promotion */
7464         board[0][toX] = CharToPiece(ToLower(promoChar));
7465         if (board[0][toX] == EmptySquare) {
7466             board[0][toX] = BlackQueen;
7467         }
7468         if(gameInfo.variant==VariantBughouse ||
7469            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7470             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7471         board[fromY][fromX] = EmptySquare;
7472     } else if ((fromY == 3)
7473                && (toX != fromX)
7474                && gameInfo.variant != VariantXiangqi
7475                && gameInfo.variant != VariantBerolina
7476                && (board[fromY][fromX] == BlackPawn)
7477                && (board[toY][toX] == EmptySquare)) {
7478         board[fromY][fromX] = EmptySquare;
7479         board[toY][toX] = BlackPawn;
7480         captured = board[toY + 1][toX];
7481         board[toY + 1][toX] = EmptySquare;
7482     } else if ((fromY == 3)
7483                && (toX == fromX)
7484                && gameInfo.variant == VariantBerolina
7485                && (board[fromY][fromX] == BlackPawn)
7486                && (board[toY][toX] == EmptySquare)) {
7487         board[fromY][fromX] = EmptySquare;
7488         board[toY][toX] = BlackPawn;
7489         if(oldEP & EP_BEROLIN_A) {
7490                 captured = board[fromY][fromX-1];
7491                 board[fromY][fromX-1] = EmptySquare;
7492         }else{  captured = board[fromY][fromX+1];
7493                 board[fromY][fromX+1] = EmptySquare;
7494         }
7495     } else {
7496         board[toY][toX] = board[fromY][fromX];
7497         board[fromY][fromX] = EmptySquare;
7498     }
7499
7500     /* [HGM] now we promote for Shogi, if needed */
7501     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7502         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7503   }
7504
7505     if (gameInfo.holdingsWidth != 0) {
7506
7507       /* !!A lot more code needs to be written to support holdings  */
7508       /* [HGM] OK, so I have written it. Holdings are stored in the */
7509       /* penultimate board files, so they are automaticlly stored   */
7510       /* in the game history.                                       */
7511       if (fromY == DROP_RANK) {
7512         /* Delete from holdings, by decreasing count */
7513         /* and erasing image if necessary            */
7514         p = (int) fromX;
7515         if(p < (int) BlackPawn) { /* white drop */
7516              p -= (int)WhitePawn;
7517                  p = PieceToNumber((ChessSquare)p);
7518              if(p >= gameInfo.holdingsSize) p = 0;
7519              if(--board[p][BOARD_WIDTH-2] <= 0)
7520                   board[p][BOARD_WIDTH-1] = EmptySquare;
7521              if((int)board[p][BOARD_WIDTH-2] < 0)
7522                         board[p][BOARD_WIDTH-2] = 0;
7523         } else {                  /* black drop */
7524              p -= (int)BlackPawn;
7525                  p = PieceToNumber((ChessSquare)p);
7526              if(p >= gameInfo.holdingsSize) p = 0;
7527              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7528                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7529              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7530                         board[BOARD_HEIGHT-1-p][1] = 0;
7531         }
7532       }
7533       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7534           && gameInfo.variant != VariantBughouse        ) {
7535         /* [HGM] holdings: Add to holdings, if holdings exist */
7536         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7537                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7538                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7539         }
7540         p = (int) captured;
7541         if (p >= (int) BlackPawn) {
7542           p -= (int)BlackPawn;
7543           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7544                   /* in Shogi restore piece to its original  first */
7545                   captured = (ChessSquare) (DEMOTED captured);
7546                   p = DEMOTED p;
7547           }
7548           p = PieceToNumber((ChessSquare)p);
7549           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7550           board[p][BOARD_WIDTH-2]++;
7551           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7552         } else {
7553           p -= (int)WhitePawn;
7554           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7555                   captured = (ChessSquare) (DEMOTED captured);
7556                   p = DEMOTED p;
7557           }
7558           p = PieceToNumber((ChessSquare)p);
7559           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7560           board[BOARD_HEIGHT-1-p][1]++;
7561           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7562         }
7563       }
7564     } else if (gameInfo.variant == VariantAtomic) {
7565       if (captured != EmptySquare) {
7566         int y, x;
7567         for (y = toY-1; y <= toY+1; y++) {
7568           for (x = toX-1; x <= toX+1; x++) {
7569             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7570                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7571               board[y][x] = EmptySquare;
7572             }
7573           }
7574         }
7575         board[toY][toX] = EmptySquare;
7576       }
7577     }
7578     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7579         /* [HGM] Shogi promotions */
7580         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7581     }
7582
7583     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7584                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7585         // [HGM] superchess: take promotion piece out of holdings
7586         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7587         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7588             if(!--board[k][BOARD_WIDTH-2])
7589                 board[k][BOARD_WIDTH-1] = EmptySquare;
7590         } else {
7591             if(!--board[BOARD_HEIGHT-1-k][1])
7592                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7593         }
7594     }
7595
7596 }
7597
7598 /* Updates forwardMostMove */
7599 void
7600 MakeMove(fromX, fromY, toX, toY, promoChar)
7601      int fromX, fromY, toX, toY;
7602      int promoChar;
7603 {
7604 //    forwardMostMove++; // [HGM] bare: moved downstream
7605
7606     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7607         int timeLeft; static int lastLoadFlag=0; int king, piece;
7608         piece = boards[forwardMostMove][fromY][fromX];
7609         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7610         if(gameInfo.variant == VariantKnightmate)
7611             king += (int) WhiteUnicorn - (int) WhiteKing;
7612         if(forwardMostMove == 0) {
7613             if(blackPlaysFirst) 
7614                 fprintf(serverMoves, "%s;", second.tidy);
7615             fprintf(serverMoves, "%s;", first.tidy);
7616             if(!blackPlaysFirst) 
7617                 fprintf(serverMoves, "%s;", second.tidy);
7618         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7619         lastLoadFlag = loadFlag;
7620         // print base move
7621         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7622         // print castling suffix
7623         if( toY == fromY && piece == king ) {
7624             if(toX-fromX > 1)
7625                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7626             if(fromX-toX >1)
7627                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7628         }
7629         // e.p. suffix
7630         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7631              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7632              boards[forwardMostMove][toY][toX] == EmptySquare
7633              && fromX != toX )
7634                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7635         // promotion suffix
7636         if(promoChar != NULLCHAR)
7637                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7638         if(!loadFlag) {
7639             fprintf(serverMoves, "/%d/%d",
7640                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7641             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7642             else                      timeLeft = blackTimeRemaining/1000;
7643             fprintf(serverMoves, "/%d", timeLeft);
7644         }
7645         fflush(serverMoves);
7646     }
7647
7648     if (forwardMostMove+1 >= MAX_MOVES) {
7649       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7650                         0, 1);
7651       return;
7652     }
7653     if (commentList[forwardMostMove+1] != NULL) {
7654         free(commentList[forwardMostMove+1]);
7655         commentList[forwardMostMove+1] = NULL;
7656     }
7657     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7658     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7659     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7660                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7661     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7662     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7663     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7664     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7665     gameInfo.result = GameUnfinished;
7666     if (gameInfo.resultDetails != NULL) {
7667         free(gameInfo.resultDetails);
7668         gameInfo.resultDetails = NULL;
7669     }
7670     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7671                               moveList[forwardMostMove - 1]);
7672     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7673                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7674                              fromY, fromX, toY, toX, promoChar,
7675                              parseList[forwardMostMove - 1]);
7676     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7677                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7678                             castlingRights[forwardMostMove]) ) {
7679       case MT_NONE:
7680       case MT_STALEMATE:
7681       default:
7682         break;
7683       case MT_CHECK:
7684         if(gameInfo.variant != VariantShogi)
7685             strcat(parseList[forwardMostMove - 1], "+");
7686         break;
7687       case MT_CHECKMATE:
7688       case MT_STAINMATE:
7689         strcat(parseList[forwardMostMove - 1], "#");
7690         break;
7691     }
7692     if (appData.debugMode) {
7693         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7694     }
7695
7696 }
7697
7698 /* Updates currentMove if not pausing */
7699 void
7700 ShowMove(fromX, fromY, toX, toY)
7701 {
7702     int instant = (gameMode == PlayFromGameFile) ?
7703         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7704     if(appData.noGUI) return;
7705     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7706         if (!instant) {
7707             if (forwardMostMove == currentMove + 1) {
7708                 AnimateMove(boards[forwardMostMove - 1],
7709                             fromX, fromY, toX, toY);
7710             }
7711             if (appData.highlightLastMove) {
7712                 SetHighlights(fromX, fromY, toX, toY);
7713             }
7714         }
7715         currentMove = forwardMostMove;
7716     }
7717
7718     if (instant) return;
7719
7720     DisplayMove(currentMove - 1);
7721     DrawPosition(FALSE, boards[currentMove]);
7722     DisplayBothClocks();
7723     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7724 }
7725
7726 void SendEgtPath(ChessProgramState *cps)
7727 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7728         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7729
7730         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7731
7732         while(*p) {
7733             char c, *q = name+1, *r, *s;
7734
7735             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7736             while(*p && *p != ',') *q++ = *p++;
7737             *q++ = ':'; *q = 0;
7738             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7739                 strcmp(name, ",nalimov:") == 0 ) {
7740                 // take nalimov path from the menu-changeable option first, if it is defined
7741                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7742                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7743             } else
7744             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7745                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7746                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7747                 s = r = StrStr(s, ":") + 1; // beginning of path info
7748                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7749                 c = *r; *r = 0;             // temporarily null-terminate path info
7750                     *--q = 0;               // strip of trailig ':' from name
7751                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7752                 *r = c;
7753                 SendToProgram(buf,cps);     // send egtbpath command for this format
7754             }
7755             if(*p == ',') p++; // read away comma to position for next format name
7756         }
7757 }
7758
7759 void
7760 InitChessProgram(cps, setup)
7761      ChessProgramState *cps;
7762      int setup; /* [HGM] needed to setup FRC opening position */
7763 {
7764     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7765     if (appData.noChessProgram) return;
7766     hintRequested = FALSE;
7767     bookRequested = FALSE;
7768
7769     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7770     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7771     if(cps->memSize) { /* [HGM] memory */
7772         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7773         SendToProgram(buf, cps);
7774     }
7775     SendEgtPath(cps); /* [HGM] EGT */
7776     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7777         sprintf(buf, "cores %d\n", appData.smpCores);
7778         SendToProgram(buf, cps);
7779     }
7780
7781     SendToProgram(cps->initString, cps);
7782     if (gameInfo.variant != VariantNormal &&
7783         gameInfo.variant != VariantLoadable
7784         /* [HGM] also send variant if board size non-standard */
7785         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7786                                             ) {
7787       char *v = VariantName(gameInfo.variant);
7788       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7789         /* [HGM] in protocol 1 we have to assume all variants valid */
7790         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7791         DisplayFatalError(buf, 0, 1);
7792         return;
7793       }
7794
7795       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7796       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7797       if( gameInfo.variant == VariantXiangqi )
7798            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7799       if( gameInfo.variant == VariantShogi )
7800            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7801       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7802            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7803       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7804                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7805            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7806       if( gameInfo.variant == VariantCourier )
7807            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7808       if( gameInfo.variant == VariantSuper )
7809            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7810       if( gameInfo.variant == VariantGreat )
7811            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7812
7813       if(overruled) {
7814            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7815                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7816            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7817            if(StrStr(cps->variants, b) == NULL) { 
7818                // specific sized variant not known, check if general sizing allowed
7819                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7820                    if(StrStr(cps->variants, "boardsize") == NULL) {
7821                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7822                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7823                        DisplayFatalError(buf, 0, 1);
7824                        return;
7825                    }
7826                    /* [HGM] here we really should compare with the maximum supported board size */
7827                }
7828            }
7829       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7830       sprintf(buf, "variant %s\n", b);
7831       SendToProgram(buf, cps);
7832     }
7833     currentlyInitializedVariant = gameInfo.variant;
7834
7835     /* [HGM] send opening position in FRC to first engine */
7836     if(setup) {
7837           SendToProgram("force\n", cps);
7838           SendBoard(cps, 0);
7839           /* engine is now in force mode! Set flag to wake it up after first move. */
7840           setboardSpoiledMachineBlack = 1;
7841     }
7842
7843     if (cps->sendICS) {
7844       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7845       SendToProgram(buf, cps);
7846     }
7847     cps->maybeThinking = FALSE;
7848     cps->offeredDraw = 0;
7849     if (!appData.icsActive) {
7850         SendTimeControl(cps, movesPerSession, timeControl,
7851                         timeIncrement, appData.searchDepth,
7852                         searchTime);
7853     }
7854     if (appData.showThinking 
7855         // [HGM] thinking: four options require thinking output to be sent
7856         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7857                                 ) {
7858         SendToProgram("post\n", cps);
7859     }
7860     SendToProgram("hard\n", cps);
7861     if (!appData.ponderNextMove) {
7862         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7863            it without being sure what state we are in first.  "hard"
7864            is not a toggle, so that one is OK.
7865          */
7866         SendToProgram("easy\n", cps);
7867     }
7868     if (cps->usePing) {
7869       sprintf(buf, "ping %d\n", ++cps->lastPing);
7870       SendToProgram(buf, cps);
7871     }
7872     cps->initDone = TRUE;
7873 }   
7874
7875
7876 void
7877 StartChessProgram(cps)
7878      ChessProgramState *cps;
7879 {
7880     char buf[MSG_SIZ];
7881     int err;
7882
7883     if (appData.noChessProgram) return;
7884     cps->initDone = FALSE;
7885
7886     if (strcmp(cps->host, "localhost") == 0) {
7887         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7888     } else if (*appData.remoteShell == NULLCHAR) {
7889         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7890     } else {
7891         if (*appData.remoteUser == NULLCHAR) {
7892           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7893                     cps->program);
7894         } else {
7895           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7896                     cps->host, appData.remoteUser, cps->program);
7897         }
7898         err = StartChildProcess(buf, "", &cps->pr);
7899     }
7900     
7901     if (err != 0) {
7902         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7903         DisplayFatalError(buf, err, 1);
7904         cps->pr = NoProc;
7905         cps->isr = NULL;
7906         return;
7907     }
7908     
7909     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7910     if (cps->protocolVersion > 1) {
7911       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7912       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7913       cps->comboCnt = 0;  //                and values of combo boxes
7914       SendToProgram(buf, cps);
7915     } else {
7916       SendToProgram("xboard\n", cps);
7917     }
7918 }
7919
7920
7921 void
7922 TwoMachinesEventIfReady P((void))
7923 {
7924   if (first.lastPing != first.lastPong) {
7925     DisplayMessage("", _("Waiting for first chess program"));
7926     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7927     return;
7928   }
7929   if (second.lastPing != second.lastPong) {
7930     DisplayMessage("", _("Waiting for second chess program"));
7931     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7932     return;
7933   }
7934   ThawUI();
7935   TwoMachinesEvent();
7936 }
7937
7938 void
7939 NextMatchGame P((void))
7940 {
7941     int index; /* [HGM] autoinc: step lod index during match */
7942     Reset(FALSE, TRUE);
7943     if (*appData.loadGameFile != NULLCHAR) {
7944         index = appData.loadGameIndex;
7945         if(index < 0) { // [HGM] autoinc
7946             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7947             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7948         } 
7949         LoadGameFromFile(appData.loadGameFile,
7950                          index,
7951                          appData.loadGameFile, FALSE);
7952     } else if (*appData.loadPositionFile != NULLCHAR) {
7953         index = appData.loadPositionIndex;
7954         if(index < 0) { // [HGM] autoinc
7955             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7956             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7957         } 
7958         LoadPositionFromFile(appData.loadPositionFile,
7959                              index,
7960                              appData.loadPositionFile);
7961     }
7962     TwoMachinesEventIfReady();
7963 }
7964
7965 void UserAdjudicationEvent( int result )
7966 {
7967     ChessMove gameResult = GameIsDrawn;
7968
7969     if( result > 0 ) {
7970         gameResult = WhiteWins;
7971     }
7972     else if( result < 0 ) {
7973         gameResult = BlackWins;
7974     }
7975
7976     if( gameMode == TwoMachinesPlay ) {
7977         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7978     }
7979 }
7980
7981
7982 // [HGM] save: calculate checksum of game to make games easily identifiable
7983 int StringCheckSum(char *s)
7984 {
7985         int i = 0;
7986         if(s==NULL) return 0;
7987         while(*s) i = i*259 + *s++;
7988         return i;
7989 }
7990
7991 int GameCheckSum()
7992 {
7993         int i, sum=0;
7994         for(i=backwardMostMove; i<forwardMostMove; i++) {
7995                 sum += pvInfoList[i].depth;
7996                 sum += StringCheckSum(parseList[i]);
7997                 sum += StringCheckSum(commentList[i]);
7998                 sum *= 261;
7999         }
8000         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8001         return sum + StringCheckSum(commentList[i]);
8002 } // end of save patch
8003
8004 void
8005 GameEnds(result, resultDetails, whosays)
8006      ChessMove result;
8007      char *resultDetails;
8008      int whosays;
8009 {
8010     GameMode nextGameMode;
8011     int isIcsGame;
8012     char buf[MSG_SIZ];
8013
8014     if(endingGame) return; /* [HGM] crash: forbid recursion */
8015     endingGame = 1;
8016
8017     if (appData.debugMode) {
8018       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8019               result, resultDetails ? resultDetails : "(null)", whosays);
8020     }
8021
8022     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8023         /* If we are playing on ICS, the server decides when the
8024            game is over, but the engine can offer to draw, claim 
8025            a draw, or resign. 
8026          */
8027 #if ZIPPY
8028         if (appData.zippyPlay && first.initDone) {
8029             if (result == GameIsDrawn) {
8030                 /* In case draw still needs to be claimed */
8031                 SendToICS(ics_prefix);
8032                 SendToICS("draw\n");
8033             } else if (StrCaseStr(resultDetails, "resign")) {
8034                 SendToICS(ics_prefix);
8035                 SendToICS("resign\n");
8036             }
8037         }
8038 #endif
8039         endingGame = 0; /* [HGM] crash */
8040         return;
8041     }
8042
8043     /* If we're loading the game from a file, stop */
8044     if (whosays == GE_FILE) {
8045       (void) StopLoadGameTimer();
8046       gameFileFP = NULL;
8047     }
8048
8049     /* Cancel draw offers */
8050     first.offeredDraw = second.offeredDraw = 0;
8051
8052     /* If this is an ICS game, only ICS can really say it's done;
8053        if not, anyone can. */
8054     isIcsGame = (gameMode == IcsPlayingWhite || 
8055                  gameMode == IcsPlayingBlack || 
8056                  gameMode == IcsObserving    || 
8057                  gameMode == IcsExamining);
8058
8059     if (!isIcsGame || whosays == GE_ICS) {
8060         /* OK -- not an ICS game, or ICS said it was done */
8061         StopClocks();
8062         if (!isIcsGame && !appData.noChessProgram) 
8063           SetUserThinkingEnables();
8064     
8065         /* [HGM] if a machine claims the game end we verify this claim */
8066         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8067             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8068                 char claimer;
8069                 ChessMove trueResult = (ChessMove) -1;
8070
8071                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8072                                             first.twoMachinesColor[0] :
8073                                             second.twoMachinesColor[0] ;
8074
8075                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8076                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8077                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8078                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8079                 } else
8080                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8081                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8082                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8083                 } else
8084                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8085                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8086                 }
8087
8088                 // now verify win claims, but not in drop games, as we don't understand those yet
8089                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8090                                                  || gameInfo.variant == VariantGreat) &&
8091                     (result == WhiteWins && claimer == 'w' ||
8092                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8093                       if (appData.debugMode) {
8094                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8095                                 result, epStatus[forwardMostMove], forwardMostMove);
8096                       }
8097                       if(result != trueResult) {
8098                               sprintf(buf, "False win claim: '%s'", resultDetails);
8099                               result = claimer == 'w' ? BlackWins : WhiteWins;
8100                               resultDetails = buf;
8101                       }
8102                 } else
8103                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8104                     && (forwardMostMove <= backwardMostMove ||
8105                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8106                         (claimer=='b')==(forwardMostMove&1))
8107                                                                                   ) {
8108                       /* [HGM] verify: draws that were not flagged are false claims */
8109                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8110                       result = claimer == 'w' ? BlackWins : WhiteWins;
8111                       resultDetails = buf;
8112                 }
8113                 /* (Claiming a loss is accepted no questions asked!) */
8114             }
8115             /* [HGM] bare: don't allow bare King to win */
8116             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8117                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8118                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8119                && result != GameIsDrawn)
8120             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8121                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8122                         int p = (int)boards[forwardMostMove][i][j] - color;
8123                         if(p >= 0 && p <= (int)WhiteKing) k++;
8124                 }
8125                 if (appData.debugMode) {
8126                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8127                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8128                 }
8129                 if(k <= 1) {
8130                         result = GameIsDrawn;
8131                         sprintf(buf, "%s but bare king", resultDetails);
8132                         resultDetails = buf;
8133                 }
8134             }
8135         }
8136
8137
8138         if(serverMoves != NULL && !loadFlag) { char c = '=';
8139             if(result==WhiteWins) c = '+';
8140             if(result==BlackWins) c = '-';
8141             if(resultDetails != NULL)
8142                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8143         }
8144         if (resultDetails != NULL) {
8145             gameInfo.result = result;
8146             gameInfo.resultDetails = StrSave(resultDetails);
8147
8148             /* display last move only if game was not loaded from file */
8149             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8150                 DisplayMove(currentMove - 1);
8151     
8152             if (forwardMostMove != 0) {
8153                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8154                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8155                                                                 ) {
8156                     if (*appData.saveGameFile != NULLCHAR) {
8157                         SaveGameToFile(appData.saveGameFile, TRUE);
8158                     } else if (appData.autoSaveGames) {
8159                         AutoSaveGame();
8160                     }
8161                     if (*appData.savePositionFile != NULLCHAR) {
8162                         SavePositionToFile(appData.savePositionFile);
8163                     }
8164                 }
8165             }
8166
8167             /* Tell program how game ended in case it is learning */
8168             /* [HGM] Moved this to after saving the PGN, just in case */
8169             /* engine died and we got here through time loss. In that */
8170             /* case we will get a fatal error writing the pipe, which */
8171             /* would otherwise lose us the PGN.                       */
8172             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8173             /* output during GameEnds should never be fatal anymore   */
8174             if (gameMode == MachinePlaysWhite ||
8175                 gameMode == MachinePlaysBlack ||
8176                 gameMode == TwoMachinesPlay ||
8177                 gameMode == IcsPlayingWhite ||
8178                 gameMode == IcsPlayingBlack ||
8179                 gameMode == BeginningOfGame) {
8180                 char buf[MSG_SIZ];
8181                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8182                         resultDetails);
8183                 if (first.pr != NoProc) {
8184                     SendToProgram(buf, &first);
8185                 }
8186                 if (second.pr != NoProc &&
8187                     gameMode == TwoMachinesPlay) {
8188                     SendToProgram(buf, &second);
8189                 }
8190             }
8191         }
8192
8193         if (appData.icsActive) {
8194             if (appData.quietPlay &&
8195                 (gameMode == IcsPlayingWhite ||
8196                  gameMode == IcsPlayingBlack)) {
8197                 SendToICS(ics_prefix);
8198                 SendToICS("set shout 1\n");
8199             }
8200             nextGameMode = IcsIdle;
8201             ics_user_moved = FALSE;
8202             /* clean up premove.  It's ugly when the game has ended and the
8203              * premove highlights are still on the board.
8204              */
8205             if (gotPremove) {
8206               gotPremove = FALSE;
8207               ClearPremoveHighlights();
8208               DrawPosition(FALSE, boards[currentMove]);
8209             }
8210             if (whosays == GE_ICS) {
8211                 switch (result) {
8212                 case WhiteWins:
8213                     if (gameMode == IcsPlayingWhite)
8214                         PlayIcsWinSound();
8215                     else if(gameMode == IcsPlayingBlack)
8216                         PlayIcsLossSound();
8217                     break;
8218                 case BlackWins:
8219                     if (gameMode == IcsPlayingBlack)
8220                         PlayIcsWinSound();
8221                     else if(gameMode == IcsPlayingWhite)
8222                         PlayIcsLossSound();
8223                     break;
8224                 case GameIsDrawn:
8225                     PlayIcsDrawSound();
8226                     break;
8227                 default:
8228                     PlayIcsUnfinishedSound();
8229                 }
8230             }
8231         } else if (gameMode == EditGame ||
8232                    gameMode == PlayFromGameFile || 
8233                    gameMode == AnalyzeMode || 
8234                    gameMode == AnalyzeFile) {
8235             nextGameMode = gameMode;
8236         } else {
8237             nextGameMode = EndOfGame;
8238         }
8239         pausing = FALSE;
8240         ModeHighlight();
8241     } else {
8242         nextGameMode = gameMode;
8243     }
8244
8245     if (appData.noChessProgram) {
8246         gameMode = nextGameMode;
8247         ModeHighlight();
8248         endingGame = 0; /* [HGM] crash */
8249         return;
8250     }
8251
8252     if (first.reuse) {
8253         /* Put first chess program into idle state */
8254         if (first.pr != NoProc &&
8255             (gameMode == MachinePlaysWhite ||
8256              gameMode == MachinePlaysBlack ||
8257              gameMode == TwoMachinesPlay ||
8258              gameMode == IcsPlayingWhite ||
8259              gameMode == IcsPlayingBlack ||
8260              gameMode == BeginningOfGame)) {
8261             SendToProgram("force\n", &first);
8262             if (first.usePing) {
8263               char buf[MSG_SIZ];
8264               sprintf(buf, "ping %d\n", ++first.lastPing);
8265               SendToProgram(buf, &first);
8266             }
8267         }
8268     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8269         /* Kill off first chess program */
8270         if (first.isr != NULL)
8271           RemoveInputSource(first.isr);
8272         first.isr = NULL;
8273     
8274         if (first.pr != NoProc) {
8275             ExitAnalyzeMode();
8276             DoSleep( appData.delayBeforeQuit );
8277             SendToProgram("quit\n", &first);
8278             DoSleep( appData.delayAfterQuit );
8279             DestroyChildProcess(first.pr, first.useSigterm);
8280         }
8281         first.pr = NoProc;
8282     }
8283     if (second.reuse) {
8284         /* Put second chess program into idle state */
8285         if (second.pr != NoProc &&
8286             gameMode == TwoMachinesPlay) {
8287             SendToProgram("force\n", &second);
8288             if (second.usePing) {
8289               char buf[MSG_SIZ];
8290               sprintf(buf, "ping %d\n", ++second.lastPing);
8291               SendToProgram(buf, &second);
8292             }
8293         }
8294     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8295         /* Kill off second chess program */
8296         if (second.isr != NULL)
8297           RemoveInputSource(second.isr);
8298         second.isr = NULL;
8299     
8300         if (second.pr != NoProc) {
8301             DoSleep( appData.delayBeforeQuit );
8302             SendToProgram("quit\n", &second);
8303             DoSleep( appData.delayAfterQuit );
8304             DestroyChildProcess(second.pr, second.useSigterm);
8305         }
8306         second.pr = NoProc;
8307     }
8308
8309     if (matchMode && gameMode == TwoMachinesPlay) {
8310         switch (result) {
8311         case WhiteWins:
8312           if (first.twoMachinesColor[0] == 'w') {
8313             first.matchWins++;
8314           } else {
8315             second.matchWins++;
8316           }
8317           break;
8318         case BlackWins:
8319           if (first.twoMachinesColor[0] == 'b') {
8320             first.matchWins++;
8321           } else {
8322             second.matchWins++;
8323           }
8324           break;
8325         default:
8326           break;
8327         }
8328         if (matchGame < appData.matchGames) {
8329             char *tmp;
8330             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8331                 tmp = first.twoMachinesColor;
8332                 first.twoMachinesColor = second.twoMachinesColor;
8333                 second.twoMachinesColor = tmp;
8334             }
8335             gameMode = nextGameMode;
8336             matchGame++;
8337             if(appData.matchPause>10000 || appData.matchPause<10)
8338                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8339             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8340             endingGame = 0; /* [HGM] crash */
8341             return;
8342         } else {
8343             char buf[MSG_SIZ];
8344             gameMode = nextGameMode;
8345             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8346                     first.tidy, second.tidy,
8347                     first.matchWins, second.matchWins,
8348                     appData.matchGames - (first.matchWins + second.matchWins));
8349             DisplayFatalError(buf, 0, 0);
8350         }
8351     }
8352     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8353         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8354       ExitAnalyzeMode();
8355     gameMode = nextGameMode;
8356     ModeHighlight();
8357     endingGame = 0;  /* [HGM] crash */
8358 }
8359
8360 /* Assumes program was just initialized (initString sent).
8361    Leaves program in force mode. */
8362 void
8363 FeedMovesToProgram(cps, upto) 
8364      ChessProgramState *cps;
8365      int upto;
8366 {
8367     int i;
8368     
8369     if (appData.debugMode)
8370       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8371               startedFromSetupPosition ? "position and " : "",
8372               backwardMostMove, upto, cps->which);
8373     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8374         // [HGM] variantswitch: make engine aware of new variant
8375         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8376                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8377         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8378         SendToProgram(buf, cps);
8379         currentlyInitializedVariant = gameInfo.variant;
8380     }
8381     SendToProgram("force\n", cps);
8382     if (startedFromSetupPosition) {
8383         SendBoard(cps, backwardMostMove);
8384     if (appData.debugMode) {
8385         fprintf(debugFP, "feedMoves\n");
8386     }
8387     }
8388     for (i = backwardMostMove; i < upto; i++) {
8389         SendMoveToProgram(i, cps);
8390     }
8391 }
8392
8393
8394 void
8395 ResurrectChessProgram()
8396 {
8397      /* The chess program may have exited.
8398         If so, restart it and feed it all the moves made so far. */
8399
8400     if (appData.noChessProgram || first.pr != NoProc) return;
8401     
8402     StartChessProgram(&first);
8403     InitChessProgram(&first, FALSE);
8404     FeedMovesToProgram(&first, currentMove);
8405
8406     if (!first.sendTime) {
8407         /* can't tell gnuchess what its clock should read,
8408            so we bow to its notion. */
8409         ResetClocks();
8410         timeRemaining[0][currentMove] = whiteTimeRemaining;
8411         timeRemaining[1][currentMove] = blackTimeRemaining;
8412     }
8413
8414     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8415                 appData.icsEngineAnalyze) && first.analysisSupport) {
8416       SendToProgram("analyze\n", &first);
8417       first.analyzing = TRUE;
8418     }
8419 }
8420
8421 /*
8422  * Button procedures
8423  */
8424 void
8425 Reset(redraw, init)
8426      int redraw, init;
8427 {
8428     int i;
8429
8430     if (appData.debugMode) {
8431         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8432                 redraw, init, gameMode);
8433     }
8434     pausing = pauseExamInvalid = FALSE;
8435     startedFromSetupPosition = blackPlaysFirst = FALSE;
8436     firstMove = TRUE;
8437     whiteFlag = blackFlag = FALSE;
8438     userOfferedDraw = FALSE;
8439     hintRequested = bookRequested = FALSE;
8440     first.maybeThinking = FALSE;
8441     second.maybeThinking = FALSE;
8442     first.bookSuspend = FALSE; // [HGM] book
8443     second.bookSuspend = FALSE;
8444     thinkOutput[0] = NULLCHAR;
8445     lastHint[0] = NULLCHAR;
8446     ClearGameInfo(&gameInfo);
8447     gameInfo.variant = StringToVariant(appData.variant);
8448     ics_user_moved = ics_clock_paused = FALSE;
8449     ics_getting_history = H_FALSE;
8450     ics_gamenum = -1;
8451     white_holding[0] = black_holding[0] = NULLCHAR;
8452     ClearProgramStats();
8453     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8454     
8455     ResetFrontEnd();
8456     ClearHighlights();
8457     flipView = appData.flipView;
8458     ClearPremoveHighlights();
8459     gotPremove = FALSE;
8460     alarmSounded = FALSE;
8461
8462     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8463     if(appData.serverMovesName != NULL) {
8464         /* [HGM] prepare to make moves file for broadcasting */
8465         clock_t t = clock();
8466         if(serverMoves != NULL) fclose(serverMoves);
8467         serverMoves = fopen(appData.serverMovesName, "r");
8468         if(serverMoves != NULL) {
8469             fclose(serverMoves);
8470             /* delay 15 sec before overwriting, so all clients can see end */
8471             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8472         }
8473         serverMoves = fopen(appData.serverMovesName, "w");
8474     }
8475
8476     ExitAnalyzeMode();
8477     gameMode = BeginningOfGame;
8478     ModeHighlight();
8479     if(appData.icsActive) gameInfo.variant = VariantNormal;
8480     currentMove = forwardMostMove = backwardMostMove = 0;
8481     InitPosition(redraw);
8482     for (i = 0; i < MAX_MOVES; i++) {
8483         if (commentList[i] != NULL) {
8484             free(commentList[i]);
8485             commentList[i] = NULL;
8486         }
8487     }
8488     ResetClocks();
8489     timeRemaining[0][0] = whiteTimeRemaining;
8490     timeRemaining[1][0] = blackTimeRemaining;
8491     if (first.pr == NULL) {
8492         StartChessProgram(&first);
8493     }
8494     if (init) {
8495             InitChessProgram(&first, startedFromSetupPosition);
8496     }
8497     DisplayTitle("");
8498     DisplayMessage("", "");
8499     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8500     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8501 }
8502
8503 void
8504 AutoPlayGameLoop()
8505 {
8506     for (;;) {
8507         if (!AutoPlayOneMove())
8508           return;
8509         if (matchMode || appData.timeDelay == 0)
8510           continue;
8511         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8512           return;
8513         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8514         break;
8515     }
8516 }
8517
8518
8519 int
8520 AutoPlayOneMove()
8521 {
8522     int fromX, fromY, toX, toY;
8523
8524     if (appData.debugMode) {
8525       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8526     }
8527
8528     if (gameMode != PlayFromGameFile)
8529       return FALSE;
8530
8531     if (currentMove >= forwardMostMove) {
8532       gameMode = EditGame;
8533       ModeHighlight();
8534
8535       /* [AS] Clear current move marker at the end of a game */
8536       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8537
8538       return FALSE;
8539     }
8540     
8541     toX = moveList[currentMove][2] - AAA;
8542     toY = moveList[currentMove][3] - ONE;
8543
8544     if (moveList[currentMove][1] == '@') {
8545         if (appData.highlightLastMove) {
8546             SetHighlights(-1, -1, toX, toY);
8547         }
8548     } else {
8549         fromX = moveList[currentMove][0] - AAA;
8550         fromY = moveList[currentMove][1] - ONE;
8551
8552         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8553
8554         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8555
8556         if (appData.highlightLastMove) {
8557             SetHighlights(fromX, fromY, toX, toY);
8558         }
8559     }
8560     DisplayMove(currentMove);
8561     SendMoveToProgram(currentMove++, &first);
8562     DisplayBothClocks();
8563     DrawPosition(FALSE, boards[currentMove]);
8564     // [HGM] PV info: always display, routine tests if empty
8565     DisplayComment(currentMove - 1, commentList[currentMove]);
8566     return TRUE;
8567 }
8568
8569
8570 int
8571 LoadGameOneMove(readAhead)
8572      ChessMove readAhead;
8573 {
8574     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8575     char promoChar = NULLCHAR;
8576     ChessMove moveType;
8577     char move[MSG_SIZ];
8578     char *p, *q;
8579     
8580     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8581         gameMode != AnalyzeMode && gameMode != Training) {
8582         gameFileFP = NULL;
8583         return FALSE;
8584     }
8585     
8586     yyboardindex = forwardMostMove;
8587     if (readAhead != (ChessMove)0) {
8588       moveType = readAhead;
8589     } else {
8590       if (gameFileFP == NULL)
8591           return FALSE;
8592       moveType = (ChessMove) yylex();
8593     }
8594     
8595     done = FALSE;
8596     switch (moveType) {
8597       case Comment:
8598         if (appData.debugMode) 
8599           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8600         p = yy_text;
8601         if (*p == '{' || *p == '[' || *p == '(') {
8602             p[strlen(p) - 1] = NULLCHAR;
8603             p++;
8604         }
8605
8606         /* append the comment but don't display it */
8607         while (*p == '\n') p++;
8608         AppendComment(currentMove, p);
8609         return TRUE;
8610
8611       case WhiteCapturesEnPassant:
8612       case BlackCapturesEnPassant:
8613       case WhitePromotionChancellor:
8614       case BlackPromotionChancellor:
8615       case WhitePromotionArchbishop:
8616       case BlackPromotionArchbishop:
8617       case WhitePromotionCentaur:
8618       case BlackPromotionCentaur:
8619       case WhitePromotionQueen:
8620       case BlackPromotionQueen:
8621       case WhitePromotionRook:
8622       case BlackPromotionRook:
8623       case WhitePromotionBishop:
8624       case BlackPromotionBishop:
8625       case WhitePromotionKnight:
8626       case BlackPromotionKnight:
8627       case WhitePromotionKing:
8628       case BlackPromotionKing:
8629       case NormalMove:
8630       case WhiteKingSideCastle:
8631       case WhiteQueenSideCastle:
8632       case BlackKingSideCastle:
8633       case BlackQueenSideCastle:
8634       case WhiteKingSideCastleWild:
8635       case WhiteQueenSideCastleWild:
8636       case BlackKingSideCastleWild:
8637       case BlackQueenSideCastleWild:
8638       /* PUSH Fabien */
8639       case WhiteHSideCastleFR:
8640       case WhiteASideCastleFR:
8641       case BlackHSideCastleFR:
8642       case BlackASideCastleFR:
8643       /* POP Fabien */
8644         if (appData.debugMode)
8645           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8646         fromX = currentMoveString[0] - AAA;
8647         fromY = currentMoveString[1] - ONE;
8648         toX = currentMoveString[2] - AAA;
8649         toY = currentMoveString[3] - ONE;
8650         promoChar = currentMoveString[4];
8651         break;
8652
8653       case WhiteDrop:
8654       case BlackDrop:
8655         if (appData.debugMode)
8656           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8657         fromX = moveType == WhiteDrop ?
8658           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8659         (int) CharToPiece(ToLower(currentMoveString[0]));
8660         fromY = DROP_RANK;
8661         toX = currentMoveString[2] - AAA;
8662         toY = currentMoveString[3] - ONE;
8663         break;
8664
8665       case WhiteWins:
8666       case BlackWins:
8667       case GameIsDrawn:
8668       case GameUnfinished:
8669         if (appData.debugMode)
8670           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8671         p = strchr(yy_text, '{');
8672         if (p == NULL) p = strchr(yy_text, '(');
8673         if (p == NULL) {
8674             p = yy_text;
8675             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8676         } else {
8677             q = strchr(p, *p == '{' ? '}' : ')');
8678             if (q != NULL) *q = NULLCHAR;
8679             p++;
8680         }
8681         GameEnds(moveType, p, GE_FILE);
8682         done = TRUE;
8683         if (cmailMsgLoaded) {
8684             ClearHighlights();
8685             flipView = WhiteOnMove(currentMove);
8686             if (moveType == GameUnfinished) flipView = !flipView;
8687             if (appData.debugMode)
8688               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8689         }
8690         break;
8691
8692       case (ChessMove) 0:       /* end of file */
8693         if (appData.debugMode)
8694           fprintf(debugFP, "Parser hit end of file\n");
8695         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8696                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8697           case MT_NONE:
8698           case MT_CHECK:
8699             break;
8700           case MT_CHECKMATE:
8701           case MT_STAINMATE:
8702             if (WhiteOnMove(currentMove)) {
8703                 GameEnds(BlackWins, "Black mates", GE_FILE);
8704             } else {
8705                 GameEnds(WhiteWins, "White mates", GE_FILE);
8706             }
8707             break;
8708           case MT_STALEMATE:
8709             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8710             break;
8711         }
8712         done = TRUE;
8713         break;
8714
8715       case MoveNumberOne:
8716         if (lastLoadGameStart == GNUChessGame) {
8717             /* GNUChessGames have numbers, but they aren't move numbers */
8718             if (appData.debugMode)
8719               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8720                       yy_text, (int) moveType);
8721             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8722         }
8723         /* else fall thru */
8724
8725       case XBoardGame:
8726       case GNUChessGame:
8727       case PGNTag:
8728         /* Reached start of next game in file */
8729         if (appData.debugMode)
8730           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8731         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8732                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8733           case MT_NONE:
8734           case MT_CHECK:
8735             break;
8736           case MT_CHECKMATE:
8737           case MT_STAINMATE:
8738             if (WhiteOnMove(currentMove)) {
8739                 GameEnds(BlackWins, "Black mates", GE_FILE);
8740             } else {
8741                 GameEnds(WhiteWins, "White mates", GE_FILE);
8742             }
8743             break;
8744           case MT_STALEMATE:
8745             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8746             break;
8747         }
8748         done = TRUE;
8749         break;
8750
8751       case PositionDiagram:     /* should not happen; ignore */
8752       case ElapsedTime:         /* ignore */
8753       case NAG:                 /* ignore */
8754         if (appData.debugMode)
8755           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8756                   yy_text, (int) moveType);
8757         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8758
8759       case IllegalMove:
8760         if (appData.testLegality) {
8761             if (appData.debugMode)
8762               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8763             sprintf(move, _("Illegal move: %d.%s%s"),
8764                     (forwardMostMove / 2) + 1,
8765                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8766             DisplayError(move, 0);
8767             done = TRUE;
8768         } else {
8769             if (appData.debugMode)
8770               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8771                       yy_text, currentMoveString);
8772             fromX = currentMoveString[0] - AAA;
8773             fromY = currentMoveString[1] - ONE;
8774             toX = currentMoveString[2] - AAA;
8775             toY = currentMoveString[3] - ONE;
8776             promoChar = currentMoveString[4];
8777         }
8778         break;
8779
8780       case AmbiguousMove:
8781         if (appData.debugMode)
8782           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8783         sprintf(move, _("Ambiguous move: %d.%s%s"),
8784                 (forwardMostMove / 2) + 1,
8785                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8786         DisplayError(move, 0);
8787         done = TRUE;
8788         break;
8789
8790       default:
8791       case ImpossibleMove:
8792         if (appData.debugMode)
8793           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8794         sprintf(move, _("Illegal move: %d.%s%s"),
8795                 (forwardMostMove / 2) + 1,
8796                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8797         DisplayError(move, 0);
8798         done = TRUE;
8799         break;
8800     }
8801
8802     if (done) {
8803         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8804             DrawPosition(FALSE, boards[currentMove]);
8805             DisplayBothClocks();
8806             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8807               DisplayComment(currentMove - 1, commentList[currentMove]);
8808         }
8809         (void) StopLoadGameTimer();
8810         gameFileFP = NULL;
8811         cmailOldMove = forwardMostMove;
8812         return FALSE;
8813     } else {
8814         /* currentMoveString is set as a side-effect of yylex */
8815         strcat(currentMoveString, "\n");
8816         strcpy(moveList[forwardMostMove], currentMoveString);
8817         
8818         thinkOutput[0] = NULLCHAR;
8819         MakeMove(fromX, fromY, toX, toY, promoChar);
8820         currentMove = forwardMostMove;
8821         return TRUE;
8822     }
8823 }
8824
8825 /* Load the nth game from the given file */
8826 int
8827 LoadGameFromFile(filename, n, title, useList)
8828      char *filename;
8829      int n;
8830      char *title;
8831      /*Boolean*/ int useList;
8832 {
8833     FILE *f;
8834     char buf[MSG_SIZ];
8835
8836     if (strcmp(filename, "-") == 0) {
8837         f = stdin;
8838         title = "stdin";
8839     } else {
8840         f = fopen(filename, "rb");
8841         if (f == NULL) {
8842           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8843             DisplayError(buf, errno);
8844             return FALSE;
8845         }
8846     }
8847     if (fseek(f, 0, 0) == -1) {
8848         /* f is not seekable; probably a pipe */
8849         useList = FALSE;
8850     }
8851     if (useList && n == 0) {
8852         int error = GameListBuild(f);
8853         if (error) {
8854             DisplayError(_("Cannot build game list"), error);
8855         } else if (!ListEmpty(&gameList) &&
8856                    ((ListGame *) gameList.tailPred)->number > 1) {
8857             GameListPopUp(f, title);
8858             return TRUE;
8859         }
8860         GameListDestroy();
8861         n = 1;
8862     }
8863     if (n == 0) n = 1;
8864     return LoadGame(f, n, title, FALSE);
8865 }
8866
8867
8868 void
8869 MakeRegisteredMove()
8870 {
8871     int fromX, fromY, toX, toY;
8872     char promoChar;
8873     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8874         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8875           case CMAIL_MOVE:
8876           case CMAIL_DRAW:
8877             if (appData.debugMode)
8878               fprintf(debugFP, "Restoring %s for game %d\n",
8879                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8880     
8881             thinkOutput[0] = NULLCHAR;
8882             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8883             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8884             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8885             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8886             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8887             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8888             MakeMove(fromX, fromY, toX, toY, promoChar);
8889             ShowMove(fromX, fromY, toX, toY);
8890               
8891             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8892                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8893               case MT_NONE:
8894               case MT_CHECK:
8895                 break;
8896                 
8897               case MT_CHECKMATE:
8898               case MT_STAINMATE:
8899                 if (WhiteOnMove(currentMove)) {
8900                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8901                 } else {
8902                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8903                 }
8904                 break;
8905                 
8906               case MT_STALEMATE:
8907                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8908                 break;
8909             }
8910
8911             break;
8912             
8913           case CMAIL_RESIGN:
8914             if (WhiteOnMove(currentMove)) {
8915                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8916             } else {
8917                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8918             }
8919             break;
8920             
8921           case CMAIL_ACCEPT:
8922             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8923             break;
8924               
8925           default:
8926             break;
8927         }
8928     }
8929
8930     return;
8931 }
8932
8933 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8934 int
8935 CmailLoadGame(f, gameNumber, title, useList)
8936      FILE *f;
8937      int gameNumber;
8938      char *title;
8939      int useList;
8940 {
8941     int retVal;
8942
8943     if (gameNumber > nCmailGames) {
8944         DisplayError(_("No more games in this message"), 0);
8945         return FALSE;
8946     }
8947     if (f == lastLoadGameFP) {
8948         int offset = gameNumber - lastLoadGameNumber;
8949         if (offset == 0) {
8950             cmailMsg[0] = NULLCHAR;
8951             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8952                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8953                 nCmailMovesRegistered--;
8954             }
8955             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8956             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8957                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8958             }
8959         } else {
8960             if (! RegisterMove()) return FALSE;
8961         }
8962     }
8963
8964     retVal = LoadGame(f, gameNumber, title, useList);
8965
8966     /* Make move registered during previous look at this game, if any */
8967     MakeRegisteredMove();
8968
8969     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8970         commentList[currentMove]
8971           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8972         DisplayComment(currentMove - 1, commentList[currentMove]);
8973     }
8974
8975     return retVal;
8976 }
8977
8978 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8979 int
8980 ReloadGame(offset)
8981      int offset;
8982 {
8983     int gameNumber = lastLoadGameNumber + offset;
8984     if (lastLoadGameFP == NULL) {
8985         DisplayError(_("No game has been loaded yet"), 0);
8986         return FALSE;
8987     }
8988     if (gameNumber <= 0) {
8989         DisplayError(_("Can't back up any further"), 0);
8990         return FALSE;
8991     }
8992     if (cmailMsgLoaded) {
8993         return CmailLoadGame(lastLoadGameFP, gameNumber,
8994                              lastLoadGameTitle, lastLoadGameUseList);
8995     } else {
8996         return LoadGame(lastLoadGameFP, gameNumber,
8997                         lastLoadGameTitle, lastLoadGameUseList);
8998     }
8999 }
9000
9001
9002
9003 /* Load the nth game from open file f */
9004 int
9005 LoadGame(f, gameNumber, title, useList)
9006      FILE *f;
9007      int gameNumber;
9008      char *title;
9009      int useList;
9010 {
9011     ChessMove cm;
9012     char buf[MSG_SIZ];
9013     int gn = gameNumber;
9014     ListGame *lg = NULL;
9015     int numPGNTags = 0;
9016     int err;
9017     GameMode oldGameMode;
9018     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9019
9020     if (appData.debugMode) 
9021         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9022
9023     if (gameMode == Training )
9024         SetTrainingModeOff();
9025
9026     oldGameMode = gameMode;
9027     if (gameMode != BeginningOfGame) {
9028       Reset(FALSE, TRUE);
9029     }
9030
9031     gameFileFP = f;
9032     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9033         fclose(lastLoadGameFP);
9034     }
9035
9036     if (useList) {
9037         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9038         
9039         if (lg) {
9040             fseek(f, lg->offset, 0);
9041             GameListHighlight(gameNumber);
9042             gn = 1;
9043         }
9044         else {
9045             DisplayError(_("Game number out of range"), 0);
9046             return FALSE;
9047         }
9048     } else {
9049         GameListDestroy();
9050         if (fseek(f, 0, 0) == -1) {
9051             if (f == lastLoadGameFP ?
9052                 gameNumber == lastLoadGameNumber + 1 :
9053                 gameNumber == 1) {
9054                 gn = 1;
9055             } else {
9056                 DisplayError(_("Can't seek on game file"), 0);
9057                 return FALSE;
9058             }
9059         }
9060     }
9061     lastLoadGameFP = f;
9062     lastLoadGameNumber = gameNumber;
9063     strcpy(lastLoadGameTitle, title);
9064     lastLoadGameUseList = useList;
9065
9066     yynewfile(f);
9067
9068     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9069       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9070                 lg->gameInfo.black);
9071             DisplayTitle(buf);
9072     } else if (*title != NULLCHAR) {
9073         if (gameNumber > 1) {
9074             sprintf(buf, "%s %d", title, gameNumber);
9075             DisplayTitle(buf);
9076         } else {
9077             DisplayTitle(title);
9078         }
9079     }
9080
9081     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9082         gameMode = PlayFromGameFile;
9083         ModeHighlight();
9084     }
9085
9086     currentMove = forwardMostMove = backwardMostMove = 0;
9087     CopyBoard(boards[0], initialPosition);
9088     StopClocks();
9089
9090     /*
9091      * Skip the first gn-1 games in the file.
9092      * Also skip over anything that precedes an identifiable 
9093      * start of game marker, to avoid being confused by 
9094      * garbage at the start of the file.  Currently 
9095      * recognized start of game markers are the move number "1",
9096      * the pattern "gnuchess .* game", the pattern
9097      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9098      * A game that starts with one of the latter two patterns
9099      * will also have a move number 1, possibly
9100      * following a position diagram.
9101      * 5-4-02: Let's try being more lenient and allowing a game to
9102      * start with an unnumbered move.  Does that break anything?
9103      */
9104     cm = lastLoadGameStart = (ChessMove) 0;
9105     while (gn > 0) {
9106         yyboardindex = forwardMostMove;
9107         cm = (ChessMove) yylex();
9108         switch (cm) {
9109           case (ChessMove) 0:
9110             if (cmailMsgLoaded) {
9111                 nCmailGames = CMAIL_MAX_GAMES - gn;
9112             } else {
9113                 Reset(TRUE, TRUE);
9114                 DisplayError(_("Game not found in file"), 0);
9115             }
9116             return FALSE;
9117
9118           case GNUChessGame:
9119           case XBoardGame:
9120             gn--;
9121             lastLoadGameStart = cm;
9122             break;
9123             
9124           case MoveNumberOne:
9125             switch (lastLoadGameStart) {
9126               case GNUChessGame:
9127               case XBoardGame:
9128               case PGNTag:
9129                 break;
9130               case MoveNumberOne:
9131               case (ChessMove) 0:
9132                 gn--;           /* count this game */
9133                 lastLoadGameStart = cm;
9134                 break;
9135               default:
9136                 /* impossible */
9137                 break;
9138             }
9139             break;
9140
9141           case PGNTag:
9142             switch (lastLoadGameStart) {
9143               case GNUChessGame:
9144               case PGNTag:
9145               case MoveNumberOne:
9146               case (ChessMove) 0:
9147                 gn--;           /* count this game */
9148                 lastLoadGameStart = cm;
9149                 break;
9150               case XBoardGame:
9151                 lastLoadGameStart = cm; /* game counted already */
9152                 break;
9153               default:
9154                 /* impossible */
9155                 break;
9156             }
9157             if (gn > 0) {
9158                 do {
9159                     yyboardindex = forwardMostMove;
9160                     cm = (ChessMove) yylex();
9161                 } while (cm == PGNTag || cm == Comment);
9162             }
9163             break;
9164
9165           case WhiteWins:
9166           case BlackWins:
9167           case GameIsDrawn:
9168             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9169                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9170                     != CMAIL_OLD_RESULT) {
9171                     nCmailResults ++ ;
9172                     cmailResult[  CMAIL_MAX_GAMES
9173                                 - gn - 1] = CMAIL_OLD_RESULT;
9174                 }
9175             }
9176             break;
9177
9178           case NormalMove:
9179             /* Only a NormalMove can be at the start of a game
9180              * without a position diagram. */
9181             if (lastLoadGameStart == (ChessMove) 0) {
9182               gn--;
9183               lastLoadGameStart = MoveNumberOne;
9184             }
9185             break;
9186
9187           default:
9188             break;
9189         }
9190     }
9191     
9192     if (appData.debugMode)
9193       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9194
9195     if (cm == XBoardGame) {
9196         /* Skip any header junk before position diagram and/or move 1 */
9197         for (;;) {
9198             yyboardindex = forwardMostMove;
9199             cm = (ChessMove) yylex();
9200
9201             if (cm == (ChessMove) 0 ||
9202                 cm == GNUChessGame || cm == XBoardGame) {
9203                 /* Empty game; pretend end-of-file and handle later */
9204                 cm = (ChessMove) 0;
9205                 break;
9206             }
9207
9208             if (cm == MoveNumberOne || cm == PositionDiagram ||
9209                 cm == PGNTag || cm == Comment)
9210               break;
9211         }
9212     } else if (cm == GNUChessGame) {
9213         if (gameInfo.event != NULL) {
9214             free(gameInfo.event);
9215         }
9216         gameInfo.event = StrSave(yy_text);
9217     }   
9218
9219     startedFromSetupPosition = FALSE;
9220     while (cm == PGNTag) {
9221         if (appData.debugMode) 
9222           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9223         err = ParsePGNTag(yy_text, &gameInfo);
9224         if (!err) numPGNTags++;
9225
9226         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9227         if(gameInfo.variant != oldVariant) {
9228             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9229             InitPosition(TRUE);
9230             oldVariant = gameInfo.variant;
9231             if (appData.debugMode) 
9232               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9233         }
9234
9235
9236         if (gameInfo.fen != NULL) {
9237           Board initial_position;
9238           startedFromSetupPosition = TRUE;
9239           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9240             Reset(TRUE, TRUE);
9241             DisplayError(_("Bad FEN position in file"), 0);
9242             return FALSE;
9243           }
9244           CopyBoard(boards[0], initial_position);
9245           if (blackPlaysFirst) {
9246             currentMove = forwardMostMove = backwardMostMove = 1;
9247             CopyBoard(boards[1], initial_position);
9248             strcpy(moveList[0], "");
9249             strcpy(parseList[0], "");
9250             timeRemaining[0][1] = whiteTimeRemaining;
9251             timeRemaining[1][1] = blackTimeRemaining;
9252             if (commentList[0] != NULL) {
9253               commentList[1] = commentList[0];
9254               commentList[0] = NULL;
9255             }
9256           } else {
9257             currentMove = forwardMostMove = backwardMostMove = 0;
9258           }
9259           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9260           {   int i;
9261               initialRulePlies = FENrulePlies;
9262               epStatus[forwardMostMove] = FENepStatus;
9263               for( i=0; i< nrCastlingRights; i++ )
9264                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9265           }
9266           yyboardindex = forwardMostMove;
9267           free(gameInfo.fen);
9268           gameInfo.fen = NULL;
9269         }
9270
9271         yyboardindex = forwardMostMove;
9272         cm = (ChessMove) yylex();
9273
9274         /* Handle comments interspersed among the tags */
9275         while (cm == Comment) {
9276             char *p;
9277             if (appData.debugMode) 
9278               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9279             p = yy_text;
9280             if (*p == '{' || *p == '[' || *p == '(') {
9281                 p[strlen(p) - 1] = NULLCHAR;
9282                 p++;
9283             }
9284             while (*p == '\n') p++;
9285             AppendComment(currentMove, p);
9286             yyboardindex = forwardMostMove;
9287             cm = (ChessMove) yylex();
9288         }
9289     }
9290
9291     /* don't rely on existence of Event tag since if game was
9292      * pasted from clipboard the Event tag may not exist
9293      */
9294     if (numPGNTags > 0){
9295         char *tags;
9296         if (gameInfo.variant == VariantNormal) {
9297           gameInfo.variant = StringToVariant(gameInfo.event);
9298         }
9299         if (!matchMode) {
9300           if( appData.autoDisplayTags ) {
9301             tags = PGNTags(&gameInfo);
9302             TagsPopUp(tags, CmailMsg());
9303             free(tags);
9304           }
9305         }
9306     } else {
9307         /* Make something up, but don't display it now */
9308         SetGameInfo();
9309         TagsPopDown();
9310     }
9311
9312     if (cm == PositionDiagram) {
9313         int i, j;
9314         char *p;
9315         Board initial_position;
9316
9317         if (appData.debugMode)
9318           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9319
9320         if (!startedFromSetupPosition) {
9321             p = yy_text;
9322             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9323               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9324                 switch (*p) {
9325                   case '[':
9326                   case '-':
9327                   case ' ':
9328                   case '\t':
9329                   case '\n':
9330                   case '\r':
9331                     break;
9332                   default:
9333                     initial_position[i][j++] = CharToPiece(*p);
9334                     break;
9335                 }
9336             while (*p == ' ' || *p == '\t' ||
9337                    *p == '\n' || *p == '\r') p++;
9338         
9339             if (strncmp(p, "black", strlen("black"))==0)
9340               blackPlaysFirst = TRUE;
9341             else
9342               blackPlaysFirst = FALSE;
9343             startedFromSetupPosition = TRUE;
9344         
9345             CopyBoard(boards[0], initial_position);
9346             if (blackPlaysFirst) {
9347                 currentMove = forwardMostMove = backwardMostMove = 1;
9348                 CopyBoard(boards[1], initial_position);
9349                 strcpy(moveList[0], "");
9350                 strcpy(parseList[0], "");
9351                 timeRemaining[0][1] = whiteTimeRemaining;
9352                 timeRemaining[1][1] = blackTimeRemaining;
9353                 if (commentList[0] != NULL) {
9354                     commentList[1] = commentList[0];
9355                     commentList[0] = NULL;
9356                 }
9357             } else {
9358                 currentMove = forwardMostMove = backwardMostMove = 0;
9359             }
9360         }
9361         yyboardindex = forwardMostMove;
9362         cm = (ChessMove) yylex();
9363     }
9364
9365     if (first.pr == NoProc) {
9366         StartChessProgram(&first);
9367     }
9368     InitChessProgram(&first, FALSE);
9369     SendToProgram("force\n", &first);
9370     if (startedFromSetupPosition) {
9371         SendBoard(&first, forwardMostMove);
9372     if (appData.debugMode) {
9373         fprintf(debugFP, "Load Game\n");
9374     }
9375         DisplayBothClocks();
9376     }      
9377
9378     /* [HGM] server: flag to write setup moves in broadcast file as one */
9379     loadFlag = appData.suppressLoadMoves;
9380
9381     while (cm == Comment) {
9382         char *p;
9383         if (appData.debugMode) 
9384           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9385         p = yy_text;
9386         if (*p == '{' || *p == '[' || *p == '(') {
9387             p[strlen(p) - 1] = NULLCHAR;
9388             p++;
9389         }
9390         while (*p == '\n') p++;
9391         AppendComment(currentMove, p);
9392         yyboardindex = forwardMostMove;
9393         cm = (ChessMove) yylex();
9394     }
9395
9396     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9397         cm == WhiteWins || cm == BlackWins ||
9398         cm == GameIsDrawn || cm == GameUnfinished) {
9399         DisplayMessage("", _("No moves in game"));
9400         if (cmailMsgLoaded) {
9401             if (appData.debugMode)
9402               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9403             ClearHighlights();
9404             flipView = FALSE;
9405         }
9406         DrawPosition(FALSE, boards[currentMove]);
9407         DisplayBothClocks();
9408         gameMode = EditGame;
9409         ModeHighlight();
9410         gameFileFP = NULL;
9411         cmailOldMove = 0;
9412         return TRUE;
9413     }
9414
9415     // [HGM] PV info: routine tests if comment empty
9416     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9417         DisplayComment(currentMove - 1, commentList[currentMove]);
9418     }
9419     if (!matchMode && appData.timeDelay != 0) 
9420       DrawPosition(FALSE, boards[currentMove]);
9421
9422     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9423       programStats.ok_to_send = 1;
9424     }
9425
9426     /* if the first token after the PGN tags is a move
9427      * and not move number 1, retrieve it from the parser 
9428      */
9429     if (cm != MoveNumberOne)
9430         LoadGameOneMove(cm);
9431
9432     /* load the remaining moves from the file */
9433     while (LoadGameOneMove((ChessMove)0)) {
9434       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9435       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9436     }
9437
9438     /* rewind to the start of the game */
9439     currentMove = backwardMostMove;
9440
9441     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9442
9443     if (oldGameMode == AnalyzeFile ||
9444         oldGameMode == AnalyzeMode) {
9445       AnalyzeFileEvent();
9446     }
9447
9448     if (matchMode || appData.timeDelay == 0) {
9449       ToEndEvent();
9450       gameMode = EditGame;
9451       ModeHighlight();
9452     } else if (appData.timeDelay > 0) {
9453       AutoPlayGameLoop();
9454     }
9455
9456     if (appData.debugMode) 
9457         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9458
9459     loadFlag = 0; /* [HGM] true game starts */
9460     return TRUE;
9461 }
9462
9463 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9464 int
9465 ReloadPosition(offset)
9466      int offset;
9467 {
9468     int positionNumber = lastLoadPositionNumber + offset;
9469     if (lastLoadPositionFP == NULL) {
9470         DisplayError(_("No position has been loaded yet"), 0);
9471         return FALSE;
9472     }
9473     if (positionNumber <= 0) {
9474         DisplayError(_("Can't back up any further"), 0);
9475         return FALSE;
9476     }
9477     return LoadPosition(lastLoadPositionFP, positionNumber,
9478                         lastLoadPositionTitle);
9479 }
9480
9481 /* Load the nth position from the given file */
9482 int
9483 LoadPositionFromFile(filename, n, title)
9484      char *filename;
9485      int n;
9486      char *title;
9487 {
9488     FILE *f;
9489     char buf[MSG_SIZ];
9490
9491     if (strcmp(filename, "-") == 0) {
9492         return LoadPosition(stdin, n, "stdin");
9493     } else {
9494         f = fopen(filename, "rb");
9495         if (f == NULL) {
9496             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9497             DisplayError(buf, errno);
9498             return FALSE;
9499         } else {
9500             return LoadPosition(f, n, title);
9501         }
9502     }
9503 }
9504
9505 /* Load the nth position from the given open file, and close it */
9506 int
9507 LoadPosition(f, positionNumber, title)
9508      FILE *f;
9509      int positionNumber;
9510      char *title;
9511 {
9512     char *p, line[MSG_SIZ];
9513     Board initial_position;
9514     int i, j, fenMode, pn;
9515     
9516     if (gameMode == Training )
9517         SetTrainingModeOff();
9518
9519     if (gameMode != BeginningOfGame) {
9520         Reset(FALSE, TRUE);
9521     }
9522     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9523         fclose(lastLoadPositionFP);
9524     }
9525     if (positionNumber == 0) positionNumber = 1;
9526     lastLoadPositionFP = f;
9527     lastLoadPositionNumber = positionNumber;
9528     strcpy(lastLoadPositionTitle, title);
9529     if (first.pr == NoProc) {
9530       StartChessProgram(&first);
9531       InitChessProgram(&first, FALSE);
9532     }    
9533     pn = positionNumber;
9534     if (positionNumber < 0) {
9535         /* Negative position number means to seek to that byte offset */
9536         if (fseek(f, -positionNumber, 0) == -1) {
9537             DisplayError(_("Can't seek on position file"), 0);
9538             return FALSE;
9539         };
9540         pn = 1;
9541     } else {
9542         if (fseek(f, 0, 0) == -1) {
9543             if (f == lastLoadPositionFP ?
9544                 positionNumber == lastLoadPositionNumber + 1 :
9545                 positionNumber == 1) {
9546                 pn = 1;
9547             } else {
9548                 DisplayError(_("Can't seek on position file"), 0);
9549                 return FALSE;
9550             }
9551         }
9552     }
9553     /* See if this file is FEN or old-style xboard */
9554     if (fgets(line, MSG_SIZ, f) == NULL) {
9555         DisplayError(_("Position not found in file"), 0);
9556         return FALSE;
9557     }
9558     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9559     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9560
9561     if (pn >= 2) {
9562         if (fenMode || line[0] == '#') pn--;
9563         while (pn > 0) {
9564             /* skip positions before number pn */
9565             if (fgets(line, MSG_SIZ, f) == NULL) {
9566                 Reset(TRUE, TRUE);
9567                 DisplayError(_("Position not found in file"), 0);
9568                 return FALSE;
9569             }
9570             if (fenMode || line[0] == '#') pn--;
9571         }
9572     }
9573
9574     if (fenMode) {
9575         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9576             DisplayError(_("Bad FEN position in file"), 0);
9577             return FALSE;
9578         }
9579     } else {
9580         (void) fgets(line, MSG_SIZ, f);
9581         (void) fgets(line, MSG_SIZ, f);
9582     
9583         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9584             (void) fgets(line, MSG_SIZ, f);
9585             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9586                 if (*p == ' ')
9587                   continue;
9588                 initial_position[i][j++] = CharToPiece(*p);
9589             }
9590         }
9591     
9592         blackPlaysFirst = FALSE;
9593         if (!feof(f)) {
9594             (void) fgets(line, MSG_SIZ, f);
9595             if (strncmp(line, "black", strlen("black"))==0)
9596               blackPlaysFirst = TRUE;
9597         }
9598     }
9599     startedFromSetupPosition = TRUE;
9600     
9601     SendToProgram("force\n", &first);
9602     CopyBoard(boards[0], initial_position);
9603     if (blackPlaysFirst) {
9604         currentMove = forwardMostMove = backwardMostMove = 1;
9605         strcpy(moveList[0], "");
9606         strcpy(parseList[0], "");
9607         CopyBoard(boards[1], initial_position);
9608         DisplayMessage("", _("Black to play"));
9609     } else {
9610         currentMove = forwardMostMove = backwardMostMove = 0;
9611         DisplayMessage("", _("White to play"));
9612     }
9613           /* [HGM] copy FEN attributes as well */
9614           {   int i;
9615               initialRulePlies = FENrulePlies;
9616               epStatus[forwardMostMove] = FENepStatus;
9617               for( i=0; i< nrCastlingRights; i++ )
9618                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9619           }
9620     SendBoard(&first, forwardMostMove);
9621     if (appData.debugMode) {
9622 int i, j;
9623   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9624   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9625         fprintf(debugFP, "Load Position\n");
9626     }
9627
9628     if (positionNumber > 1) {
9629         sprintf(line, "%s %d", title, positionNumber);
9630         DisplayTitle(line);
9631     } else {
9632         DisplayTitle(title);
9633     }
9634     gameMode = EditGame;
9635     ModeHighlight();
9636     ResetClocks();
9637     timeRemaining[0][1] = whiteTimeRemaining;
9638     timeRemaining[1][1] = blackTimeRemaining;
9639     DrawPosition(FALSE, boards[currentMove]);
9640    
9641     return TRUE;
9642 }
9643
9644
9645 void
9646 CopyPlayerNameIntoFileName(dest, src)
9647      char **dest, *src;
9648 {
9649     while (*src != NULLCHAR && *src != ',') {
9650         if (*src == ' ') {
9651             *(*dest)++ = '_';
9652             src++;
9653         } else {
9654             *(*dest)++ = *src++;
9655         }
9656     }
9657 }
9658
9659 char *DefaultFileName(ext)
9660      char *ext;
9661 {
9662     static char def[MSG_SIZ];
9663     char *p;
9664
9665     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9666         p = def;
9667         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9668         *p++ = '-';
9669         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9670         *p++ = '.';
9671         strcpy(p, ext);
9672     } else {
9673         def[0] = NULLCHAR;
9674     }
9675     return def;
9676 }
9677
9678 /* Save the current game to the given file */
9679 int
9680 SaveGameToFile(filename, append)
9681      char *filename;
9682      int append;
9683 {
9684     FILE *f;
9685     char buf[MSG_SIZ];
9686
9687     if (strcmp(filename, "-") == 0) {
9688         return SaveGame(stdout, 0, NULL);
9689     } else {
9690         f = fopen(filename, append ? "a" : "w");
9691         if (f == NULL) {
9692             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9693             DisplayError(buf, errno);
9694             return FALSE;
9695         } else {
9696             return SaveGame(f, 0, NULL);
9697         }
9698     }
9699 }
9700
9701 char *
9702 SavePart(str)
9703      char *str;
9704 {
9705     static char buf[MSG_SIZ];
9706     char *p;
9707     
9708     p = strchr(str, ' ');
9709     if (p == NULL) return str;
9710     strncpy(buf, str, p - str);
9711     buf[p - str] = NULLCHAR;
9712     return buf;
9713 }
9714
9715 #define PGN_MAX_LINE 75
9716
9717 #define PGN_SIDE_WHITE  0
9718 #define PGN_SIDE_BLACK  1
9719
9720 /* [AS] */
9721 static int FindFirstMoveOutOfBook( int side )
9722 {
9723     int result = -1;
9724
9725     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9726         int index = backwardMostMove;
9727         int has_book_hit = 0;
9728
9729         if( (index % 2) != side ) {
9730             index++;
9731         }
9732
9733         while( index < forwardMostMove ) {
9734             /* Check to see if engine is in book */
9735             int depth = pvInfoList[index].depth;
9736             int score = pvInfoList[index].score;
9737             int in_book = 0;
9738
9739             if( depth <= 2 ) {
9740                 in_book = 1;
9741             }
9742             else if( score == 0 && depth == 63 ) {
9743                 in_book = 1; /* Zappa */
9744             }
9745             else if( score == 2 && depth == 99 ) {
9746                 in_book = 1; /* Abrok */
9747             }
9748
9749             has_book_hit += in_book;
9750
9751             if( ! in_book ) {
9752                 result = index;
9753
9754                 break;
9755             }
9756
9757             index += 2;
9758         }
9759     }
9760
9761     return result;
9762 }
9763
9764 /* [AS] */
9765 void GetOutOfBookInfo( char * buf )
9766 {
9767     int oob[2];
9768     int i;
9769     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9770
9771     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9772     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9773
9774     *buf = '\0';
9775
9776     if( oob[0] >= 0 || oob[1] >= 0 ) {
9777         for( i=0; i<2; i++ ) {
9778             int idx = oob[i];
9779
9780             if( idx >= 0 ) {
9781                 if( i > 0 && oob[0] >= 0 ) {
9782                     strcat( buf, "   " );
9783                 }
9784
9785                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9786                 sprintf( buf+strlen(buf), "%s%.2f", 
9787                     pvInfoList[idx].score >= 0 ? "+" : "",
9788                     pvInfoList[idx].score / 100.0 );
9789             }
9790         }
9791     }
9792 }
9793
9794 /* Save game in PGN style and close the file */
9795 int
9796 SaveGamePGN(f)
9797      FILE *f;
9798 {
9799     int i, offset, linelen, newblock;
9800     time_t tm;
9801 //    char *movetext;
9802     char numtext[32];
9803     int movelen, numlen, blank;
9804     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9805
9806     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9807     
9808     tm = time((time_t *) NULL);
9809     
9810     PrintPGNTags(f, &gameInfo);
9811     
9812     if (backwardMostMove > 0 || startedFromSetupPosition) {
9813         char *fen = PositionToFEN(backwardMostMove, NULL);
9814         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9815         fprintf(f, "\n{--------------\n");
9816         PrintPosition(f, backwardMostMove);
9817         fprintf(f, "--------------}\n");
9818         free(fen);
9819     }
9820     else {
9821         /* [AS] Out of book annotation */
9822         if( appData.saveOutOfBookInfo ) {
9823             char buf[64];
9824
9825             GetOutOfBookInfo( buf );
9826
9827             if( buf[0] != '\0' ) {
9828                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9829             }
9830         }
9831
9832         fprintf(f, "\n");
9833     }
9834
9835     i = backwardMostMove;
9836     linelen = 0;
9837     newblock = TRUE;
9838
9839     while (i < forwardMostMove) {
9840         /* Print comments preceding this move */
9841         if (commentList[i] != NULL) {
9842             if (linelen > 0) fprintf(f, "\n");
9843             fprintf(f, "{\n%s}\n", commentList[i]);
9844             linelen = 0;
9845             newblock = TRUE;
9846         }
9847
9848         /* Format move number */
9849         if ((i % 2) == 0) {
9850             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9851         } else {
9852             if (newblock) {
9853                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9854             } else {
9855                 numtext[0] = NULLCHAR;
9856             }
9857         }
9858         numlen = strlen(numtext);
9859         newblock = FALSE;
9860
9861         /* Print move number */
9862         blank = linelen > 0 && numlen > 0;
9863         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9864             fprintf(f, "\n");
9865             linelen = 0;
9866             blank = 0;
9867         }
9868         if (blank) {
9869             fprintf(f, " ");
9870             linelen++;
9871         }
9872         fprintf(f, "%s", numtext);
9873         linelen += numlen;
9874
9875         /* Get move */
9876         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9877         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9878
9879         /* Print move */
9880         blank = linelen > 0 && movelen > 0;
9881         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9882             fprintf(f, "\n");
9883             linelen = 0;
9884             blank = 0;
9885         }
9886         if (blank) {
9887             fprintf(f, " ");
9888             linelen++;
9889         }
9890         fprintf(f, "%s", move_buffer);
9891         linelen += movelen;
9892
9893         /* [AS] Add PV info if present */
9894         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9895             /* [HGM] add time */
9896             char buf[MSG_SIZ]; int seconds = 0;
9897
9898             if(i >= backwardMostMove) {
9899                 if(WhiteOnMove(i))
9900                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9901                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9902                 else
9903                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9904                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9905             }
9906             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9907
9908             if( seconds <= 0) buf[0] = 0; else
9909             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9910                 seconds = (seconds + 4)/10; // round to full seconds
9911                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9912                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9913             }
9914
9915             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9916                 pvInfoList[i].score >= 0 ? "+" : "",
9917                 pvInfoList[i].score / 100.0,
9918                 pvInfoList[i].depth,
9919                 buf );
9920
9921             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9922
9923             /* Print score/depth */
9924             blank = linelen > 0 && movelen > 0;
9925             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9926                 fprintf(f, "\n");
9927                 linelen = 0;
9928                 blank = 0;
9929             }
9930             if (blank) {
9931                 fprintf(f, " ");
9932                 linelen++;
9933             }
9934             fprintf(f, "%s", move_buffer);
9935             linelen += movelen;
9936         }
9937
9938         i++;
9939     }
9940     
9941     /* Start a new line */
9942     if (linelen > 0) fprintf(f, "\n");
9943
9944     /* Print comments after last move */
9945     if (commentList[i] != NULL) {
9946         fprintf(f, "{\n%s}\n", commentList[i]);
9947     }
9948
9949     /* Print result */
9950     if (gameInfo.resultDetails != NULL &&
9951         gameInfo.resultDetails[0] != NULLCHAR) {
9952         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9953                 PGNResult(gameInfo.result));
9954     } else {
9955         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9956     }
9957
9958     fclose(f);
9959     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9960     return TRUE;
9961 }
9962
9963 /* Save game in old style and close the file */
9964 int
9965 SaveGameOldStyle(f)
9966      FILE *f;
9967 {
9968     int i, offset;
9969     time_t tm;
9970     
9971     tm = time((time_t *) NULL);
9972     
9973     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9974     PrintOpponents(f);
9975     
9976     if (backwardMostMove > 0 || startedFromSetupPosition) {
9977         fprintf(f, "\n[--------------\n");
9978         PrintPosition(f, backwardMostMove);
9979         fprintf(f, "--------------]\n");
9980     } else {
9981         fprintf(f, "\n");
9982     }
9983
9984     i = backwardMostMove;
9985     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9986
9987     while (i < forwardMostMove) {
9988         if (commentList[i] != NULL) {
9989             fprintf(f, "[%s]\n", commentList[i]);
9990         }
9991
9992         if ((i % 2) == 1) {
9993             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9994             i++;
9995         } else {
9996             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9997             i++;
9998             if (commentList[i] != NULL) {
9999                 fprintf(f, "\n");
10000                 continue;
10001             }
10002             if (i >= forwardMostMove) {
10003                 fprintf(f, "\n");
10004                 break;
10005             }
10006             fprintf(f, "%s\n", parseList[i]);
10007             i++;
10008         }
10009     }
10010     
10011     if (commentList[i] != NULL) {
10012         fprintf(f, "[%s]\n", commentList[i]);
10013     }
10014
10015     /* This isn't really the old style, but it's close enough */
10016     if (gameInfo.resultDetails != NULL &&
10017         gameInfo.resultDetails[0] != NULLCHAR) {
10018         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10019                 gameInfo.resultDetails);
10020     } else {
10021         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10022     }
10023
10024     fclose(f);
10025     return TRUE;
10026 }
10027
10028 /* Save the current game to open file f and close the file */
10029 int
10030 SaveGame(f, dummy, dummy2)
10031      FILE *f;
10032      int dummy;
10033      char *dummy2;
10034 {
10035     if (gameMode == EditPosition) EditPositionDone();
10036     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10037     if (appData.oldSaveStyle)
10038       return SaveGameOldStyle(f);
10039     else
10040       return SaveGamePGN(f);
10041 }
10042
10043 /* Save the current position to the given file */
10044 int
10045 SavePositionToFile(filename)
10046      char *filename;
10047 {
10048     FILE *f;
10049     char buf[MSG_SIZ];
10050
10051     if (strcmp(filename, "-") == 0) {
10052         return SavePosition(stdout, 0, NULL);
10053     } else {
10054         f = fopen(filename, "a");
10055         if (f == NULL) {
10056             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10057             DisplayError(buf, errno);
10058             return FALSE;
10059         } else {
10060             SavePosition(f, 0, NULL);
10061             return TRUE;
10062         }
10063     }
10064 }
10065
10066 /* Save the current position to the given open file and close the file */
10067 int
10068 SavePosition(f, dummy, dummy2)
10069      FILE *f;
10070      int dummy;
10071      char *dummy2;
10072 {
10073     time_t tm;
10074     char *fen;
10075     
10076     if (appData.oldSaveStyle) {
10077         tm = time((time_t *) NULL);
10078     
10079         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10080         PrintOpponents(f);
10081         fprintf(f, "[--------------\n");
10082         PrintPosition(f, currentMove);
10083         fprintf(f, "--------------]\n");
10084     } else {
10085         fen = PositionToFEN(currentMove, NULL);
10086         fprintf(f, "%s\n", fen);
10087         free(fen);
10088     }
10089     fclose(f);
10090     return TRUE;
10091 }
10092
10093 void
10094 ReloadCmailMsgEvent(unregister)
10095      int unregister;
10096 {
10097 #if !WIN32
10098     static char *inFilename = NULL;
10099     static char *outFilename;
10100     int i;
10101     struct stat inbuf, outbuf;
10102     int status;
10103     
10104     /* Any registered moves are unregistered if unregister is set, */
10105     /* i.e. invoked by the signal handler */
10106     if (unregister) {
10107         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10108             cmailMoveRegistered[i] = FALSE;
10109             if (cmailCommentList[i] != NULL) {
10110                 free(cmailCommentList[i]);
10111                 cmailCommentList[i] = NULL;
10112             }
10113         }
10114         nCmailMovesRegistered = 0;
10115     }
10116
10117     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10118         cmailResult[i] = CMAIL_NOT_RESULT;
10119     }
10120     nCmailResults = 0;
10121
10122     if (inFilename == NULL) {
10123         /* Because the filenames are static they only get malloced once  */
10124         /* and they never get freed                                      */
10125         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10126         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10127
10128         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10129         sprintf(outFilename, "%s.out", appData.cmailGameName);
10130     }
10131     
10132     status = stat(outFilename, &outbuf);
10133     if (status < 0) {
10134         cmailMailedMove = FALSE;
10135     } else {
10136         status = stat(inFilename, &inbuf);
10137         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10138     }
10139     
10140     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10141        counts the games, notes how each one terminated, etc.
10142        
10143        It would be nice to remove this kludge and instead gather all
10144        the information while building the game list.  (And to keep it
10145        in the game list nodes instead of having a bunch of fixed-size
10146        parallel arrays.)  Note this will require getting each game's
10147        termination from the PGN tags, as the game list builder does
10148        not process the game moves.  --mann
10149        */
10150     cmailMsgLoaded = TRUE;
10151     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10152     
10153     /* Load first game in the file or popup game menu */
10154     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10155
10156 #endif /* !WIN32 */
10157     return;
10158 }
10159
10160 int
10161 RegisterMove()
10162 {
10163     FILE *f;
10164     char string[MSG_SIZ];
10165
10166     if (   cmailMailedMove
10167         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10168         return TRUE;            /* Allow free viewing  */
10169     }
10170
10171     /* Unregister move to ensure that we don't leave RegisterMove        */
10172     /* with the move registered when the conditions for registering no   */
10173     /* longer hold                                                       */
10174     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10175         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10176         nCmailMovesRegistered --;
10177
10178         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10179           {
10180               free(cmailCommentList[lastLoadGameNumber - 1]);
10181               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10182           }
10183     }
10184
10185     if (cmailOldMove == -1) {
10186         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10187         return FALSE;
10188     }
10189
10190     if (currentMove > cmailOldMove + 1) {
10191         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10192         return FALSE;
10193     }
10194
10195     if (currentMove < cmailOldMove) {
10196         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10197         return FALSE;
10198     }
10199
10200     if (forwardMostMove > currentMove) {
10201         /* Silently truncate extra moves */
10202         TruncateGame();
10203     }
10204
10205     if (   (currentMove == cmailOldMove + 1)
10206         || (   (currentMove == cmailOldMove)
10207             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10208                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10209         if (gameInfo.result != GameUnfinished) {
10210             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10211         }
10212
10213         if (commentList[currentMove] != NULL) {
10214             cmailCommentList[lastLoadGameNumber - 1]
10215               = StrSave(commentList[currentMove]);
10216         }
10217         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10218
10219         if (appData.debugMode)
10220           fprintf(debugFP, "Saving %s for game %d\n",
10221                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10222
10223         sprintf(string,
10224                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10225         
10226         f = fopen(string, "w");
10227         if (appData.oldSaveStyle) {
10228             SaveGameOldStyle(f); /* also closes the file */
10229             
10230             sprintf(string, "%s.pos.out", appData.cmailGameName);
10231             f = fopen(string, "w");
10232             SavePosition(f, 0, NULL); /* also closes the file */
10233         } else {
10234             fprintf(f, "{--------------\n");
10235             PrintPosition(f, currentMove);
10236             fprintf(f, "--------------}\n\n");
10237             
10238             SaveGame(f, 0, NULL); /* also closes the file*/
10239         }
10240         
10241         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10242         nCmailMovesRegistered ++;
10243     } else if (nCmailGames == 1) {
10244         DisplayError(_("You have not made a move yet"), 0);
10245         return FALSE;
10246     }
10247
10248     return TRUE;
10249 }
10250
10251 void
10252 MailMoveEvent()
10253 {
10254 #if !WIN32
10255     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10256     FILE *commandOutput;
10257     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10258     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10259     int nBuffers;
10260     int i;
10261     int archived;
10262     char *arcDir;
10263
10264     if (! cmailMsgLoaded) {
10265         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10266         return;
10267     }
10268
10269     if (nCmailGames == nCmailResults) {
10270         DisplayError(_("No unfinished games"), 0);
10271         return;
10272     }
10273
10274 #if CMAIL_PROHIBIT_REMAIL
10275     if (cmailMailedMove) {
10276         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);
10277         DisplayError(msg, 0);
10278         return;
10279     }
10280 #endif
10281
10282     if (! (cmailMailedMove || RegisterMove())) return;
10283     
10284     if (   cmailMailedMove
10285         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10286         sprintf(string, partCommandString,
10287                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10288         commandOutput = popen(string, "r");
10289
10290         if (commandOutput == NULL) {
10291             DisplayError(_("Failed to invoke cmail"), 0);
10292         } else {
10293             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10294                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10295             }
10296             if (nBuffers > 1) {
10297                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10298                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10299                 nBytes = MSG_SIZ - 1;
10300             } else {
10301                 (void) memcpy(msg, buffer, nBytes);
10302             }
10303             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10304
10305             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10306                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10307
10308                 archived = TRUE;
10309                 for (i = 0; i < nCmailGames; i ++) {
10310                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10311                         archived = FALSE;
10312                     }
10313                 }
10314                 if (   archived
10315                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10316                         != NULL)) {
10317                     sprintf(buffer, "%s/%s.%s.archive",
10318                             arcDir,
10319                             appData.cmailGameName,
10320                             gameInfo.date);
10321                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10322                     cmailMsgLoaded = FALSE;
10323                 }
10324             }
10325
10326             DisplayInformation(msg);
10327             pclose(commandOutput);
10328         }
10329     } else {
10330         if ((*cmailMsg) != '\0') {
10331             DisplayInformation(cmailMsg);
10332         }
10333     }
10334
10335     return;
10336 #endif /* !WIN32 */
10337 }
10338
10339 char *
10340 CmailMsg()
10341 {
10342 #if WIN32
10343     return NULL;
10344 #else
10345     int  prependComma = 0;
10346     char number[5];
10347     char string[MSG_SIZ];       /* Space for game-list */
10348     int  i;
10349     
10350     if (!cmailMsgLoaded) return "";
10351
10352     if (cmailMailedMove) {
10353         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10354     } else {
10355         /* Create a list of games left */
10356         sprintf(string, "[");
10357         for (i = 0; i < nCmailGames; i ++) {
10358             if (! (   cmailMoveRegistered[i]
10359                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10360                 if (prependComma) {
10361                     sprintf(number, ",%d", i + 1);
10362                 } else {
10363                     sprintf(number, "%d", i + 1);
10364                     prependComma = 1;
10365                 }
10366                 
10367                 strcat(string, number);
10368             }
10369         }
10370         strcat(string, "]");
10371
10372         if (nCmailMovesRegistered + nCmailResults == 0) {
10373             switch (nCmailGames) {
10374               case 1:
10375                 sprintf(cmailMsg,
10376                         _("Still need to make move for game\n"));
10377                 break;
10378                 
10379               case 2:
10380                 sprintf(cmailMsg,
10381                         _("Still need to make moves for both games\n"));
10382                 break;
10383                 
10384               default:
10385                 sprintf(cmailMsg,
10386                         _("Still need to make moves for all %d games\n"),
10387                         nCmailGames);
10388                 break;
10389             }
10390         } else {
10391             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10392               case 1:
10393                 sprintf(cmailMsg,
10394                         _("Still need to make a move for game %s\n"),
10395                         string);
10396                 break;
10397                 
10398               case 0:
10399                 if (nCmailResults == nCmailGames) {
10400                     sprintf(cmailMsg, _("No unfinished games\n"));
10401                 } else {
10402                     sprintf(cmailMsg, _("Ready to send mail\n"));
10403                 }
10404                 break;
10405                 
10406               default:
10407                 sprintf(cmailMsg,
10408                         _("Still need to make moves for games %s\n"),
10409                         string);
10410             }
10411         }
10412     }
10413     return cmailMsg;
10414 #endif /* WIN32 */
10415 }
10416
10417 void
10418 ResetGameEvent()
10419 {
10420     if (gameMode == Training)
10421       SetTrainingModeOff();
10422
10423     Reset(TRUE, TRUE);
10424     cmailMsgLoaded = FALSE;
10425     if (appData.icsActive) {
10426       SendToICS(ics_prefix);
10427       SendToICS("refresh\n");
10428     }
10429 }
10430
10431 void
10432 ExitEvent(status)
10433      int status;
10434 {
10435     exiting++;
10436     if (exiting > 2) {
10437       /* Give up on clean exit */
10438       exit(status);
10439     }
10440     if (exiting > 1) {
10441       /* Keep trying for clean exit */
10442       return;
10443     }
10444
10445     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10446
10447     if (telnetISR != NULL) {
10448       RemoveInputSource(telnetISR);
10449     }
10450     if (icsPR != NoProc) {
10451       DestroyChildProcess(icsPR, TRUE);
10452     }
10453
10454     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10455     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10456
10457     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10458     /* make sure this other one finishes before killing it!                  */
10459     if(endingGame) { int count = 0;
10460         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10461         while(endingGame && count++ < 10) DoSleep(1);
10462         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10463     }
10464
10465     /* Kill off chess programs */
10466     if (first.pr != NoProc) {
10467         ExitAnalyzeMode();
10468         
10469         DoSleep( appData.delayBeforeQuit );
10470         SendToProgram("quit\n", &first);
10471         DoSleep( appData.delayAfterQuit );
10472         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10473     }
10474     if (second.pr != NoProc) {
10475         DoSleep( appData.delayBeforeQuit );
10476         SendToProgram("quit\n", &second);
10477         DoSleep( appData.delayAfterQuit );
10478         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10479     }
10480     if (first.isr != NULL) {
10481         RemoveInputSource(first.isr);
10482     }
10483     if (second.isr != NULL) {
10484         RemoveInputSource(second.isr);
10485     }
10486
10487     ShutDownFrontEnd();
10488     exit(status);
10489 }
10490
10491 void
10492 PauseEvent()
10493 {
10494     if (appData.debugMode)
10495         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10496     if (pausing) {
10497         pausing = FALSE;
10498         ModeHighlight();
10499         if (gameMode == MachinePlaysWhite ||
10500             gameMode == MachinePlaysBlack) {
10501             StartClocks();
10502         } else {
10503             DisplayBothClocks();
10504         }
10505         if (gameMode == PlayFromGameFile) {
10506             if (appData.timeDelay >= 0) 
10507                 AutoPlayGameLoop();
10508         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10509             Reset(FALSE, TRUE);
10510             SendToICS(ics_prefix);
10511             SendToICS("refresh\n");
10512         } else if (currentMove < forwardMostMove) {
10513             ForwardInner(forwardMostMove);
10514         }
10515         pauseExamInvalid = FALSE;
10516     } else {
10517         switch (gameMode) {
10518           default:
10519             return;
10520           case IcsExamining:
10521             pauseExamForwardMostMove = forwardMostMove;
10522             pauseExamInvalid = FALSE;
10523             /* fall through */
10524           case IcsObserving:
10525           case IcsPlayingWhite:
10526           case IcsPlayingBlack:
10527             pausing = TRUE;
10528             ModeHighlight();
10529             return;
10530           case PlayFromGameFile:
10531             (void) StopLoadGameTimer();
10532             pausing = TRUE;
10533             ModeHighlight();
10534             break;
10535           case BeginningOfGame:
10536             if (appData.icsActive) return;
10537             /* else fall through */
10538           case MachinePlaysWhite:
10539           case MachinePlaysBlack:
10540           case TwoMachinesPlay:
10541             if (forwardMostMove == 0)
10542               return;           /* don't pause if no one has moved */
10543             if ((gameMode == MachinePlaysWhite &&
10544                  !WhiteOnMove(forwardMostMove)) ||
10545                 (gameMode == MachinePlaysBlack &&
10546                  WhiteOnMove(forwardMostMove))) {
10547                 StopClocks();
10548             }
10549             pausing = TRUE;
10550             ModeHighlight();
10551             break;
10552         }
10553     }
10554 }
10555
10556 void
10557 EditCommentEvent()
10558 {
10559     char title[MSG_SIZ];
10560
10561     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10562         strcpy(title, _("Edit comment"));
10563     } else {
10564         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10565                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10566                 parseList[currentMove - 1]);
10567     }
10568
10569     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10570 }
10571
10572
10573 void
10574 EditTagsEvent()
10575 {
10576     char *tags = PGNTags(&gameInfo);
10577     EditTagsPopUp(tags);
10578     free(tags);
10579 }
10580
10581 void
10582 AnalyzeModeEvent()
10583 {
10584     if (appData.noChessProgram || gameMode == AnalyzeMode)
10585       return;
10586
10587     if (gameMode != AnalyzeFile) {
10588         if (!appData.icsEngineAnalyze) {
10589                EditGameEvent();
10590                if (gameMode != EditGame) return;
10591         }
10592         ResurrectChessProgram();
10593         SendToProgram("analyze\n", &first);
10594         first.analyzing = TRUE;
10595         /*first.maybeThinking = TRUE;*/
10596         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10597         EngineOutputPopUp();
10598     }
10599     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10600     pausing = FALSE;
10601     ModeHighlight();
10602     SetGameInfo();
10603
10604     StartAnalysisClock();
10605     GetTimeMark(&lastNodeCountTime);
10606     lastNodeCount = 0;
10607 }
10608
10609 void
10610 AnalyzeFileEvent()
10611 {
10612     if (appData.noChessProgram || gameMode == AnalyzeFile)
10613       return;
10614
10615     if (gameMode != AnalyzeMode) {
10616         EditGameEvent();
10617         if (gameMode != EditGame) return;
10618         ResurrectChessProgram();
10619         SendToProgram("analyze\n", &first);
10620         first.analyzing = TRUE;
10621         /*first.maybeThinking = TRUE;*/
10622         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10623         EngineOutputPopUp();
10624     }
10625     gameMode = AnalyzeFile;
10626     pausing = FALSE;
10627     ModeHighlight();
10628     SetGameInfo();
10629
10630     StartAnalysisClock();
10631     GetTimeMark(&lastNodeCountTime);
10632     lastNodeCount = 0;
10633 }
10634
10635 void
10636 MachineWhiteEvent()
10637 {
10638     char buf[MSG_SIZ];
10639     char *bookHit = NULL;
10640
10641     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10642       return;
10643
10644
10645     if (gameMode == PlayFromGameFile || 
10646         gameMode == TwoMachinesPlay  || 
10647         gameMode == Training         || 
10648         gameMode == AnalyzeMode      || 
10649         gameMode == EndOfGame)
10650         EditGameEvent();
10651
10652     if (gameMode == EditPosition) 
10653         EditPositionDone();
10654
10655     if (!WhiteOnMove(currentMove)) {
10656         DisplayError(_("It is not White's turn"), 0);
10657         return;
10658     }
10659   
10660     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10661       ExitAnalyzeMode();
10662
10663     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10664         gameMode == AnalyzeFile)
10665         TruncateGame();
10666
10667     ResurrectChessProgram();    /* in case it isn't running */
10668     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10669         gameMode = MachinePlaysWhite;
10670         ResetClocks();
10671     } else
10672     gameMode = MachinePlaysWhite;
10673     pausing = FALSE;
10674     ModeHighlight();
10675     SetGameInfo();
10676     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10677     DisplayTitle(buf);
10678     if (first.sendName) {
10679       sprintf(buf, "name %s\n", gameInfo.black);
10680       SendToProgram(buf, &first);
10681     }
10682     if (first.sendTime) {
10683       if (first.useColors) {
10684         SendToProgram("black\n", &first); /*gnu kludge*/
10685       }
10686       SendTimeRemaining(&first, TRUE);
10687     }
10688     if (first.useColors) {
10689       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10690     }
10691     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10692     SetMachineThinkingEnables();
10693     first.maybeThinking = TRUE;
10694     StartClocks();
10695     firstMove = FALSE;
10696
10697     if (appData.autoFlipView && !flipView) {
10698       flipView = !flipView;
10699       DrawPosition(FALSE, NULL);
10700       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10701     }
10702
10703     if(bookHit) { // [HGM] book: simulate book reply
10704         static char bookMove[MSG_SIZ]; // a bit generous?
10705
10706         programStats.nodes = programStats.depth = programStats.time = 
10707         programStats.score = programStats.got_only_move = 0;
10708         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10709
10710         strcpy(bookMove, "move ");
10711         strcat(bookMove, bookHit);
10712         HandleMachineMove(bookMove, &first);
10713     }
10714 }
10715
10716 void
10717 MachineBlackEvent()
10718 {
10719     char buf[MSG_SIZ];
10720    char *bookHit = NULL;
10721
10722     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10723         return;
10724
10725
10726     if (gameMode == PlayFromGameFile || 
10727         gameMode == TwoMachinesPlay  || 
10728         gameMode == Training         || 
10729         gameMode == AnalyzeMode      || 
10730         gameMode == EndOfGame)
10731         EditGameEvent();
10732
10733     if (gameMode == EditPosition) 
10734         EditPositionDone();
10735
10736     if (WhiteOnMove(currentMove)) {
10737         DisplayError(_("It is not Black's turn"), 0);
10738         return;
10739     }
10740     
10741     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10742       ExitAnalyzeMode();
10743
10744     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10745         gameMode == AnalyzeFile)
10746         TruncateGame();
10747
10748     ResurrectChessProgram();    /* in case it isn't running */
10749     gameMode = MachinePlaysBlack;
10750     pausing = FALSE;
10751     ModeHighlight();
10752     SetGameInfo();
10753     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10754     DisplayTitle(buf);
10755     if (first.sendName) {
10756       sprintf(buf, "name %s\n", gameInfo.white);
10757       SendToProgram(buf, &first);
10758     }
10759     if (first.sendTime) {
10760       if (first.useColors) {
10761         SendToProgram("white\n", &first); /*gnu kludge*/
10762       }
10763       SendTimeRemaining(&first, FALSE);
10764     }
10765     if (first.useColors) {
10766       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10767     }
10768     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10769     SetMachineThinkingEnables();
10770     first.maybeThinking = TRUE;
10771     StartClocks();
10772
10773     if (appData.autoFlipView && flipView) {
10774       flipView = !flipView;
10775       DrawPosition(FALSE, NULL);
10776       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10777     }
10778     if(bookHit) { // [HGM] book: simulate book reply
10779         static char bookMove[MSG_SIZ]; // a bit generous?
10780
10781         programStats.nodes = programStats.depth = programStats.time = 
10782         programStats.score = programStats.got_only_move = 0;
10783         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10784
10785         strcpy(bookMove, "move ");
10786         strcat(bookMove, bookHit);
10787         HandleMachineMove(bookMove, &first);
10788     }
10789 }
10790
10791
10792 void
10793 DisplayTwoMachinesTitle()
10794 {
10795     char buf[MSG_SIZ];
10796     if (appData.matchGames > 0) {
10797         if (first.twoMachinesColor[0] == 'w') {
10798             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10799                     gameInfo.white, gameInfo.black,
10800                     first.matchWins, second.matchWins,
10801                     matchGame - 1 - (first.matchWins + second.matchWins));
10802         } else {
10803             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10804                     gameInfo.white, gameInfo.black,
10805                     second.matchWins, first.matchWins,
10806                     matchGame - 1 - (first.matchWins + second.matchWins));
10807         }
10808     } else {
10809         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10810     }
10811     DisplayTitle(buf);
10812 }
10813
10814 void
10815 TwoMachinesEvent P((void))
10816 {
10817     int i;
10818     char buf[MSG_SIZ];
10819     ChessProgramState *onmove;
10820     char *bookHit = NULL;
10821     
10822     if (appData.noChessProgram) return;
10823
10824     switch (gameMode) {
10825       case TwoMachinesPlay:
10826         return;
10827       case MachinePlaysWhite:
10828       case MachinePlaysBlack:
10829         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10830             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10831             return;
10832         }
10833         /* fall through */
10834       case BeginningOfGame:
10835       case PlayFromGameFile:
10836       case EndOfGame:
10837         EditGameEvent();
10838         if (gameMode != EditGame) return;
10839         break;
10840       case EditPosition:
10841         EditPositionDone();
10842         break;
10843       case AnalyzeMode:
10844       case AnalyzeFile:
10845         ExitAnalyzeMode();
10846         break;
10847       case EditGame:
10848       default:
10849         break;
10850     }
10851
10852     forwardMostMove = currentMove;
10853     ResurrectChessProgram();    /* in case first program isn't running */
10854
10855     if (second.pr == NULL) {
10856         StartChessProgram(&second);
10857         if (second.protocolVersion == 1) {
10858           TwoMachinesEventIfReady();
10859         } else {
10860           /* kludge: allow timeout for initial "feature" command */
10861           FreezeUI();
10862           DisplayMessage("", _("Starting second chess program"));
10863           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10864         }
10865         return;
10866     }
10867     DisplayMessage("", "");
10868     InitChessProgram(&second, FALSE);
10869     SendToProgram("force\n", &second);
10870     if (startedFromSetupPosition) {
10871         SendBoard(&second, backwardMostMove);
10872     if (appData.debugMode) {
10873         fprintf(debugFP, "Two Machines\n");
10874     }
10875     }
10876     for (i = backwardMostMove; i < forwardMostMove; i++) {
10877         SendMoveToProgram(i, &second);
10878     }
10879
10880     gameMode = TwoMachinesPlay;
10881     pausing = FALSE;
10882     ModeHighlight();
10883     SetGameInfo();
10884     DisplayTwoMachinesTitle();
10885     firstMove = TRUE;
10886     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10887         onmove = &first;
10888     } else {
10889         onmove = &second;
10890     }
10891
10892     SendToProgram(first.computerString, &first);
10893     if (first.sendName) {
10894       sprintf(buf, "name %s\n", second.tidy);
10895       SendToProgram(buf, &first);
10896     }
10897     SendToProgram(second.computerString, &second);
10898     if (second.sendName) {
10899       sprintf(buf, "name %s\n", first.tidy);
10900       SendToProgram(buf, &second);
10901     }
10902
10903     ResetClocks();
10904     if (!first.sendTime || !second.sendTime) {
10905         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10906         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10907     }
10908     if (onmove->sendTime) {
10909       if (onmove->useColors) {
10910         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10911       }
10912       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10913     }
10914     if (onmove->useColors) {
10915       SendToProgram(onmove->twoMachinesColor, onmove);
10916     }
10917     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10918 //    SendToProgram("go\n", onmove);
10919     onmove->maybeThinking = TRUE;
10920     SetMachineThinkingEnables();
10921
10922     StartClocks();
10923
10924     if(bookHit) { // [HGM] book: simulate book reply
10925         static char bookMove[MSG_SIZ]; // a bit generous?
10926
10927         programStats.nodes = programStats.depth = programStats.time = 
10928         programStats.score = programStats.got_only_move = 0;
10929         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10930
10931         strcpy(bookMove, "move ");
10932         strcat(bookMove, bookHit);
10933         HandleMachineMove(bookMove, &first);
10934     }
10935 }
10936
10937 void
10938 TrainingEvent()
10939 {
10940     if (gameMode == Training) {
10941       SetTrainingModeOff();
10942       gameMode = PlayFromGameFile;
10943       DisplayMessage("", _("Training mode off"));
10944     } else {
10945       gameMode = Training;
10946       animateTraining = appData.animate;
10947
10948       /* make sure we are not already at the end of the game */
10949       if (currentMove < forwardMostMove) {
10950         SetTrainingModeOn();
10951         DisplayMessage("", _("Training mode on"));
10952       } else {
10953         gameMode = PlayFromGameFile;
10954         DisplayError(_("Already at end of game"), 0);
10955       }
10956     }
10957     ModeHighlight();
10958 }
10959
10960 void
10961 IcsClientEvent()
10962 {
10963     if (!appData.icsActive) return;
10964     switch (gameMode) {
10965       case IcsPlayingWhite:
10966       case IcsPlayingBlack:
10967       case IcsObserving:
10968       case IcsIdle:
10969       case BeginningOfGame:
10970       case IcsExamining:
10971         return;
10972
10973       case EditGame:
10974         break;
10975
10976       case EditPosition:
10977         EditPositionDone();
10978         break;
10979
10980       case AnalyzeMode:
10981       case AnalyzeFile:
10982         ExitAnalyzeMode();
10983         break;
10984         
10985       default:
10986         EditGameEvent();
10987         break;
10988     }
10989
10990     gameMode = IcsIdle;
10991     ModeHighlight();
10992     return;
10993 }
10994
10995
10996 void
10997 EditGameEvent()
10998 {
10999     int i;
11000
11001     switch (gameMode) {
11002       case Training:
11003         SetTrainingModeOff();
11004         break;
11005       case MachinePlaysWhite:
11006       case MachinePlaysBlack:
11007       case BeginningOfGame:
11008         SendToProgram("force\n", &first);
11009         SetUserThinkingEnables();
11010         break;
11011       case PlayFromGameFile:
11012         (void) StopLoadGameTimer();
11013         if (gameFileFP != NULL) {
11014             gameFileFP = NULL;
11015         }
11016         break;
11017       case EditPosition:
11018         EditPositionDone();
11019         break;
11020       case AnalyzeMode:
11021       case AnalyzeFile:
11022         ExitAnalyzeMode();
11023         SendToProgram("force\n", &first);
11024         break;
11025       case TwoMachinesPlay:
11026         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11027         ResurrectChessProgram();
11028         SetUserThinkingEnables();
11029         break;
11030       case EndOfGame:
11031         ResurrectChessProgram();
11032         break;
11033       case IcsPlayingBlack:
11034       case IcsPlayingWhite:
11035         DisplayError(_("Warning: You are still playing a game"), 0);
11036         break;
11037       case IcsObserving:
11038         DisplayError(_("Warning: You are still observing a game"), 0);
11039         break;
11040       case IcsExamining:
11041         DisplayError(_("Warning: You are still examining a game"), 0);
11042         break;
11043       case IcsIdle:
11044         break;
11045       case EditGame:
11046       default:
11047         return;
11048     }
11049     
11050     pausing = FALSE;
11051     StopClocks();
11052     first.offeredDraw = second.offeredDraw = 0;
11053
11054     if (gameMode == PlayFromGameFile) {
11055         whiteTimeRemaining = timeRemaining[0][currentMove];
11056         blackTimeRemaining = timeRemaining[1][currentMove];
11057         DisplayTitle("");
11058     }
11059
11060     if (gameMode == MachinePlaysWhite ||
11061         gameMode == MachinePlaysBlack ||
11062         gameMode == TwoMachinesPlay ||
11063         gameMode == EndOfGame) {
11064         i = forwardMostMove;
11065         while (i > currentMove) {
11066             SendToProgram("undo\n", &first);
11067             i--;
11068         }
11069         whiteTimeRemaining = timeRemaining[0][currentMove];
11070         blackTimeRemaining = timeRemaining[1][currentMove];
11071         DisplayBothClocks();
11072         if (whiteFlag || blackFlag) {
11073             whiteFlag = blackFlag = 0;
11074         }
11075         DisplayTitle("");
11076     }           
11077     
11078     gameMode = EditGame;
11079     ModeHighlight();
11080     SetGameInfo();
11081 }
11082
11083
11084 void
11085 EditPositionEvent()
11086 {
11087     if (gameMode == EditPosition) {
11088         EditGameEvent();
11089         return;
11090     }
11091     
11092     EditGameEvent();
11093     if (gameMode != EditGame) return;
11094     
11095     gameMode = EditPosition;
11096     ModeHighlight();
11097     SetGameInfo();
11098     if (currentMove > 0)
11099       CopyBoard(boards[0], boards[currentMove]);
11100     
11101     blackPlaysFirst = !WhiteOnMove(currentMove);
11102     ResetClocks();
11103     currentMove = forwardMostMove = backwardMostMove = 0;
11104     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11105     DisplayMove(-1);
11106 }
11107
11108 void
11109 ExitAnalyzeMode()
11110 {
11111     /* [DM] icsEngineAnalyze - possible call from other functions */
11112     if (appData.icsEngineAnalyze) {
11113         appData.icsEngineAnalyze = FALSE;
11114
11115         DisplayMessage("",_("Close ICS engine analyze..."));
11116     }
11117     if (first.analysisSupport && first.analyzing) {
11118       SendToProgram("exit\n", &first);
11119       first.analyzing = FALSE;
11120     }
11121     thinkOutput[0] = NULLCHAR;
11122 }
11123
11124 void
11125 EditPositionDone()
11126 {
11127     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11128
11129     startedFromSetupPosition = TRUE;
11130     InitChessProgram(&first, FALSE);
11131     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11132     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11133         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11134         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11135     } else castlingRights[0][2] = -1;
11136     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11137         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11138         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11139     } else castlingRights[0][5] = -1;
11140     SendToProgram("force\n", &first);
11141     if (blackPlaysFirst) {
11142         strcpy(moveList[0], "");
11143         strcpy(parseList[0], "");
11144         currentMove = forwardMostMove = backwardMostMove = 1;
11145         CopyBoard(boards[1], boards[0]);
11146         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11147         { int i;
11148           epStatus[1] = epStatus[0];
11149           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11150         }
11151     } else {
11152         currentMove = forwardMostMove = backwardMostMove = 0;
11153     }
11154     SendBoard(&first, forwardMostMove);
11155     if (appData.debugMode) {
11156         fprintf(debugFP, "EditPosDone\n");
11157     }
11158     DisplayTitle("");
11159     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11160     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11161     gameMode = EditGame;
11162     ModeHighlight();
11163     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11164     ClearHighlights(); /* [AS] */
11165 }
11166
11167 /* Pause for `ms' milliseconds */
11168 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11169 void
11170 TimeDelay(ms)
11171      long ms;
11172 {
11173     TimeMark m1, m2;
11174
11175     GetTimeMark(&m1);
11176     do {
11177         GetTimeMark(&m2);
11178     } while (SubtractTimeMarks(&m2, &m1) < ms);
11179 }
11180
11181 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11182 void
11183 SendMultiLineToICS(buf)
11184      char *buf;
11185 {
11186     char temp[MSG_SIZ+1], *p;
11187     int len;
11188
11189     len = strlen(buf);
11190     if (len > MSG_SIZ)
11191       len = MSG_SIZ;
11192   
11193     strncpy(temp, buf, len);
11194     temp[len] = 0;
11195
11196     p = temp;
11197     while (*p) {
11198         if (*p == '\n' || *p == '\r')
11199           *p = ' ';
11200         ++p;
11201     }
11202
11203     strcat(temp, "\n");
11204     SendToICS(temp);
11205     SendToPlayer(temp, strlen(temp));
11206 }
11207
11208 void
11209 SetWhiteToPlayEvent()
11210 {
11211     if (gameMode == EditPosition) {
11212         blackPlaysFirst = FALSE;
11213         DisplayBothClocks();    /* works because currentMove is 0 */
11214     } else if (gameMode == IcsExamining) {
11215         SendToICS(ics_prefix);
11216         SendToICS("tomove white\n");
11217     }
11218 }
11219
11220 void
11221 SetBlackToPlayEvent()
11222 {
11223     if (gameMode == EditPosition) {
11224         blackPlaysFirst = TRUE;
11225         currentMove = 1;        /* kludge */
11226         DisplayBothClocks();
11227         currentMove = 0;
11228     } else if (gameMode == IcsExamining) {
11229         SendToICS(ics_prefix);
11230         SendToICS("tomove black\n");
11231     }
11232 }
11233
11234 void
11235 EditPositionMenuEvent(selection, x, y)
11236      ChessSquare selection;
11237      int x, y;
11238 {
11239     char buf[MSG_SIZ];
11240     ChessSquare piece = boards[0][y][x];
11241
11242     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11243
11244     switch (selection) {
11245       case ClearBoard:
11246         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11247             SendToICS(ics_prefix);
11248             SendToICS("bsetup clear\n");
11249         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11250             SendToICS(ics_prefix);
11251             SendToICS("clearboard\n");
11252         } else {
11253             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11254                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11255                 for (y = 0; y < BOARD_HEIGHT; y++) {
11256                     if (gameMode == IcsExamining) {
11257                         if (boards[currentMove][y][x] != EmptySquare) {
11258                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11259                                     AAA + x, ONE + y);
11260                             SendToICS(buf);
11261                         }
11262                     } else {
11263                         boards[0][y][x] = p;
11264                     }
11265                 }
11266             }
11267         }
11268         if (gameMode == EditPosition) {
11269             DrawPosition(FALSE, boards[0]);
11270         }
11271         break;
11272
11273       case WhitePlay:
11274         SetWhiteToPlayEvent();
11275         break;
11276
11277       case BlackPlay:
11278         SetBlackToPlayEvent();
11279         break;
11280
11281       case EmptySquare:
11282         if (gameMode == IcsExamining) {
11283             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11284             SendToICS(buf);
11285         } else {
11286             boards[0][y][x] = EmptySquare;
11287             DrawPosition(FALSE, boards[0]);
11288         }
11289         break;
11290
11291       case PromotePiece:
11292         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11293            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11294             selection = (ChessSquare) (PROMOTED piece);
11295         } else if(piece == EmptySquare) selection = WhiteSilver;
11296         else selection = (ChessSquare)((int)piece - 1);
11297         goto defaultlabel;
11298
11299       case DemotePiece:
11300         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11301            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11302             selection = (ChessSquare) (DEMOTED piece);
11303         } else if(piece == EmptySquare) selection = BlackSilver;
11304         else selection = (ChessSquare)((int)piece + 1);       
11305         goto defaultlabel;
11306
11307       case WhiteQueen:
11308       case BlackQueen:
11309         if(gameInfo.variant == VariantShatranj ||
11310            gameInfo.variant == VariantXiangqi  ||
11311            gameInfo.variant == VariantCourier    )
11312             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11313         goto defaultlabel;
11314
11315       case WhiteKing:
11316       case BlackKing:
11317         if(gameInfo.variant == VariantXiangqi)
11318             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11319         if(gameInfo.variant == VariantKnightmate)
11320             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11321       default:
11322         defaultlabel:
11323         if (gameMode == IcsExamining) {
11324             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11325                     PieceToChar(selection), AAA + x, ONE + y);
11326             SendToICS(buf);
11327         } else {
11328             boards[0][y][x] = selection;
11329             DrawPosition(FALSE, boards[0]);
11330         }
11331         break;
11332     }
11333 }
11334
11335
11336 void
11337 DropMenuEvent(selection, x, y)
11338      ChessSquare selection;
11339      int x, y;
11340 {
11341     ChessMove moveType;
11342
11343     switch (gameMode) {
11344       case IcsPlayingWhite:
11345       case MachinePlaysBlack:
11346         if (!WhiteOnMove(currentMove)) {
11347             DisplayMoveError(_("It is Black's turn"));
11348             return;
11349         }
11350         moveType = WhiteDrop;
11351         break;
11352       case IcsPlayingBlack:
11353       case MachinePlaysWhite:
11354         if (WhiteOnMove(currentMove)) {
11355             DisplayMoveError(_("It is White's turn"));
11356             return;
11357         }
11358         moveType = BlackDrop;
11359         break;
11360       case EditGame:
11361         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11362         break;
11363       default:
11364         return;
11365     }
11366
11367     if (moveType == BlackDrop && selection < BlackPawn) {
11368       selection = (ChessSquare) ((int) selection
11369                                  + (int) BlackPawn - (int) WhitePawn);
11370     }
11371     if (boards[currentMove][y][x] != EmptySquare) {
11372         DisplayMoveError(_("That square is occupied"));
11373         return;
11374     }
11375
11376     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11377 }
11378
11379 void
11380 AcceptEvent()
11381 {
11382     /* Accept a pending offer of any kind from opponent */
11383     
11384     if (appData.icsActive) {
11385         SendToICS(ics_prefix);
11386         SendToICS("accept\n");
11387     } else if (cmailMsgLoaded) {
11388         if (currentMove == cmailOldMove &&
11389             commentList[cmailOldMove] != NULL &&
11390             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11391                    "Black offers a draw" : "White offers a draw")) {
11392             TruncateGame();
11393             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11394             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11395         } else {
11396             DisplayError(_("There is no pending offer on this move"), 0);
11397             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11398         }
11399     } else {
11400         /* Not used for offers from chess program */
11401     }
11402 }
11403
11404 void
11405 DeclineEvent()
11406 {
11407     /* Decline a pending offer of any kind from opponent */
11408     
11409     if (appData.icsActive) {
11410         SendToICS(ics_prefix);
11411         SendToICS("decline\n");
11412     } else if (cmailMsgLoaded) {
11413         if (currentMove == cmailOldMove &&
11414             commentList[cmailOldMove] != NULL &&
11415             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11416                    "Black offers a draw" : "White offers a draw")) {
11417 #ifdef NOTDEF
11418             AppendComment(cmailOldMove, "Draw declined");
11419             DisplayComment(cmailOldMove - 1, "Draw declined");
11420 #endif /*NOTDEF*/
11421         } else {
11422             DisplayError(_("There is no pending offer on this move"), 0);
11423         }
11424     } else {
11425         /* Not used for offers from chess program */
11426     }
11427 }
11428
11429 void
11430 RematchEvent()
11431 {
11432     /* Issue ICS rematch command */
11433     if (appData.icsActive) {
11434         SendToICS(ics_prefix);
11435         SendToICS("rematch\n");
11436     }
11437 }
11438
11439 void
11440 CallFlagEvent()
11441 {
11442     /* Call your opponent's flag (claim a win on time) */
11443     if (appData.icsActive) {
11444         SendToICS(ics_prefix);
11445         SendToICS("flag\n");
11446     } else {
11447         switch (gameMode) {
11448           default:
11449             return;
11450           case MachinePlaysWhite:
11451             if (whiteFlag) {
11452                 if (blackFlag)
11453                   GameEnds(GameIsDrawn, "Both players ran out of time",
11454                            GE_PLAYER);
11455                 else
11456                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11457             } else {
11458                 DisplayError(_("Your opponent is not out of time"), 0);
11459             }
11460             break;
11461           case MachinePlaysBlack:
11462             if (blackFlag) {
11463                 if (whiteFlag)
11464                   GameEnds(GameIsDrawn, "Both players ran out of time",
11465                            GE_PLAYER);
11466                 else
11467                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11468             } else {
11469                 DisplayError(_("Your opponent is not out of time"), 0);
11470             }
11471             break;
11472         }
11473     }
11474 }
11475
11476 void
11477 DrawEvent()
11478 {
11479     /* Offer draw or accept pending draw offer from opponent */
11480     
11481     if (appData.icsActive) {
11482         /* Note: tournament rules require draw offers to be
11483            made after you make your move but before you punch
11484            your clock.  Currently ICS doesn't let you do that;
11485            instead, you immediately punch your clock after making
11486            a move, but you can offer a draw at any time. */
11487         
11488         SendToICS(ics_prefix);
11489         SendToICS("draw\n");
11490     } else if (cmailMsgLoaded) {
11491         if (currentMove == cmailOldMove &&
11492             commentList[cmailOldMove] != NULL &&
11493             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11494                    "Black offers a draw" : "White offers a draw")) {
11495             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11496             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11497         } else if (currentMove == cmailOldMove + 1) {
11498             char *offer = WhiteOnMove(cmailOldMove) ?
11499               "White offers a draw" : "Black offers a draw";
11500             AppendComment(currentMove, offer);
11501             DisplayComment(currentMove - 1, offer);
11502             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11503         } else {
11504             DisplayError(_("You must make your move before offering a draw"), 0);
11505             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11506         }
11507     } else if (first.offeredDraw) {
11508         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11509     } else {
11510         if (first.sendDrawOffers) {
11511             SendToProgram("draw\n", &first);
11512             userOfferedDraw = TRUE;
11513         }
11514     }
11515 }
11516
11517 void
11518 AdjournEvent()
11519 {
11520     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11521     
11522     if (appData.icsActive) {
11523         SendToICS(ics_prefix);
11524         SendToICS("adjourn\n");
11525     } else {
11526         /* Currently GNU Chess doesn't offer or accept Adjourns */
11527     }
11528 }
11529
11530
11531 void
11532 AbortEvent()
11533 {
11534     /* Offer Abort or accept pending Abort offer from opponent */
11535     
11536     if (appData.icsActive) {
11537         SendToICS(ics_prefix);
11538         SendToICS("abort\n");
11539     } else {
11540         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11541     }
11542 }
11543
11544 void
11545 ResignEvent()
11546 {
11547     /* Resign.  You can do this even if it's not your turn. */
11548     
11549     if (appData.icsActive) {
11550         SendToICS(ics_prefix);
11551         SendToICS("resign\n");
11552     } else {
11553         switch (gameMode) {
11554           case MachinePlaysWhite:
11555             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11556             break;
11557           case MachinePlaysBlack:
11558             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11559             break;
11560           case EditGame:
11561             if (cmailMsgLoaded) {
11562                 TruncateGame();
11563                 if (WhiteOnMove(cmailOldMove)) {
11564                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11565                 } else {
11566                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11567                 }
11568                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11569             }
11570             break;
11571           default:
11572             break;
11573         }
11574     }
11575 }
11576
11577
11578 void
11579 StopObservingEvent()
11580 {
11581     /* Stop observing current games */
11582     SendToICS(ics_prefix);
11583     SendToICS("unobserve\n");
11584 }
11585
11586 void
11587 StopExaminingEvent()
11588 {
11589     /* Stop observing current game */
11590     SendToICS(ics_prefix);
11591     SendToICS("unexamine\n");
11592 }
11593
11594 void
11595 ForwardInner(target)
11596      int target;
11597 {
11598     int limit;
11599
11600     if (appData.debugMode)
11601         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11602                 target, currentMove, forwardMostMove);
11603
11604     if (gameMode == EditPosition)
11605       return;
11606
11607     if (gameMode == PlayFromGameFile && !pausing)
11608       PauseEvent();
11609     
11610     if (gameMode == IcsExamining && pausing)
11611       limit = pauseExamForwardMostMove;
11612     else
11613       limit = forwardMostMove;
11614     
11615     if (target > limit) target = limit;
11616
11617     if (target > 0 && moveList[target - 1][0]) {
11618         int fromX, fromY, toX, toY;
11619         toX = moveList[target - 1][2] - AAA;
11620         toY = moveList[target - 1][3] - ONE;
11621         if (moveList[target - 1][1] == '@') {
11622             if (appData.highlightLastMove) {
11623                 SetHighlights(-1, -1, toX, toY);
11624             }
11625         } else {
11626             fromX = moveList[target - 1][0] - AAA;
11627             fromY = moveList[target - 1][1] - ONE;
11628             if (target == currentMove + 1) {
11629                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11630             }
11631             if (appData.highlightLastMove) {
11632                 SetHighlights(fromX, fromY, toX, toY);
11633             }
11634         }
11635     }
11636     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11637         gameMode == Training || gameMode == PlayFromGameFile || 
11638         gameMode == AnalyzeFile) {
11639         while (currentMove < target) {
11640             SendMoveToProgram(currentMove++, &first);
11641         }
11642     } else {
11643         currentMove = target;
11644     }
11645     
11646     if (gameMode == EditGame || gameMode == EndOfGame) {
11647         whiteTimeRemaining = timeRemaining[0][currentMove];
11648         blackTimeRemaining = timeRemaining[1][currentMove];
11649     }
11650     DisplayBothClocks();
11651     DisplayMove(currentMove - 1);
11652     DrawPosition(FALSE, boards[currentMove]);
11653     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11654     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11655         DisplayComment(currentMove - 1, commentList[currentMove]);
11656     }
11657 }
11658
11659
11660 void
11661 ForwardEvent()
11662 {
11663     if (gameMode == IcsExamining && !pausing) {
11664         SendToICS(ics_prefix);
11665         SendToICS("forward\n");
11666     } else {
11667         ForwardInner(currentMove + 1);
11668     }
11669 }
11670
11671 void
11672 ToEndEvent()
11673 {
11674     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11675         /* to optimze, we temporarily turn off analysis mode while we feed
11676          * the remaining moves to the engine. Otherwise we get analysis output
11677          * after each move.
11678          */ 
11679         if (first.analysisSupport) {
11680           SendToProgram("exit\nforce\n", &first);
11681           first.analyzing = FALSE;
11682         }
11683     }
11684         
11685     if (gameMode == IcsExamining && !pausing) {
11686         SendToICS(ics_prefix);
11687         SendToICS("forward 999999\n");
11688     } else {
11689         ForwardInner(forwardMostMove);
11690     }
11691
11692     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11693         /* we have fed all the moves, so reactivate analysis mode */
11694         SendToProgram("analyze\n", &first);
11695         first.analyzing = TRUE;
11696         /*first.maybeThinking = TRUE;*/
11697         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11698     }
11699 }
11700
11701 void
11702 BackwardInner(target)
11703      int target;
11704 {
11705     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11706
11707     if (appData.debugMode)
11708         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11709                 target, currentMove, forwardMostMove);
11710
11711     if (gameMode == EditPosition) return;
11712     if (currentMove <= backwardMostMove) {
11713         ClearHighlights();
11714         DrawPosition(full_redraw, boards[currentMove]);
11715         return;
11716     }
11717     if (gameMode == PlayFromGameFile && !pausing)
11718       PauseEvent();
11719     
11720     if (moveList[target][0]) {
11721         int fromX, fromY, toX, toY;
11722         toX = moveList[target][2] - AAA;
11723         toY = moveList[target][3] - ONE;
11724         if (moveList[target][1] == '@') {
11725             if (appData.highlightLastMove) {
11726                 SetHighlights(-1, -1, toX, toY);
11727             }
11728         } else {
11729             fromX = moveList[target][0] - AAA;
11730             fromY = moveList[target][1] - ONE;
11731             if (target == currentMove - 1) {
11732                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11733             }
11734             if (appData.highlightLastMove) {
11735                 SetHighlights(fromX, fromY, toX, toY);
11736             }
11737         }
11738     }
11739     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11740         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11741         while (currentMove > target) {
11742             SendToProgram("undo\n", &first);
11743             currentMove--;
11744         }
11745     } else {
11746         currentMove = target;
11747     }
11748     
11749     if (gameMode == EditGame || gameMode == EndOfGame) {
11750         whiteTimeRemaining = timeRemaining[0][currentMove];
11751         blackTimeRemaining = timeRemaining[1][currentMove];
11752     }
11753     DisplayBothClocks();
11754     DisplayMove(currentMove - 1);
11755     DrawPosition(full_redraw, boards[currentMove]);
11756     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11757     // [HGM] PV info: routine tests if comment empty
11758     DisplayComment(currentMove - 1, commentList[currentMove]);
11759 }
11760
11761 void
11762 BackwardEvent()
11763 {
11764     if (gameMode == IcsExamining && !pausing) {
11765         SendToICS(ics_prefix);
11766         SendToICS("backward\n");
11767     } else {
11768         BackwardInner(currentMove - 1);
11769     }
11770 }
11771
11772 void
11773 ToStartEvent()
11774 {
11775     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11776         /* to optimze, we temporarily turn off analysis mode while we undo
11777          * all the moves. Otherwise we get analysis output after each undo.
11778          */ 
11779         if (first.analysisSupport) {
11780           SendToProgram("exit\nforce\n", &first);
11781           first.analyzing = FALSE;
11782         }
11783     }
11784
11785     if (gameMode == IcsExamining && !pausing) {
11786         SendToICS(ics_prefix);
11787         SendToICS("backward 999999\n");
11788     } else {
11789         BackwardInner(backwardMostMove);
11790     }
11791
11792     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11793         /* we have fed all the moves, so reactivate analysis mode */
11794         SendToProgram("analyze\n", &first);
11795         first.analyzing = TRUE;
11796         /*first.maybeThinking = TRUE;*/
11797         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11798     }
11799 }
11800
11801 void
11802 ToNrEvent(int to)
11803 {
11804   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11805   if (to >= forwardMostMove) to = forwardMostMove;
11806   if (to <= backwardMostMove) to = backwardMostMove;
11807   if (to < currentMove) {
11808     BackwardInner(to);
11809   } else {
11810     ForwardInner(to);
11811   }
11812 }
11813
11814 void
11815 RevertEvent()
11816 {
11817     if (gameMode != IcsExamining) {
11818         DisplayError(_("You are not examining a game"), 0);
11819         return;
11820     }
11821     if (pausing) {
11822         DisplayError(_("You can't revert while pausing"), 0);
11823         return;
11824     }
11825     SendToICS(ics_prefix);
11826     SendToICS("revert\n");
11827 }
11828
11829 void
11830 RetractMoveEvent()
11831 {
11832     switch (gameMode) {
11833       case MachinePlaysWhite:
11834       case MachinePlaysBlack:
11835         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11836             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11837             return;
11838         }
11839         if (forwardMostMove < 2) return;
11840         currentMove = forwardMostMove = forwardMostMove - 2;
11841         whiteTimeRemaining = timeRemaining[0][currentMove];
11842         blackTimeRemaining = timeRemaining[1][currentMove];
11843         DisplayBothClocks();
11844         DisplayMove(currentMove - 1);
11845         ClearHighlights();/*!! could figure this out*/
11846         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11847         SendToProgram("remove\n", &first);
11848         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11849         break;
11850
11851       case BeginningOfGame:
11852       default:
11853         break;
11854
11855       case IcsPlayingWhite:
11856       case IcsPlayingBlack:
11857         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11858             SendToICS(ics_prefix);
11859             SendToICS("takeback 2\n");
11860         } else {
11861             SendToICS(ics_prefix);
11862             SendToICS("takeback 1\n");
11863         }
11864         break;
11865     }
11866 }
11867
11868 void
11869 MoveNowEvent()
11870 {
11871     ChessProgramState *cps;
11872
11873     switch (gameMode) {
11874       case MachinePlaysWhite:
11875         if (!WhiteOnMove(forwardMostMove)) {
11876             DisplayError(_("It is your turn"), 0);
11877             return;
11878         }
11879         cps = &first;
11880         break;
11881       case MachinePlaysBlack:
11882         if (WhiteOnMove(forwardMostMove)) {
11883             DisplayError(_("It is your turn"), 0);
11884             return;
11885         }
11886         cps = &first;
11887         break;
11888       case TwoMachinesPlay:
11889         if (WhiteOnMove(forwardMostMove) ==
11890             (first.twoMachinesColor[0] == 'w')) {
11891             cps = &first;
11892         } else {
11893             cps = &second;
11894         }
11895         break;
11896       case BeginningOfGame:
11897       default:
11898         return;
11899     }
11900     SendToProgram("?\n", cps);
11901 }
11902
11903 void
11904 TruncateGameEvent()
11905 {
11906     EditGameEvent();
11907     if (gameMode != EditGame) return;
11908     TruncateGame();
11909 }
11910
11911 void
11912 TruncateGame()
11913 {
11914     if (forwardMostMove > currentMove) {
11915         if (gameInfo.resultDetails != NULL) {
11916             free(gameInfo.resultDetails);
11917             gameInfo.resultDetails = NULL;
11918             gameInfo.result = GameUnfinished;
11919         }
11920         forwardMostMove = currentMove;
11921         HistorySet(parseList, backwardMostMove, forwardMostMove,
11922                    currentMove-1);
11923     }
11924 }
11925
11926 void
11927 HintEvent()
11928 {
11929     if (appData.noChessProgram) return;
11930     switch (gameMode) {
11931       case MachinePlaysWhite:
11932         if (WhiteOnMove(forwardMostMove)) {
11933             DisplayError(_("Wait until your turn"), 0);
11934             return;
11935         }
11936         break;
11937       case BeginningOfGame:
11938       case MachinePlaysBlack:
11939         if (!WhiteOnMove(forwardMostMove)) {
11940             DisplayError(_("Wait until your turn"), 0);
11941             return;
11942         }
11943         break;
11944       default:
11945         DisplayError(_("No hint available"), 0);
11946         return;
11947     }
11948     SendToProgram("hint\n", &first);
11949     hintRequested = TRUE;
11950 }
11951
11952 void
11953 BookEvent()
11954 {
11955     if (appData.noChessProgram) return;
11956     switch (gameMode) {
11957       case MachinePlaysWhite:
11958         if (WhiteOnMove(forwardMostMove)) {
11959             DisplayError(_("Wait until your turn"), 0);
11960             return;
11961         }
11962         break;
11963       case BeginningOfGame:
11964       case MachinePlaysBlack:
11965         if (!WhiteOnMove(forwardMostMove)) {
11966             DisplayError(_("Wait until your turn"), 0);
11967             return;
11968         }
11969         break;
11970       case EditPosition:
11971         EditPositionDone();
11972         break;
11973       case TwoMachinesPlay:
11974         return;
11975       default:
11976         break;
11977     }
11978     SendToProgram("bk\n", &first);
11979     bookOutput[0] = NULLCHAR;
11980     bookRequested = TRUE;
11981 }
11982
11983 void
11984 AboutGameEvent()
11985 {
11986     char *tags = PGNTags(&gameInfo);
11987     TagsPopUp(tags, CmailMsg());
11988     free(tags);
11989 }
11990
11991 /* end button procedures */
11992
11993 void
11994 PrintPosition(fp, move)
11995      FILE *fp;
11996      int move;
11997 {
11998     int i, j;
11999     
12000     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12001         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12002             char c = PieceToChar(boards[move][i][j]);
12003             fputc(c == 'x' ? '.' : c, fp);
12004             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12005         }
12006     }
12007     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12008       fprintf(fp, "white to play\n");
12009     else
12010       fprintf(fp, "black to play\n");
12011 }
12012
12013 void
12014 PrintOpponents(fp)
12015      FILE *fp;
12016 {
12017     if (gameInfo.white != NULL) {
12018         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12019     } else {
12020         fprintf(fp, "\n");
12021     }
12022 }
12023
12024 /* Find last component of program's own name, using some heuristics */
12025 void
12026 TidyProgramName(prog, host, buf)
12027      char *prog, *host, buf[MSG_SIZ];
12028 {
12029     char *p, *q;
12030     int local = (strcmp(host, "localhost") == 0);
12031     while (!local && (p = strchr(prog, ';')) != NULL) {
12032         p++;
12033         while (*p == ' ') p++;
12034         prog = p;
12035     }
12036     if (*prog == '"' || *prog == '\'') {
12037         q = strchr(prog + 1, *prog);
12038     } else {
12039         q = strchr(prog, ' ');
12040     }
12041     if (q == NULL) q = prog + strlen(prog);
12042     p = q;
12043     while (p >= prog && *p != '/' && *p != '\\') p--;
12044     p++;
12045     if(p == prog && *p == '"') p++;
12046     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12047     memcpy(buf, p, q - p);
12048     buf[q - p] = NULLCHAR;
12049     if (!local) {
12050         strcat(buf, "@");
12051         strcat(buf, host);
12052     }
12053 }
12054
12055 char *
12056 TimeControlTagValue()
12057 {
12058     char buf[MSG_SIZ];
12059     if (!appData.clockMode) {
12060         strcpy(buf, "-");
12061     } else if (movesPerSession > 0) {
12062         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12063     } else if (timeIncrement == 0) {
12064         sprintf(buf, "%ld", timeControl/1000);
12065     } else {
12066         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12067     }
12068     return StrSave(buf);
12069 }
12070
12071 void
12072 SetGameInfo()
12073 {
12074     /* This routine is used only for certain modes */
12075     VariantClass v = gameInfo.variant;
12076     ClearGameInfo(&gameInfo);
12077     gameInfo.variant = v;
12078
12079     switch (gameMode) {
12080       case MachinePlaysWhite:
12081         gameInfo.event = StrSave( appData.pgnEventHeader );
12082         gameInfo.site = StrSave(HostName());
12083         gameInfo.date = PGNDate();
12084         gameInfo.round = StrSave("-");
12085         gameInfo.white = StrSave(first.tidy);
12086         gameInfo.black = StrSave(UserName());
12087         gameInfo.timeControl = TimeControlTagValue();
12088         break;
12089
12090       case MachinePlaysBlack:
12091         gameInfo.event = StrSave( appData.pgnEventHeader );
12092         gameInfo.site = StrSave(HostName());
12093         gameInfo.date = PGNDate();
12094         gameInfo.round = StrSave("-");
12095         gameInfo.white = StrSave(UserName());
12096         gameInfo.black = StrSave(first.tidy);
12097         gameInfo.timeControl = TimeControlTagValue();
12098         break;
12099
12100       case TwoMachinesPlay:
12101         gameInfo.event = StrSave( appData.pgnEventHeader );
12102         gameInfo.site = StrSave(HostName());
12103         gameInfo.date = PGNDate();
12104         if (matchGame > 0) {
12105             char buf[MSG_SIZ];
12106             sprintf(buf, "%d", matchGame);
12107             gameInfo.round = StrSave(buf);
12108         } else {
12109             gameInfo.round = StrSave("-");
12110         }
12111         if (first.twoMachinesColor[0] == 'w') {
12112             gameInfo.white = StrSave(first.tidy);
12113             gameInfo.black = StrSave(second.tidy);
12114         } else {
12115             gameInfo.white = StrSave(second.tidy);
12116             gameInfo.black = StrSave(first.tidy);
12117         }
12118         gameInfo.timeControl = TimeControlTagValue();
12119         break;
12120
12121       case EditGame:
12122         gameInfo.event = StrSave("Edited game");
12123         gameInfo.site = StrSave(HostName());
12124         gameInfo.date = PGNDate();
12125         gameInfo.round = StrSave("-");
12126         gameInfo.white = StrSave("-");
12127         gameInfo.black = StrSave("-");
12128         break;
12129
12130       case EditPosition:
12131         gameInfo.event = StrSave("Edited position");
12132         gameInfo.site = StrSave(HostName());
12133         gameInfo.date = PGNDate();
12134         gameInfo.round = StrSave("-");
12135         gameInfo.white = StrSave("-");
12136         gameInfo.black = StrSave("-");
12137         break;
12138
12139       case IcsPlayingWhite:
12140       case IcsPlayingBlack:
12141       case IcsObserving:
12142       case IcsExamining:
12143         break;
12144
12145       case PlayFromGameFile:
12146         gameInfo.event = StrSave("Game from non-PGN file");
12147         gameInfo.site = StrSave(HostName());
12148         gameInfo.date = PGNDate();
12149         gameInfo.round = StrSave("-");
12150         gameInfo.white = StrSave("?");
12151         gameInfo.black = StrSave("?");
12152         break;
12153
12154       default:
12155         break;
12156     }
12157 }
12158
12159 void
12160 ReplaceComment(index, text)
12161      int index;
12162      char *text;
12163 {
12164     int len;
12165
12166     while (*text == '\n') text++;
12167     len = strlen(text);
12168     while (len > 0 && text[len - 1] == '\n') len--;
12169
12170     if (commentList[index] != NULL)
12171       free(commentList[index]);
12172
12173     if (len == 0) {
12174         commentList[index] = NULL;
12175         return;
12176     }
12177     commentList[index] = (char *) malloc(len + 2);
12178     strncpy(commentList[index], text, len);
12179     commentList[index][len] = '\n';
12180     commentList[index][len + 1] = NULLCHAR;
12181 }
12182
12183 void
12184 CrushCRs(text)
12185      char *text;
12186 {
12187   char *p = text;
12188   char *q = text;
12189   char ch;
12190
12191   do {
12192     ch = *p++;
12193     if (ch == '\r') continue;
12194     *q++ = ch;
12195   } while (ch != '\0');
12196 }
12197
12198 void
12199 AppendComment(index, text)
12200      int index;
12201      char *text;
12202 {
12203     int oldlen, len;
12204     char *old;
12205
12206     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12207
12208     CrushCRs(text);
12209     while (*text == '\n') text++;
12210     len = strlen(text);
12211     while (len > 0 && text[len - 1] == '\n') len--;
12212
12213     if (len == 0) return;
12214
12215     if (commentList[index] != NULL) {
12216         old = commentList[index];
12217         oldlen = strlen(old);
12218         commentList[index] = (char *) malloc(oldlen + len + 2);
12219         strcpy(commentList[index], old);
12220         free(old);
12221         strncpy(&commentList[index][oldlen], text, len);
12222         commentList[index][oldlen + len] = '\n';
12223         commentList[index][oldlen + len + 1] = NULLCHAR;
12224     } else {
12225         commentList[index] = (char *) malloc(len + 2);
12226         strncpy(commentList[index], text, len);
12227         commentList[index][len] = '\n';
12228         commentList[index][len + 1] = NULLCHAR;
12229     }
12230 }
12231
12232 static char * FindStr( char * text, char * sub_text )
12233 {
12234     char * result = strstr( text, sub_text );
12235
12236     if( result != NULL ) {
12237         result += strlen( sub_text );
12238     }
12239
12240     return result;
12241 }
12242
12243 /* [AS] Try to extract PV info from PGN comment */
12244 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12245 char *GetInfoFromComment( int index, char * text )
12246 {
12247     char * sep = text;
12248
12249     if( text != NULL && index > 0 ) {
12250         int score = 0;
12251         int depth = 0;
12252         int time = -1, sec = 0, deci;
12253         char * s_eval = FindStr( text, "[%eval " );
12254         char * s_emt = FindStr( text, "[%emt " );
12255
12256         if( s_eval != NULL || s_emt != NULL ) {
12257             /* New style */
12258             char delim;
12259
12260             if( s_eval != NULL ) {
12261                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12262                     return text;
12263                 }
12264
12265                 if( delim != ']' ) {
12266                     return text;
12267                 }
12268             }
12269
12270             if( s_emt != NULL ) {
12271             }
12272         }
12273         else {
12274             /* We expect something like: [+|-]nnn.nn/dd */
12275             int score_lo = 0;
12276
12277             sep = strchr( text, '/' );
12278             if( sep == NULL || sep < (text+4) ) {
12279                 return text;
12280             }
12281
12282             time = -1; sec = -1; deci = -1;
12283             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12284                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12285                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12286                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12287                 return text;
12288             }
12289
12290             if( score_lo < 0 || score_lo >= 100 ) {
12291                 return text;
12292             }
12293
12294             if(sec >= 0) time = 600*time + 10*sec; else
12295             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12296
12297             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12298
12299             /* [HGM] PV time: now locate end of PV info */
12300             while( *++sep >= '0' && *sep <= '9'); // strip depth
12301             if(time >= 0)
12302             while( *++sep >= '0' && *sep <= '9'); // strip time
12303             if(sec >= 0)
12304             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12305             if(deci >= 0)
12306             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12307             while(*sep == ' ') sep++;
12308         }
12309
12310         if( depth <= 0 ) {
12311             return text;
12312         }
12313
12314         if( time < 0 ) {
12315             time = -1;
12316         }
12317
12318         pvInfoList[index-1].depth = depth;
12319         pvInfoList[index-1].score = score;
12320         pvInfoList[index-1].time  = 10*time; // centi-sec
12321     }
12322     return sep;
12323 }
12324
12325 void
12326 SendToProgram(message, cps)
12327      char *message;
12328      ChessProgramState *cps;
12329 {
12330     int count, outCount, error;
12331     char buf[MSG_SIZ];
12332
12333     if (cps->pr == NULL) return;
12334     Attention(cps);
12335     
12336     if (appData.debugMode) {
12337         TimeMark now;
12338         GetTimeMark(&now);
12339         fprintf(debugFP, "%ld >%-6s: %s", 
12340                 SubtractTimeMarks(&now, &programStartTime),
12341                 cps->which, message);
12342     }
12343     
12344     count = strlen(message);
12345     outCount = OutputToProcess(cps->pr, message, count, &error);
12346     if (outCount < count && !exiting 
12347                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12348         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12349         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12350             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12351                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12352                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12353             } else {
12354                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12355             }
12356             gameInfo.resultDetails = buf;
12357         }
12358         DisplayFatalError(buf, error, 1);
12359     }
12360 }
12361
12362 void
12363 ReceiveFromProgram(isr, closure, message, count, error)
12364      InputSourceRef isr;
12365      VOIDSTAR closure;
12366      char *message;
12367      int count;
12368      int error;
12369 {
12370     char *end_str;
12371     char buf[MSG_SIZ];
12372     ChessProgramState *cps = (ChessProgramState *)closure;
12373
12374     if (isr != cps->isr) return; /* Killed intentionally */
12375     if (count <= 0) {
12376         if (count == 0) {
12377             sprintf(buf,
12378                     _("Error: %s chess program (%s) exited unexpectedly"),
12379                     cps->which, cps->program);
12380         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12381                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12382                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12383                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12384                 } else {
12385                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12386                 }
12387                 gameInfo.resultDetails = buf;
12388             }
12389             RemoveInputSource(cps->isr);
12390             DisplayFatalError(buf, 0, 1);
12391         } else {
12392             sprintf(buf,
12393                     _("Error reading from %s chess program (%s)"),
12394                     cps->which, cps->program);
12395             RemoveInputSource(cps->isr);
12396
12397             /* [AS] Program is misbehaving badly... kill it */
12398             if( count == -2 ) {
12399                 DestroyChildProcess( cps->pr, 9 );
12400                 cps->pr = NoProc;
12401             }
12402
12403             DisplayFatalError(buf, error, 1);
12404         }
12405         return;
12406     }
12407     
12408     if ((end_str = strchr(message, '\r')) != NULL)
12409       *end_str = NULLCHAR;
12410     if ((end_str = strchr(message, '\n')) != NULL)
12411       *end_str = NULLCHAR;
12412     
12413     if (appData.debugMode) {
12414         TimeMark now; int print = 1;
12415         char *quote = ""; char c; int i;
12416
12417         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12418                 char start = message[0];
12419                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12420                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12421                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12422                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12423                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12424                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12425                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12426                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12427                         { quote = "# "; print = (appData.engineComments == 2); }
12428                 message[0] = start; // restore original message
12429         }
12430         if(print) {
12431                 GetTimeMark(&now);
12432                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12433                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12434                         quote,
12435                         message);
12436         }
12437     }
12438
12439     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12440     if (appData.icsEngineAnalyze) {
12441         if (strstr(message, "whisper") != NULL ||
12442              strstr(message, "kibitz") != NULL || 
12443             strstr(message, "tellics") != NULL) return;
12444     }
12445
12446     HandleMachineMove(message, cps);
12447 }
12448
12449
12450 void
12451 SendTimeControl(cps, mps, tc, inc, sd, st)
12452      ChessProgramState *cps;
12453      int mps, inc, sd, st;
12454      long tc;
12455 {
12456     char buf[MSG_SIZ];
12457     int seconds;
12458
12459     if( timeControl_2 > 0 ) {
12460         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12461             tc = timeControl_2;
12462         }
12463     }
12464     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12465     inc /= cps->timeOdds;
12466     st  /= cps->timeOdds;
12467
12468     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12469
12470     if (st > 0) {
12471       /* Set exact time per move, normally using st command */
12472       if (cps->stKludge) {
12473         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12474         seconds = st % 60;
12475         if (seconds == 0) {
12476           sprintf(buf, "level 1 %d\n", st/60);
12477         } else {
12478           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12479         }
12480       } else {
12481         sprintf(buf, "st %d\n", st);
12482       }
12483     } else {
12484       /* Set conventional or incremental time control, using level command */
12485       if (seconds == 0) {
12486         /* Note old gnuchess bug -- minutes:seconds used to not work.
12487            Fixed in later versions, but still avoid :seconds
12488            when seconds is 0. */
12489         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12490       } else {
12491         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12492                 seconds, inc/1000);
12493       }
12494     }
12495     SendToProgram(buf, cps);
12496
12497     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12498     /* Orthogonally, limit search to given depth */
12499     if (sd > 0) {
12500       if (cps->sdKludge) {
12501         sprintf(buf, "depth\n%d\n", sd);
12502       } else {
12503         sprintf(buf, "sd %d\n", sd);
12504       }
12505       SendToProgram(buf, cps);
12506     }
12507
12508     if(cps->nps > 0) { /* [HGM] nps */
12509         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12510         else {
12511                 sprintf(buf, "nps %d\n", cps->nps);
12512               SendToProgram(buf, cps);
12513         }
12514     }
12515 }
12516
12517 ChessProgramState *WhitePlayer()
12518 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12519 {
12520     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12521        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12522         return &second;
12523     return &first;
12524 }
12525
12526 void
12527 SendTimeRemaining(cps, machineWhite)
12528      ChessProgramState *cps;
12529      int /*boolean*/ machineWhite;
12530 {
12531     char message[MSG_SIZ];
12532     long time, otime;
12533
12534     /* Note: this routine must be called when the clocks are stopped
12535        or when they have *just* been set or switched; otherwise
12536        it will be off by the time since the current tick started.
12537     */
12538     if (machineWhite) {
12539         time = whiteTimeRemaining / 10;
12540         otime = blackTimeRemaining / 10;
12541     } else {
12542         time = blackTimeRemaining / 10;
12543         otime = whiteTimeRemaining / 10;
12544     }
12545     /* [HGM] translate opponent's time by time-odds factor */
12546     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12547     if (appData.debugMode) {
12548         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12549     }
12550
12551     if (time <= 0) time = 1;
12552     if (otime <= 0) otime = 1;
12553     
12554     sprintf(message, "time %ld\n", time);
12555     SendToProgram(message, cps);
12556
12557     sprintf(message, "otim %ld\n", otime);
12558     SendToProgram(message, cps);
12559 }
12560
12561 int
12562 BoolFeature(p, name, loc, cps)
12563      char **p;
12564      char *name;
12565      int *loc;
12566      ChessProgramState *cps;
12567 {
12568   char buf[MSG_SIZ];
12569   int len = strlen(name);
12570   int val;
12571   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12572     (*p) += len + 1;
12573     sscanf(*p, "%d", &val);
12574     *loc = (val != 0);
12575     while (**p && **p != ' ') (*p)++;
12576     sprintf(buf, "accepted %s\n", name);
12577     SendToProgram(buf, cps);
12578     return TRUE;
12579   }
12580   return FALSE;
12581 }
12582
12583 int
12584 IntFeature(p, name, loc, cps)
12585      char **p;
12586      char *name;
12587      int *loc;
12588      ChessProgramState *cps;
12589 {
12590   char buf[MSG_SIZ];
12591   int len = strlen(name);
12592   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12593     (*p) += len + 1;
12594     sscanf(*p, "%d", loc);
12595     while (**p && **p != ' ') (*p)++;
12596     sprintf(buf, "accepted %s\n", name);
12597     SendToProgram(buf, cps);
12598     return TRUE;
12599   }
12600   return FALSE;
12601 }
12602
12603 int
12604 StringFeature(p, name, loc, cps)
12605      char **p;
12606      char *name;
12607      char loc[];
12608      ChessProgramState *cps;
12609 {
12610   char buf[MSG_SIZ];
12611   int len = strlen(name);
12612   if (strncmp((*p), name, len) == 0
12613       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12614     (*p) += len + 2;
12615     sscanf(*p, "%[^\"]", loc);
12616     while (**p && **p != '\"') (*p)++;
12617     if (**p == '\"') (*p)++;
12618     sprintf(buf, "accepted %s\n", name);
12619     SendToProgram(buf, cps);
12620     return TRUE;
12621   }
12622   return FALSE;
12623 }
12624
12625 int 
12626 ParseOption(Option *opt, ChessProgramState *cps)
12627 // [HGM] options: process the string that defines an engine option, and determine
12628 // name, type, default value, and allowed value range
12629 {
12630         char *p, *q, buf[MSG_SIZ];
12631         int n, min = (-1)<<31, max = 1<<31, def;
12632
12633         if(p = strstr(opt->name, " -spin ")) {
12634             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12635             if(max < min) max = min; // enforce consistency
12636             if(def < min) def = min;
12637             if(def > max) def = max;
12638             opt->value = def;
12639             opt->min = min;
12640             opt->max = max;
12641             opt->type = Spin;
12642         } else if((p = strstr(opt->name, " -slider "))) {
12643             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12644             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12645             if(max < min) max = min; // enforce consistency
12646             if(def < min) def = min;
12647             if(def > max) def = max;
12648             opt->value = def;
12649             opt->min = min;
12650             opt->max = max;
12651             opt->type = Spin; // Slider;
12652         } else if((p = strstr(opt->name, " -string "))) {
12653             opt->textValue = p+9;
12654             opt->type = TextBox;
12655         } else if((p = strstr(opt->name, " -file "))) {
12656             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12657             opt->textValue = p+7;
12658             opt->type = TextBox; // FileName;
12659         } else if((p = strstr(opt->name, " -path "))) {
12660             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12661             opt->textValue = p+7;
12662             opt->type = TextBox; // PathName;
12663         } else if(p = strstr(opt->name, " -check ")) {
12664             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12665             opt->value = (def != 0);
12666             opt->type = CheckBox;
12667         } else if(p = strstr(opt->name, " -combo ")) {
12668             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12669             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12670             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12671             opt->value = n = 0;
12672             while(q = StrStr(q, " /// ")) {
12673                 n++; *q = 0;    // count choices, and null-terminate each of them
12674                 q += 5;
12675                 if(*q == '*') { // remember default, which is marked with * prefix
12676                     q++;
12677                     opt->value = n;
12678                 }
12679                 cps->comboList[cps->comboCnt++] = q;
12680             }
12681             cps->comboList[cps->comboCnt++] = NULL;
12682             opt->max = n + 1;
12683             opt->type = ComboBox;
12684         } else if(p = strstr(opt->name, " -button")) {
12685             opt->type = Button;
12686         } else if(p = strstr(opt->name, " -save")) {
12687             opt->type = SaveButton;
12688         } else return FALSE;
12689         *p = 0; // terminate option name
12690         // now look if the command-line options define a setting for this engine option.
12691         if(cps->optionSettings && cps->optionSettings[0])
12692             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12693         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12694                 sprintf(buf, "option %s", p);
12695                 if(p = strstr(buf, ",")) *p = 0;
12696                 strcat(buf, "\n");
12697                 SendToProgram(buf, cps);
12698         }
12699         return TRUE;
12700 }
12701
12702 void
12703 FeatureDone(cps, val)
12704      ChessProgramState* cps;
12705      int val;
12706 {
12707   DelayedEventCallback cb = GetDelayedEvent();
12708   if ((cb == InitBackEnd3 && cps == &first) ||
12709       (cb == TwoMachinesEventIfReady && cps == &second)) {
12710     CancelDelayedEvent();
12711     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12712   }
12713   cps->initDone = val;
12714 }
12715
12716 /* Parse feature command from engine */
12717 void
12718 ParseFeatures(args, cps)
12719      char* args;
12720      ChessProgramState *cps;  
12721 {
12722   char *p = args;
12723   char *q;
12724   int val;
12725   char buf[MSG_SIZ];
12726
12727   for (;;) {
12728     while (*p == ' ') p++;
12729     if (*p == NULLCHAR) return;
12730
12731     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12732     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12733     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12734     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12735     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12736     if (BoolFeature(&p, "reuse", &val, cps)) {
12737       /* Engine can disable reuse, but can't enable it if user said no */
12738       if (!val) cps->reuse = FALSE;
12739       continue;
12740     }
12741     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12742     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12743       if (gameMode == TwoMachinesPlay) {
12744         DisplayTwoMachinesTitle();
12745       } else {
12746         DisplayTitle("");
12747       }
12748       continue;
12749     }
12750     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12751     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12752     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12753     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12754     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12755     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12756     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12757     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12758     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12759     if (IntFeature(&p, "done", &val, cps)) {
12760       FeatureDone(cps, val);
12761       continue;
12762     }
12763     /* Added by Tord: */
12764     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12765     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12766     /* End of additions by Tord */
12767
12768     /* [HGM] added features: */
12769     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12770     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12771     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12772     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12773     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12774     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12775     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12776         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12777             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12778             SendToProgram(buf, cps);
12779             continue;
12780         }
12781         if(cps->nrOptions >= MAX_OPTIONS) {
12782             cps->nrOptions--;
12783             sprintf(buf, "%s engine has too many options\n", cps->which);
12784             DisplayError(buf, 0);
12785         }
12786         continue;
12787     }
12788     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12789     /* End of additions by HGM */
12790
12791     /* unknown feature: complain and skip */
12792     q = p;
12793     while (*q && *q != '=') q++;
12794     sprintf(buf, "rejected %.*s\n", q-p, p);
12795     SendToProgram(buf, cps);
12796     p = q;
12797     if (*p == '=') {
12798       p++;
12799       if (*p == '\"') {
12800         p++;
12801         while (*p && *p != '\"') p++;
12802         if (*p == '\"') p++;
12803       } else {
12804         while (*p && *p != ' ') p++;
12805       }
12806     }
12807   }
12808
12809 }
12810
12811 void
12812 PeriodicUpdatesEvent(newState)
12813      int newState;
12814 {
12815     if (newState == appData.periodicUpdates)
12816       return;
12817
12818     appData.periodicUpdates=newState;
12819
12820     /* Display type changes, so update it now */
12821 //    DisplayAnalysis();
12822
12823     /* Get the ball rolling again... */
12824     if (newState) {
12825         AnalysisPeriodicEvent(1);
12826         StartAnalysisClock();
12827     }
12828 }
12829
12830 void
12831 PonderNextMoveEvent(newState)
12832      int newState;
12833 {
12834     if (newState == appData.ponderNextMove) return;
12835     if (gameMode == EditPosition) EditPositionDone();
12836     if (newState) {
12837         SendToProgram("hard\n", &first);
12838         if (gameMode == TwoMachinesPlay) {
12839             SendToProgram("hard\n", &second);
12840         }
12841     } else {
12842         SendToProgram("easy\n", &first);
12843         thinkOutput[0] = NULLCHAR;
12844         if (gameMode == TwoMachinesPlay) {
12845             SendToProgram("easy\n", &second);
12846         }
12847     }
12848     appData.ponderNextMove = newState;
12849 }
12850
12851 void
12852 NewSettingEvent(option, command, value)
12853      char *command;
12854      int option, value;
12855 {
12856     char buf[MSG_SIZ];
12857
12858     if (gameMode == EditPosition) EditPositionDone();
12859     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12860     SendToProgram(buf, &first);
12861     if (gameMode == TwoMachinesPlay) {
12862         SendToProgram(buf, &second);
12863     }
12864 }
12865
12866 void
12867 ShowThinkingEvent()
12868 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12869 {
12870     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12871     int newState = appData.showThinking
12872         // [HGM] thinking: other features now need thinking output as well
12873         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12874     
12875     if (oldState == newState) return;
12876     oldState = newState;
12877     if (gameMode == EditPosition) EditPositionDone();
12878     if (oldState) {
12879         SendToProgram("post\n", &first);
12880         if (gameMode == TwoMachinesPlay) {
12881             SendToProgram("post\n", &second);
12882         }
12883     } else {
12884         SendToProgram("nopost\n", &first);
12885         thinkOutput[0] = NULLCHAR;
12886         if (gameMode == TwoMachinesPlay) {
12887             SendToProgram("nopost\n", &second);
12888         }
12889     }
12890 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12891 }
12892
12893 void
12894 AskQuestionEvent(title, question, replyPrefix, which)
12895      char *title; char *question; char *replyPrefix; char *which;
12896 {
12897   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12898   if (pr == NoProc) return;
12899   AskQuestion(title, question, replyPrefix, pr);
12900 }
12901
12902 void
12903 DisplayMove(moveNumber)
12904      int moveNumber;
12905 {
12906     char message[MSG_SIZ];
12907     char res[MSG_SIZ];
12908     char cpThinkOutput[MSG_SIZ];
12909
12910     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12911     
12912     if (moveNumber == forwardMostMove - 1 || 
12913         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12914
12915         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12916
12917         if (strchr(cpThinkOutput, '\n')) {
12918             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12919         }
12920     } else {
12921         *cpThinkOutput = NULLCHAR;
12922     }
12923
12924     /* [AS] Hide thinking from human user */
12925     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12926         *cpThinkOutput = NULLCHAR;
12927         if( thinkOutput[0] != NULLCHAR ) {
12928             int i;
12929
12930             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12931                 cpThinkOutput[i] = '.';
12932             }
12933             cpThinkOutput[i] = NULLCHAR;
12934             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12935         }
12936     }
12937
12938     if (moveNumber == forwardMostMove - 1 &&
12939         gameInfo.resultDetails != NULL) {
12940         if (gameInfo.resultDetails[0] == NULLCHAR) {
12941             sprintf(res, " %s", PGNResult(gameInfo.result));
12942         } else {
12943             sprintf(res, " {%s} %s",
12944                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12945         }
12946     } else {
12947         res[0] = NULLCHAR;
12948     }
12949
12950     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12951         DisplayMessage(res, cpThinkOutput);
12952     } else {
12953         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12954                 WhiteOnMove(moveNumber) ? " " : ".. ",
12955                 parseList[moveNumber], res);
12956         DisplayMessage(message, cpThinkOutput);
12957     }
12958 }
12959
12960 void
12961 DisplayComment(moveNumber, text)
12962      int moveNumber;
12963      char *text;
12964 {
12965     char title[MSG_SIZ];
12966     char buf[8000]; // comment can be long!
12967     int score, depth;
12968
12969     if( appData.autoDisplayComment ) {
12970         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12971             strcpy(title, "Comment");
12972         } else {
12973             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12974                     WhiteOnMove(moveNumber) ? " " : ".. ",
12975                     parseList[moveNumber]);
12976         }
12977         // [HGM] PV info: display PV info together with (or as) comment
12978         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12979             if(text == NULL) text = "";                                           
12980             score = pvInfoList[moveNumber].score;
12981             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12982                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12983             text = buf;
12984         }
12985     } else title[0] = 0;
12986
12987     if (text != NULL)
12988         CommentPopUp(title, text);
12989 }
12990
12991 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12992  * might be busy thinking or pondering.  It can be omitted if your
12993  * gnuchess is configured to stop thinking immediately on any user
12994  * input.  However, that gnuchess feature depends on the FIONREAD
12995  * ioctl, which does not work properly on some flavors of Unix.
12996  */
12997 void
12998 Attention(cps)
12999      ChessProgramState *cps;
13000 {
13001 #if ATTENTION
13002     if (!cps->useSigint) return;
13003     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13004     switch (gameMode) {
13005       case MachinePlaysWhite:
13006       case MachinePlaysBlack:
13007       case TwoMachinesPlay:
13008       case IcsPlayingWhite:
13009       case IcsPlayingBlack:
13010       case AnalyzeMode:
13011       case AnalyzeFile:
13012         /* Skip if we know it isn't thinking */
13013         if (!cps->maybeThinking) return;
13014         if (appData.debugMode)
13015           fprintf(debugFP, "Interrupting %s\n", cps->which);
13016         InterruptChildProcess(cps->pr);
13017         cps->maybeThinking = FALSE;
13018         break;
13019       default:
13020         break;
13021     }
13022 #endif /*ATTENTION*/
13023 }
13024
13025 int
13026 CheckFlags()
13027 {
13028     if (whiteTimeRemaining <= 0) {
13029         if (!whiteFlag) {
13030             whiteFlag = TRUE;
13031             if (appData.icsActive) {
13032                 if (appData.autoCallFlag &&
13033                     gameMode == IcsPlayingBlack && !blackFlag) {
13034                   SendToICS(ics_prefix);
13035                   SendToICS("flag\n");
13036                 }
13037             } else {
13038                 if (blackFlag) {
13039                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13040                 } else {
13041                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13042                     if (appData.autoCallFlag) {
13043                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13044                         return TRUE;
13045                     }
13046                 }
13047             }
13048         }
13049     }
13050     if (blackTimeRemaining <= 0) {
13051         if (!blackFlag) {
13052             blackFlag = TRUE;
13053             if (appData.icsActive) {
13054                 if (appData.autoCallFlag &&
13055                     gameMode == IcsPlayingWhite && !whiteFlag) {
13056                   SendToICS(ics_prefix);
13057                   SendToICS("flag\n");
13058                 }
13059             } else {
13060                 if (whiteFlag) {
13061                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13062                 } else {
13063                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13064                     if (appData.autoCallFlag) {
13065                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13066                         return TRUE;
13067                     }
13068                 }
13069             }
13070         }
13071     }
13072     return FALSE;
13073 }
13074
13075 void
13076 CheckTimeControl()
13077 {
13078     if (!appData.clockMode || appData.icsActive ||
13079         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13080
13081     /*
13082      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13083      */
13084     if ( !WhiteOnMove(forwardMostMove) )
13085         /* White made time control */
13086         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13087         /* [HGM] time odds: correct new time quota for time odds! */
13088                                             / WhitePlayer()->timeOdds;
13089       else
13090         /* Black made time control */
13091         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13092                                             / WhitePlayer()->other->timeOdds;
13093 }
13094
13095 void
13096 DisplayBothClocks()
13097 {
13098     int wom = gameMode == EditPosition ?
13099       !blackPlaysFirst : WhiteOnMove(currentMove);
13100     DisplayWhiteClock(whiteTimeRemaining, wom);
13101     DisplayBlackClock(blackTimeRemaining, !wom);
13102 }
13103
13104
13105 /* Timekeeping seems to be a portability nightmare.  I think everyone
13106    has ftime(), but I'm really not sure, so I'm including some ifdefs
13107    to use other calls if you don't.  Clocks will be less accurate if
13108    you have neither ftime nor gettimeofday.
13109 */
13110
13111 /* VS 2008 requires the #include outside of the function */
13112 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13113 #include <sys/timeb.h>
13114 #endif
13115
13116 /* Get the current time as a TimeMark */
13117 void
13118 GetTimeMark(tm)
13119      TimeMark *tm;
13120 {
13121 #if HAVE_GETTIMEOFDAY
13122
13123     struct timeval timeVal;
13124     struct timezone timeZone;
13125
13126     gettimeofday(&timeVal, &timeZone);
13127     tm->sec = (long) timeVal.tv_sec; 
13128     tm->ms = (int) (timeVal.tv_usec / 1000L);
13129
13130 #else /*!HAVE_GETTIMEOFDAY*/
13131 #if HAVE_FTIME
13132
13133 // include <sys/timeb.h> / moved to just above start of function
13134     struct timeb timeB;
13135
13136     ftime(&timeB);
13137     tm->sec = (long) timeB.time;
13138     tm->ms = (int) timeB.millitm;
13139
13140 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13141     tm->sec = (long) time(NULL);
13142     tm->ms = 0;
13143 #endif
13144 #endif
13145 }
13146
13147 /* Return the difference in milliseconds between two
13148    time marks.  We assume the difference will fit in a long!
13149 */
13150 long
13151 SubtractTimeMarks(tm2, tm1)
13152      TimeMark *tm2, *tm1;
13153 {
13154     return 1000L*(tm2->sec - tm1->sec) +
13155            (long) (tm2->ms - tm1->ms);
13156 }
13157
13158
13159 /*
13160  * Code to manage the game clocks.
13161  *
13162  * In tournament play, black starts the clock and then white makes a move.
13163  * We give the human user a slight advantage if he is playing white---the
13164  * clocks don't run until he makes his first move, so it takes zero time.
13165  * Also, we don't account for network lag, so we could get out of sync
13166  * with GNU Chess's clock -- but then, referees are always right.  
13167  */
13168
13169 static TimeMark tickStartTM;
13170 static long intendedTickLength;
13171
13172 long
13173 NextTickLength(timeRemaining)
13174      long timeRemaining;
13175 {
13176     long nominalTickLength, nextTickLength;
13177
13178     if (timeRemaining > 0L && timeRemaining <= 10000L)
13179       nominalTickLength = 100L;
13180     else
13181       nominalTickLength = 1000L;
13182     nextTickLength = timeRemaining % nominalTickLength;
13183     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13184
13185     return nextTickLength;
13186 }
13187
13188 /* Adjust clock one minute up or down */
13189 void
13190 AdjustClock(Boolean which, int dir)
13191 {
13192     if(which) blackTimeRemaining += 60000*dir;
13193     else      whiteTimeRemaining += 60000*dir;
13194     DisplayBothClocks();
13195 }
13196
13197 /* Stop clocks and reset to a fresh time control */
13198 void
13199 ResetClocks() 
13200 {
13201     (void) StopClockTimer();
13202     if (appData.icsActive) {
13203         whiteTimeRemaining = blackTimeRemaining = 0;
13204     } else { /* [HGM] correct new time quote for time odds */
13205         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13206         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13207     }
13208     if (whiteFlag || blackFlag) {
13209         DisplayTitle("");
13210         whiteFlag = blackFlag = FALSE;
13211     }
13212     DisplayBothClocks();
13213 }
13214
13215 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13216
13217 /* Decrement running clock by amount of time that has passed */
13218 void
13219 DecrementClocks()
13220 {
13221     long timeRemaining;
13222     long lastTickLength, fudge;
13223     TimeMark now;
13224
13225     if (!appData.clockMode) return;
13226     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13227         
13228     GetTimeMark(&now);
13229
13230     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13231
13232     /* Fudge if we woke up a little too soon */
13233     fudge = intendedTickLength - lastTickLength;
13234     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13235
13236     if (WhiteOnMove(forwardMostMove)) {
13237         if(whiteNPS >= 0) lastTickLength = 0;
13238         timeRemaining = whiteTimeRemaining -= lastTickLength;
13239         DisplayWhiteClock(whiteTimeRemaining - fudge,
13240                           WhiteOnMove(currentMove));
13241     } else {
13242         if(blackNPS >= 0) lastTickLength = 0;
13243         timeRemaining = blackTimeRemaining -= lastTickLength;
13244         DisplayBlackClock(blackTimeRemaining - fudge,
13245                           !WhiteOnMove(currentMove));
13246     }
13247
13248     if (CheckFlags()) return;
13249         
13250     tickStartTM = now;
13251     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13252     StartClockTimer(intendedTickLength);
13253
13254     /* if the time remaining has fallen below the alarm threshold, sound the
13255      * alarm. if the alarm has sounded and (due to a takeback or time control
13256      * with increment) the time remaining has increased to a level above the
13257      * threshold, reset the alarm so it can sound again. 
13258      */
13259     
13260     if (appData.icsActive && appData.icsAlarm) {
13261
13262         /* make sure we are dealing with the user's clock */
13263         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13264                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13265            )) return;
13266
13267         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13268             alarmSounded = FALSE;
13269         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13270             PlayAlarmSound();
13271             alarmSounded = TRUE;
13272         }
13273     }
13274 }
13275
13276
13277 /* A player has just moved, so stop the previously running
13278    clock and (if in clock mode) start the other one.
13279    We redisplay both clocks in case we're in ICS mode, because
13280    ICS gives us an update to both clocks after every move.
13281    Note that this routine is called *after* forwardMostMove
13282    is updated, so the last fractional tick must be subtracted
13283    from the color that is *not* on move now.
13284 */
13285 void
13286 SwitchClocks()
13287 {
13288     long lastTickLength;
13289     TimeMark now;
13290     int flagged = FALSE;
13291
13292     GetTimeMark(&now);
13293
13294     if (StopClockTimer() && appData.clockMode) {
13295         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13296         if (WhiteOnMove(forwardMostMove)) {
13297             if(blackNPS >= 0) lastTickLength = 0;
13298             blackTimeRemaining -= lastTickLength;
13299            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13300 //         if(pvInfoList[forwardMostMove-1].time == -1)
13301                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13302                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13303         } else {
13304            if(whiteNPS >= 0) lastTickLength = 0;
13305            whiteTimeRemaining -= lastTickLength;
13306            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13307 //         if(pvInfoList[forwardMostMove-1].time == -1)
13308                  pvInfoList[forwardMostMove-1].time = 
13309                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13310         }
13311         flagged = CheckFlags();
13312     }
13313     CheckTimeControl();
13314
13315     if (flagged || !appData.clockMode) return;
13316
13317     switch (gameMode) {
13318       case MachinePlaysBlack:
13319       case MachinePlaysWhite:
13320       case BeginningOfGame:
13321         if (pausing) return;
13322         break;
13323
13324       case EditGame:
13325       case PlayFromGameFile:
13326       case IcsExamining:
13327         return;
13328
13329       default:
13330         break;
13331     }
13332
13333     tickStartTM = now;
13334     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13335       whiteTimeRemaining : blackTimeRemaining);
13336     StartClockTimer(intendedTickLength);
13337 }
13338         
13339
13340 /* Stop both clocks */
13341 void
13342 StopClocks()
13343 {       
13344     long lastTickLength;
13345     TimeMark now;
13346
13347     if (!StopClockTimer()) return;
13348     if (!appData.clockMode) return;
13349
13350     GetTimeMark(&now);
13351
13352     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13353     if (WhiteOnMove(forwardMostMove)) {
13354         if(whiteNPS >= 0) lastTickLength = 0;
13355         whiteTimeRemaining -= lastTickLength;
13356         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13357     } else {
13358         if(blackNPS >= 0) lastTickLength = 0;
13359         blackTimeRemaining -= lastTickLength;
13360         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13361     }
13362     CheckFlags();
13363 }
13364         
13365 /* Start clock of player on move.  Time may have been reset, so
13366    if clock is already running, stop and restart it. */
13367 void
13368 StartClocks()
13369 {
13370     (void) StopClockTimer(); /* in case it was running already */
13371     DisplayBothClocks();
13372     if (CheckFlags()) return;
13373
13374     if (!appData.clockMode) return;
13375     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13376
13377     GetTimeMark(&tickStartTM);
13378     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13379       whiteTimeRemaining : blackTimeRemaining);
13380
13381    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13382     whiteNPS = blackNPS = -1; 
13383     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13384        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13385         whiteNPS = first.nps;
13386     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13387        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13388         blackNPS = first.nps;
13389     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13390         whiteNPS = second.nps;
13391     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13392         blackNPS = second.nps;
13393     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13394
13395     StartClockTimer(intendedTickLength);
13396 }
13397
13398 char *
13399 TimeString(ms)
13400      long ms;
13401 {
13402     long second, minute, hour, day;
13403     char *sign = "";
13404     static char buf[32];
13405     
13406     if (ms > 0 && ms <= 9900) {
13407       /* convert milliseconds to tenths, rounding up */
13408       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13409
13410       sprintf(buf, " %03.1f ", tenths/10.0);
13411       return buf;
13412     }
13413
13414     /* convert milliseconds to seconds, rounding up */
13415     /* use floating point to avoid strangeness of integer division
13416        with negative dividends on many machines */
13417     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13418
13419     if (second < 0) {
13420         sign = "-";
13421         second = -second;
13422     }
13423     
13424     day = second / (60 * 60 * 24);
13425     second = second % (60 * 60 * 24);
13426     hour = second / (60 * 60);
13427     second = second % (60 * 60);
13428     minute = second / 60;
13429     second = second % 60;
13430     
13431     if (day > 0)
13432       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13433               sign, day, hour, minute, second);
13434     else if (hour > 0)
13435       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13436     else
13437       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13438     
13439     return buf;
13440 }
13441
13442
13443 /*
13444  * This is necessary because some C libraries aren't ANSI C compliant yet.
13445  */
13446 char *
13447 StrStr(string, match)
13448      char *string, *match;
13449 {
13450     int i, length;
13451     
13452     length = strlen(match);
13453     
13454     for (i = strlen(string) - length; i >= 0; i--, string++)
13455       if (!strncmp(match, string, length))
13456         return string;
13457     
13458     return NULL;
13459 }
13460
13461 char *
13462 StrCaseStr(string, match)
13463      char *string, *match;
13464 {
13465     int i, j, length;
13466     
13467     length = strlen(match);
13468     
13469     for (i = strlen(string) - length; i >= 0; i--, string++) {
13470         for (j = 0; j < length; j++) {
13471             if (ToLower(match[j]) != ToLower(string[j]))
13472               break;
13473         }
13474         if (j == length) return string;
13475     }
13476
13477     return NULL;
13478 }
13479
13480 #ifndef _amigados
13481 int
13482 StrCaseCmp(s1, s2)
13483      char *s1, *s2;
13484 {
13485     char c1, c2;
13486     
13487     for (;;) {
13488         c1 = ToLower(*s1++);
13489         c2 = ToLower(*s2++);
13490         if (c1 > c2) return 1;
13491         if (c1 < c2) return -1;
13492         if (c1 == NULLCHAR) return 0;
13493     }
13494 }
13495
13496
13497 int
13498 ToLower(c)
13499      int c;
13500 {
13501     return isupper(c) ? tolower(c) : c;
13502 }
13503
13504
13505 int
13506 ToUpper(c)
13507      int c;
13508 {
13509     return islower(c) ? toupper(c) : c;
13510 }
13511 #endif /* !_amigados    */
13512
13513 char *
13514 StrSave(s)
13515      char *s;
13516 {
13517     char *ret;
13518
13519     if ((ret = (char *) malloc(strlen(s) + 1))) {
13520         strcpy(ret, s);
13521     }
13522     return ret;
13523 }
13524
13525 char *
13526 StrSavePtr(s, savePtr)
13527      char *s, **savePtr;
13528 {
13529     if (*savePtr) {
13530         free(*savePtr);
13531     }
13532     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13533         strcpy(*savePtr, s);
13534     }
13535     return(*savePtr);
13536 }
13537
13538 char *
13539 PGNDate()
13540 {
13541     time_t clock;
13542     struct tm *tm;
13543     char buf[MSG_SIZ];
13544
13545     clock = time((time_t *)NULL);
13546     tm = localtime(&clock);
13547     sprintf(buf, "%04d.%02d.%02d",
13548             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13549     return StrSave(buf);
13550 }
13551
13552
13553 char *
13554 PositionToFEN(move, overrideCastling)
13555      int move;
13556      char *overrideCastling;
13557 {
13558     int i, j, fromX, fromY, toX, toY;
13559     int whiteToPlay;
13560     char buf[128];
13561     char *p, *q;
13562     int emptycount;
13563     ChessSquare piece;
13564
13565     whiteToPlay = (gameMode == EditPosition) ?
13566       !blackPlaysFirst : (move % 2 == 0);
13567     p = buf;
13568
13569     /* Piece placement data */
13570     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13571         emptycount = 0;
13572         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13573             if (boards[move][i][j] == EmptySquare) {
13574                 emptycount++;
13575             } else { ChessSquare piece = boards[move][i][j];
13576                 if (emptycount > 0) {
13577                     if(emptycount<10) /* [HGM] can be >= 10 */
13578                         *p++ = '0' + emptycount;
13579                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13580                     emptycount = 0;
13581                 }
13582                 if(PieceToChar(piece) == '+') {
13583                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13584                     *p++ = '+';
13585                     piece = (ChessSquare)(DEMOTED piece);
13586                 } 
13587                 *p++ = PieceToChar(piece);
13588                 if(p[-1] == '~') {
13589                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13590                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13591                     *p++ = '~';
13592                 }
13593             }
13594         }
13595         if (emptycount > 0) {
13596             if(emptycount<10) /* [HGM] can be >= 10 */
13597                 *p++ = '0' + emptycount;
13598             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13599             emptycount = 0;
13600         }
13601         *p++ = '/';
13602     }
13603     *(p - 1) = ' ';
13604
13605     /* [HGM] print Crazyhouse or Shogi holdings */
13606     if( gameInfo.holdingsWidth ) {
13607         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13608         q = p;
13609         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13610             piece = boards[move][i][BOARD_WIDTH-1];
13611             if( piece != EmptySquare )
13612               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13613                   *p++ = PieceToChar(piece);
13614         }
13615         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13616             piece = boards[move][BOARD_HEIGHT-i-1][0];
13617             if( piece != EmptySquare )
13618               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13619                   *p++ = PieceToChar(piece);
13620         }
13621
13622         if( q == p ) *p++ = '-';
13623         *p++ = ']';
13624         *p++ = ' ';
13625     }
13626
13627     /* Active color */
13628     *p++ = whiteToPlay ? 'w' : 'b';
13629     *p++ = ' ';
13630
13631   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13632     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13633   } else {
13634   if(nrCastlingRights) {
13635      q = p;
13636      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13637        /* [HGM] write directly from rights */
13638            if(castlingRights[move][2] >= 0 &&
13639               castlingRights[move][0] >= 0   )
13640                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13641            if(castlingRights[move][2] >= 0 &&
13642               castlingRights[move][1] >= 0   )
13643                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13644            if(castlingRights[move][5] >= 0 &&
13645               castlingRights[move][3] >= 0   )
13646                 *p++ = castlingRights[move][3] + AAA;
13647            if(castlingRights[move][5] >= 0 &&
13648               castlingRights[move][4] >= 0   )
13649                 *p++ = castlingRights[move][4] + AAA;
13650      } else {
13651
13652         /* [HGM] write true castling rights */
13653         if( nrCastlingRights == 6 ) {
13654             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13655                castlingRights[move][2] >= 0  ) *p++ = 'K';
13656             if(castlingRights[move][1] == BOARD_LEFT &&
13657                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13658             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13659                castlingRights[move][5] >= 0  ) *p++ = 'k';
13660             if(castlingRights[move][4] == BOARD_LEFT &&
13661                castlingRights[move][5] >= 0  ) *p++ = 'q';
13662         }
13663      }
13664      if (q == p) *p++ = '-'; /* No castling rights */
13665      *p++ = ' ';
13666   }
13667
13668   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13669      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13670     /* En passant target square */
13671     if (move > backwardMostMove) {
13672         fromX = moveList[move - 1][0] - AAA;
13673         fromY = moveList[move - 1][1] - ONE;
13674         toX = moveList[move - 1][2] - AAA;
13675         toY = moveList[move - 1][3] - ONE;
13676         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13677             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13678             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13679             fromX == toX) {
13680             /* 2-square pawn move just happened */
13681             *p++ = toX + AAA;
13682             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13683         } else {
13684             *p++ = '-';
13685         }
13686     } else if(move == backwardMostMove) {
13687         // [HGM] perhaps we should always do it like this, and forget the above?
13688         if(epStatus[move] >= 0) {
13689             *p++ = epStatus[move] + AAA;
13690             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13691         } else {
13692             *p++ = '-';
13693         }
13694     } else {
13695         *p++ = '-';
13696     }
13697     *p++ = ' ';
13698   }
13699   }
13700
13701     /* [HGM] find reversible plies */
13702     {   int i = 0, j=move;
13703
13704         if (appData.debugMode) { int k;
13705             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13706             for(k=backwardMostMove; k<=forwardMostMove; k++)
13707                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13708
13709         }
13710
13711         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13712         if( j == backwardMostMove ) i += initialRulePlies;
13713         sprintf(p, "%d ", i);
13714         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13715     }
13716     /* Fullmove number */
13717     sprintf(p, "%d", (move / 2) + 1);
13718     
13719     return StrSave(buf);
13720 }
13721
13722 Boolean
13723 ParseFEN(board, blackPlaysFirst, fen)
13724     Board board;
13725      int *blackPlaysFirst;
13726      char *fen;
13727 {
13728     int i, j;
13729     char *p;
13730     int emptycount;
13731     ChessSquare piece;
13732
13733     p = fen;
13734
13735     /* [HGM] by default clear Crazyhouse holdings, if present */
13736     if(gameInfo.holdingsWidth) {
13737        for(i=0; i<BOARD_HEIGHT; i++) {
13738            board[i][0]             = EmptySquare; /* black holdings */
13739            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13740            board[i][1]             = (ChessSquare) 0; /* black counts */
13741            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13742        }
13743     }
13744
13745     /* Piece placement data */
13746     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13747         j = 0;
13748         for (;;) {
13749             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13750                 if (*p == '/') p++;
13751                 emptycount = gameInfo.boardWidth - j;
13752                 while (emptycount--)
13753                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13754                 break;
13755 #if(BOARD_SIZE >= 10)
13756             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13757                 p++; emptycount=10;
13758                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13759                 while (emptycount--)
13760                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13761 #endif
13762             } else if (isdigit(*p)) {
13763                 emptycount = *p++ - '0';
13764                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13765                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13766                 while (emptycount--)
13767                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13768             } else if (*p == '+' || isalpha(*p)) {
13769                 if (j >= gameInfo.boardWidth) return FALSE;
13770                 if(*p=='+') {
13771                     piece = CharToPiece(*++p);
13772                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13773                     piece = (ChessSquare) (PROMOTED piece ); p++;
13774                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13775                 } else piece = CharToPiece(*p++);
13776
13777                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13778                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13779                     piece = (ChessSquare) (PROMOTED piece);
13780                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13781                     p++;
13782                 }
13783                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13784             } else {
13785                 return FALSE;
13786             }
13787         }
13788     }
13789     while (*p == '/' || *p == ' ') p++;
13790
13791     /* [HGM] look for Crazyhouse holdings here */
13792     while(*p==' ') p++;
13793     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13794         if(*p == '[') p++;
13795         if(*p == '-' ) *p++; /* empty holdings */ else {
13796             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13797             /* if we would allow FEN reading to set board size, we would   */
13798             /* have to add holdings and shift the board read so far here   */
13799             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13800                 *p++;
13801                 if((int) piece >= (int) BlackPawn ) {
13802                     i = (int)piece - (int)BlackPawn;
13803                     i = PieceToNumber((ChessSquare)i);
13804                     if( i >= gameInfo.holdingsSize ) return FALSE;
13805                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13806                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13807                 } else {
13808                     i = (int)piece - (int)WhitePawn;
13809                     i = PieceToNumber((ChessSquare)i);
13810                     if( i >= gameInfo.holdingsSize ) return FALSE;
13811                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13812                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13813                 }
13814             }
13815         }
13816         if(*p == ']') *p++;
13817     }
13818
13819     while(*p == ' ') p++;
13820
13821     /* Active color */
13822     switch (*p++) {
13823       case 'w':
13824         *blackPlaysFirst = FALSE;
13825         break;
13826       case 'b': 
13827         *blackPlaysFirst = TRUE;
13828         break;
13829       default:
13830         return FALSE;
13831     }
13832
13833     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13834     /* return the extra info in global variiables             */
13835
13836     /* set defaults in case FEN is incomplete */
13837     FENepStatus = EP_UNKNOWN;
13838     for(i=0; i<nrCastlingRights; i++ ) {
13839         FENcastlingRights[i] =
13840             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13841     }   /* assume possible unless obviously impossible */
13842     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13843     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13844     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13845     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13846     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13847     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13848     FENrulePlies = 0;
13849
13850     while(*p==' ') p++;
13851     if(nrCastlingRights) {
13852       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13853           /* castling indicator present, so default becomes no castlings */
13854           for(i=0; i<nrCastlingRights; i++ ) {
13855                  FENcastlingRights[i] = -1;
13856           }
13857       }
13858       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13859              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13860              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13861              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13862         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13863
13864         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13865             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13866             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13867         }
13868         switch(c) {
13869           case'K':
13870               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13871               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13872               FENcastlingRights[2] = whiteKingFile;
13873               break;
13874           case'Q':
13875               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13876               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13877               FENcastlingRights[2] = whiteKingFile;
13878               break;
13879           case'k':
13880               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13881               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13882               FENcastlingRights[5] = blackKingFile;
13883               break;
13884           case'q':
13885               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13886               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13887               FENcastlingRights[5] = blackKingFile;
13888           case '-':
13889               break;
13890           default: /* FRC castlings */
13891               if(c >= 'a') { /* black rights */
13892                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13893                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13894                   if(i == BOARD_RGHT) break;
13895                   FENcastlingRights[5] = i;
13896                   c -= AAA;
13897                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13898                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13899                   if(c > i)
13900                       FENcastlingRights[3] = c;
13901                   else
13902                       FENcastlingRights[4] = c;
13903               } else { /* white rights */
13904                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13905                     if(board[0][i] == WhiteKing) break;
13906                   if(i == BOARD_RGHT) break;
13907                   FENcastlingRights[2] = i;
13908                   c -= AAA - 'a' + 'A';
13909                   if(board[0][c] >= WhiteKing) break;
13910                   if(c > i)
13911                       FENcastlingRights[0] = c;
13912                   else
13913                       FENcastlingRights[1] = c;
13914               }
13915         }
13916       }
13917     if (appData.debugMode) {
13918         fprintf(debugFP, "FEN castling rights:");
13919         for(i=0; i<nrCastlingRights; i++)
13920         fprintf(debugFP, " %d", FENcastlingRights[i]);
13921         fprintf(debugFP, "\n");
13922     }
13923
13924       while(*p==' ') p++;
13925     }
13926
13927     /* read e.p. field in games that know e.p. capture */
13928     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13929        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13930       if(*p=='-') {
13931         p++; FENepStatus = EP_NONE;
13932       } else {
13933          char c = *p++ - AAA;
13934
13935          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13936          if(*p >= '0' && *p <='9') *p++;
13937          FENepStatus = c;
13938       }
13939     }
13940
13941
13942     if(sscanf(p, "%d", &i) == 1) {
13943         FENrulePlies = i; /* 50-move ply counter */
13944         /* (The move number is still ignored)    */
13945     }
13946
13947     return TRUE;
13948 }
13949       
13950 void
13951 EditPositionPasteFEN(char *fen)
13952 {
13953   if (fen != NULL) {
13954     Board initial_position;
13955
13956     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13957       DisplayError(_("Bad FEN position in clipboard"), 0);
13958       return ;
13959     } else {
13960       int savedBlackPlaysFirst = blackPlaysFirst;
13961       EditPositionEvent();
13962       blackPlaysFirst = savedBlackPlaysFirst;
13963       CopyBoard(boards[0], initial_position);
13964           /* [HGM] copy FEN attributes as well */
13965           {   int i;
13966               initialRulePlies = FENrulePlies;
13967               epStatus[0] = FENepStatus;
13968               for( i=0; i<nrCastlingRights; i++ )
13969                   castlingRights[0][i] = FENcastlingRights[i];
13970           }
13971       EditPositionDone();
13972       DisplayBothClocks();
13973       DrawPosition(FALSE, boards[currentMove]);
13974     }
13975   }
13976 }
13977
13978 static char cseq[12] = "\\   ";
13979
13980 Boolean set_cont_sequence(char *new_seq)
13981 {
13982     int len;
13983     Boolean ret;
13984
13985     // handle bad attempts to set the sequence
13986         if (!new_seq)
13987                 return 0; // acceptable error - no debug
13988
13989     len = strlen(new_seq);
13990     ret = (len > 0) && (len < sizeof(cseq));
13991     if (ret)
13992         strcpy(cseq, new_seq);
13993     else if (appData.debugMode)
13994         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %d)\n", new_seq, sizeof(cseq)-1);
13995     return ret;
13996 }
13997
13998 /*
13999     reformat a source message so words don't cross the width boundary.  internal
14000     newlines are not removed.  returns the wrapped size (no null character unless
14001     included in source message).  If dest is NULL, only calculate the size required
14002     for the dest buffer.  lp argument indicats line position upon entry, and it's
14003     passed back upon exit.
14004 */
14005 int wrap(char *dest, char *src, int count, int width, int *lp)
14006 {
14007     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14008
14009     cseq_len = strlen(cseq);
14010     old_line = line = *lp;
14011     ansi = len = clen = 0;
14012
14013     for (i=0; i < count; i++)
14014     {
14015         if (src[i] == '\033')
14016             ansi = 1;
14017
14018         // if we hit the width, back up
14019         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14020         {
14021             // store i & len in case the word is too long
14022             old_i = i, old_len = len;
14023
14024             // find the end of the last word
14025             while (i && src[i] != ' ' && src[i] != '\n')
14026             {
14027                 i--;
14028                 len--;
14029             }
14030
14031             // word too long?  restore i & len before splitting it
14032             if ((old_i-i+clen) >= width)
14033             {
14034                 i = old_i;
14035                 len = old_len;
14036             }
14037
14038             // extra space?
14039             if (i && src[i-1] == ' ')
14040                 len--;
14041
14042             if (src[i] != ' ' && src[i] != '\n')
14043             {
14044                 i--;
14045                 if (len)
14046                     len--;
14047             }
14048
14049             // now append the newline and continuation sequence
14050             if (dest)
14051                 dest[len] = '\n';
14052             len++;
14053             if (dest)
14054                 strncpy(dest+len, cseq, cseq_len);
14055             len += cseq_len;
14056             line = cseq_len;
14057             clen = cseq_len;
14058             continue;
14059         }
14060
14061         if (dest)
14062             dest[len] = src[i];
14063         len++;
14064         if (!ansi)
14065             line++;
14066         if (src[i] == '\n')
14067             line = 0;
14068         if (src[i] == 'm')
14069             ansi = 0;
14070     }
14071     if (dest && appData.debugMode)
14072     {
14073         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14074             count, width, line, len, *lp);
14075         show_bytes(debugFP, src, count);
14076         fprintf(debugFP, "\ndest: ");
14077         show_bytes(debugFP, dest, len);
14078         fprintf(debugFP, "\n");
14079     }
14080     *lp = dest ? line : old_line;
14081
14082     return len;
14083 }