Use squares for computer seek ads
[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));
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 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
254 int chattingPartner;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
256
257 /* States for ics_getting_history */
258 #define H_FALSE 0
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
264
265 /* whosays values for GameEnds */
266 #define GE_ICS 0
267 #define GE_ENGINE 1
268 #define GE_PLAYER 2
269 #define GE_FILE 3
270 #define GE_XBOARD 4
271 #define GE_ENGINE1 5
272 #define GE_ENGINE2 6
273
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
276
277 /* Different types of move when calling RegisterMove */
278 #define CMAIL_MOVE   0
279 #define CMAIL_RESIGN 1
280 #define CMAIL_DRAW   2
281 #define CMAIL_ACCEPT 3
282
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
287
288 /* Telnet protocol constants */
289 #define TN_WILL 0373
290 #define TN_WONT 0374
291 #define TN_DO   0375
292 #define TN_DONT 0376
293 #define TN_IAC  0377
294 #define TN_ECHO 0001
295 #define TN_SGA  0003
296 #define TN_PORT 23
297
298 /* [AS] */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
300 {
301     assert( dst != NULL );
302     assert( src != NULL );
303     assert( count > 0 );
304
305     strncpy( dst, src, count );
306     dst[ count-1 ] = '\0';
307     return dst;
308 }
309
310 /* Some compiler can't cast u64 to double
311  * This function do the job for us:
312
313  * We use the highest bit for cast, this only
314  * works if the highest bit is not
315  * in use (This should not happen)
316  *
317  * We used this for all compiler
318  */
319 double
320 u64ToDouble(u64 value)
321 {
322   double r;
323   u64 tmp = value & u64Const(0x7fffffffffffffff);
324   r = (double)(s64)tmp;
325   if (value & u64Const(0x8000000000000000))
326        r +=  9.2233720368547758080e18; /* 2^63 */
327  return r;
328 }
329
330 /* Fake up flags for now, as we aren't keeping track of castling
331    availability yet. [HGM] Change of logic: the flag now only
332    indicates the type of castlings allowed by the rule of the game.
333    The actual rights themselves are maintained in the array
334    castlingRights, as part of the game history, and are not probed
335    by this function.
336  */
337 int
338 PosFlags(index)
339 {
340   int flags = F_ALL_CASTLE_OK;
341   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342   switch (gameInfo.variant) {
343   case VariantSuicide:
344     flags &= ~F_ALL_CASTLE_OK;
345   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346     flags |= F_IGNORE_CHECK;
347   case VariantLosers:
348     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
349     break;
350   case VariantAtomic:
351     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
352     break;
353   case VariantKriegspiel:
354     flags |= F_KRIEGSPIEL_CAPTURE;
355     break;
356   case VariantCapaRandom: 
357   case VariantFischeRandom:
358     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359   case VariantNoCastle:
360   case VariantShatranj:
361   case VariantCourier:
362   case VariantMakruk:
363     flags &= ~F_ALL_CASTLE_OK;
364     break;
365   default:
366     break;
367   }
368   return flags;
369 }
370
371 FILE *gameFileFP, *debugFP;
372
373 /* 
374     [AS] Note: sometimes, the sscanf() function is used to parse the input
375     into a fixed-size buffer. Because of this, we must be prepared to
376     receive strings as long as the size of the input buffer, which is currently
377     set to 4K for Windows and 8K for the rest.
378     So, we must either allocate sufficiently large buffers here, or
379     reduce the size of the input buffer in the input reading part.
380 */
381
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
385
386 ChessProgramState first, second;
387
388 /* premove variables */
389 int premoveToX = 0;
390 int premoveToY = 0;
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
394 int gotPremove = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
397
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
400
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
428 int movesPerSession;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
433 int matchGame = 0;
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
437
438 /* animateTraining preserves the state of appData.animate
439  * when Training mode is activated. This allows the
440  * response to be animated when appData.animate == TRUE and
441  * appData.animateDragging == TRUE.
442  */
443 Boolean animateTraining;
444
445 GameInfo gameInfo;
446
447 AppData appData;
448
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char  initialRights[BOARD_FILES];
453 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int   initialRulePlies, FENrulePlies;
455 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
456 int loadFlag = 0; 
457 int shuffleOpenings;
458 int mute; // mute all sounds
459
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
463 int storedGames = 0;
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
469
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
473
474 ChessSquare  FIDEArray[2][BOARD_FILES] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackBishop, BlackKnight, BlackRook }
479 };
480
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackKing, BlackKnight, BlackRook }
486 };
487
488 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
489     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491     { BlackRook, BlackMan, BlackBishop, BlackQueen,
492         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
493 };
494
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
500 };
501
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 };
508
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackMan, BlackFerz,
513         BlackKing, BlackMan, BlackKnight, BlackRook }
514 };
515
516
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
523 };
524
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
530 };
531
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
534         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
536         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
537 };
538
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
541         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
543         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
544 };
545
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
548         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
550         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
551 };
552
553 #ifdef GOTHIC
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
556         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
558         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !GOTHIC
561 #define GothicArray CapablancaArray
562 #endif // !GOTHIC
563
564 #ifdef FALCON
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
567         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
569         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
570 };
571 #else // !FALCON
572 #define FalconArray CapablancaArray
573 #endif // !FALCON
574
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
581
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
588 };
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
592
593
594 Board initialPosition;
595
596
597 /* Convert str to a rating. Checks for special cases of "----",
598
599    "++++", etc. Also strips ()'s */
600 int
601 string_to_rating(str)
602   char *str;
603 {
604   while(*str && !isdigit(*str)) ++str;
605   if (!*str)
606     return 0;   /* One of the special "no rating" cases */
607   else
608     return atoi(str);
609 }
610
611 void
612 ClearProgramStats()
613 {
614     /* Init programStats */
615     programStats.movelist[0] = 0;
616     programStats.depth = 0;
617     programStats.nr_moves = 0;
618     programStats.moves_left = 0;
619     programStats.nodes = 0;
620     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
621     programStats.score = 0;
622     programStats.got_only_move = 0;
623     programStats.got_fail = 0;
624     programStats.line_is_book = 0;
625 }
626
627 void
628 InitBackEnd1()
629 {
630     int matched, min, sec;
631
632     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
633
634     GetTimeMark(&programStartTime);
635     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
636
637     ClearProgramStats();
638     programStats.ok_to_send = 1;
639     programStats.seen_stat = 0;
640
641     /*
642      * Initialize game list
643      */
644     ListNew(&gameList);
645
646
647     /*
648      * Internet chess server status
649      */
650     if (appData.icsActive) {
651         appData.matchMode = FALSE;
652         appData.matchGames = 0;
653 #if ZIPPY       
654         appData.noChessProgram = !appData.zippyPlay;
655 #else
656         appData.zippyPlay = FALSE;
657         appData.zippyTalk = FALSE;
658         appData.noChessProgram = TRUE;
659 #endif
660         if (*appData.icsHelper != NULLCHAR) {
661             appData.useTelnet = TRUE;
662             appData.telnetProgram = appData.icsHelper;
663         }
664     } else {
665         appData.zippyTalk = appData.zippyPlay = FALSE;
666     }
667
668     /* [AS] Initialize pv info list [HGM] and game state */
669     {
670         int i, j;
671
672         for( i=0; i<=framePtr; i++ ) {
673             pvInfoList[i].depth = -1;
674             boards[i][EP_STATUS] = EP_NONE;
675             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
676         }
677     }
678
679     /*
680      * Parse timeControl resource
681      */
682     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683                           appData.movesPerSession)) {
684         char buf[MSG_SIZ];
685         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686         DisplayFatalError(buf, 0, 2);
687     }
688
689     /*
690      * Parse searchTime resource
691      */
692     if (*appData.searchTime != NULLCHAR) {
693         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
694         if (matched == 1) {
695             searchTime = min * 60;
696         } else if (matched == 2) {
697             searchTime = min * 60 + sec;
698         } else {
699             char buf[MSG_SIZ];
700             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701             DisplayFatalError(buf, 0, 2);
702         }
703     }
704
705     /* [AS] Adjudication threshold */
706     adjudicateLossThreshold = appData.adjudicateLossThreshold;
707     
708     first.which = "first";
709     second.which = "second";
710     first.maybeThinking = second.maybeThinking = FALSE;
711     first.pr = second.pr = NoProc;
712     first.isr = second.isr = NULL;
713     first.sendTime = second.sendTime = 2;
714     first.sendDrawOffers = 1;
715     if (appData.firstPlaysBlack) {
716         first.twoMachinesColor = "black\n";
717         second.twoMachinesColor = "white\n";
718     } else {
719         first.twoMachinesColor = "white\n";
720         second.twoMachinesColor = "black\n";
721     }
722     first.program = appData.firstChessProgram;
723     second.program = appData.secondChessProgram;
724     first.host = appData.firstHost;
725     second.host = appData.secondHost;
726     first.dir = appData.firstDirectory;
727     second.dir = appData.secondDirectory;
728     first.other = &second;
729     second.other = &first;
730     first.initString = appData.initString;
731     second.initString = appData.secondInitString;
732     first.computerString = appData.firstComputerString;
733     second.computerString = appData.secondComputerString;
734     first.useSigint = second.useSigint = TRUE;
735     first.useSigterm = second.useSigterm = TRUE;
736     first.reuse = appData.reuseFirst;
737     second.reuse = appData.reuseSecond;
738     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
739     second.nps = appData.secondNPS;
740     first.useSetboard = second.useSetboard = FALSE;
741     first.useSAN = second.useSAN = FALSE;
742     first.usePing = second.usePing = FALSE;
743     first.lastPing = second.lastPing = 0;
744     first.lastPong = second.lastPong = 0;
745     first.usePlayother = second.usePlayother = FALSE;
746     first.useColors = second.useColors = TRUE;
747     first.useUsermove = second.useUsermove = FALSE;
748     first.sendICS = second.sendICS = FALSE;
749     first.sendName = second.sendName = appData.icsActive;
750     first.sdKludge = second.sdKludge = FALSE;
751     first.stKludge = second.stKludge = FALSE;
752     TidyProgramName(first.program, first.host, first.tidy);
753     TidyProgramName(second.program, second.host, second.tidy);
754     first.matchWins = second.matchWins = 0;
755     strcpy(first.variants, appData.variant);
756     strcpy(second.variants, appData.variant);
757     first.analysisSupport = second.analysisSupport = 2; /* detect */
758     first.analyzing = second.analyzing = FALSE;
759     first.initDone = second.initDone = FALSE;
760
761     /* New features added by Tord: */
762     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764     /* End of new features added by Tord. */
765     first.fenOverride  = appData.fenOverride1;
766     second.fenOverride = appData.fenOverride2;
767
768     /* [HGM] time odds: set factor for each machine */
769     first.timeOdds  = appData.firstTimeOdds;
770     second.timeOdds = appData.secondTimeOdds;
771     { float norm = 1;
772         if(appData.timeOddsMode) {
773             norm = first.timeOdds;
774             if(norm > second.timeOdds) norm = second.timeOdds;
775         }
776         first.timeOdds /= norm;
777         second.timeOdds /= norm;
778     }
779
780     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781     first.accumulateTC = appData.firstAccumulateTC;
782     second.accumulateTC = appData.secondAccumulateTC;
783     first.maxNrOfSessions = second.maxNrOfSessions = 1;
784
785     /* [HGM] debug */
786     first.debug = second.debug = FALSE;
787     first.supportsNPS = second.supportsNPS = UNKNOWN;
788
789     /* [HGM] options */
790     first.optionSettings  = appData.firstOptions;
791     second.optionSettings = appData.secondOptions;
792
793     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795     first.isUCI = appData.firstIsUCI; /* [AS] */
796     second.isUCI = appData.secondIsUCI; /* [AS] */
797     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
799
800     if (appData.firstProtocolVersion > PROTOVER ||
801         appData.firstProtocolVersion < 1) {
802       char buf[MSG_SIZ];
803       sprintf(buf, _("protocol version %d not supported"),
804               appData.firstProtocolVersion);
805       DisplayFatalError(buf, 0, 2);
806     } else {
807       first.protocolVersion = appData.firstProtocolVersion;
808     }
809
810     if (appData.secondProtocolVersion > PROTOVER ||
811         appData.secondProtocolVersion < 1) {
812       char buf[MSG_SIZ];
813       sprintf(buf, _("protocol version %d not supported"),
814               appData.secondProtocolVersion);
815       DisplayFatalError(buf, 0, 2);
816     } else {
817       second.protocolVersion = appData.secondProtocolVersion;
818     }
819
820     if (appData.icsActive) {
821         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
822 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824         appData.clockMode = FALSE;
825         first.sendTime = second.sendTime = 0;
826     }
827     
828 #if ZIPPY
829     /* Override some settings from environment variables, for backward
830        compatibility.  Unfortunately it's not feasible to have the env
831        vars just set defaults, at least in xboard.  Ugh.
832     */
833     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
834       ZippyInit();
835     }
836 #endif
837     
838     if (appData.noChessProgram) {
839         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840         sprintf(programVersion, "%s", PACKAGE_STRING);
841     } else {
842       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
845     }
846
847     if (!appData.icsActive) {
848       char buf[MSG_SIZ];
849       /* Check for variants that are supported only in ICS mode,
850          or not at all.  Some that are accepted here nevertheless
851          have bugs; see comments below.
852       */
853       VariantClass variant = StringToVariant(appData.variant);
854       switch (variant) {
855       case VariantBughouse:     /* need four players and two boards */
856       case VariantKriegspiel:   /* need to hide pieces and move details */
857       /* case VariantFischeRandom: (Fabien: moved below) */
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859         DisplayFatalError(buf, 0, 2);
860         return;
861
862       case VariantUnknown:
863       case VariantLoadable:
864       case Variant29:
865       case Variant30:
866       case Variant31:
867       case Variant32:
868       case Variant33:
869       case Variant34:
870       case Variant35:
871       case Variant36:
872       default:
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);
874         DisplayFatalError(buf, 0, 2);
875         return;
876
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */
879       case VariantGothic:     /* [HGM] should work */
880       case VariantCapablanca: /* [HGM] should work */
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */
882       case VariantShogi:      /* [HGM] drops not tested for legality */
883       case VariantKnightmate: /* [HGM] should work */
884       case VariantCylinder:   /* [HGM] untested */
885       case VariantFalcon:     /* [HGM] untested */
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887                                  offboard interposition not understood */
888       case VariantNormal:     /* definitely works! */
889       case VariantWildCastle: /* pieces not automatically shuffled */
890       case VariantNoCastle:   /* pieces not automatically shuffled */
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892       case VariantLosers:     /* should work except for win condition,
893                                  and doesn't know captures are mandatory */
894       case VariantSuicide:    /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantGiveaway:   /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantTwoKings:   /* should work */
899       case VariantAtomic:     /* should work except for win condition */
900       case Variant3Check:     /* should work except for win condition */
901       case VariantShatranj:   /* should work except for all win conditions */
902       case VariantMakruk:     /* should work except for daw countdown */
903       case VariantBerolina:   /* might work if TestLegality is off */
904       case VariantCapaRandom: /* should work */
905       case VariantJanus:      /* should work */
906       case VariantSuper:      /* experimental */
907       case VariantGreat:      /* experimental, requires legality testing to be off */
908         break;
909       }
910     }
911
912     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
913     InitEngineUCI( installDir, &second );
914 }
915
916 int NextIntegerFromString( char ** str, long * value )
917 {
918     int result = -1;
919     char * s = *str;
920
921     while( *s == ' ' || *s == '\t' ) {
922         s++;
923     }
924
925     *value = 0;
926
927     if( *s >= '0' && *s <= '9' ) {
928         while( *s >= '0' && *s <= '9' ) {
929             *value = *value * 10 + (*s - '0');
930             s++;
931         }
932
933         result = 0;
934     }
935
936     *str = s;
937
938     return result;
939 }
940
941 int NextTimeControlFromString( char ** str, long * value )
942 {
943     long temp;
944     int result = NextIntegerFromString( str, &temp );
945
946     if( result == 0 ) {
947         *value = temp * 60; /* Minutes */
948         if( **str == ':' ) {
949             (*str)++;
950             result = NextIntegerFromString( str, &temp );
951             *value += temp; /* Seconds */
952         }
953     }
954
955     return result;
956 }
957
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
960     int result = -1; long temp, temp2;
961
962     if(**str != '+') return -1; // old params remain in force!
963     (*str)++;
964     if( NextTimeControlFromString( str, &temp ) ) return -1;
965
966     if(**str != '/') {
967         /* time only: incremental or sudden-death time control */
968         if(**str == '+') { /* increment follows; read it */
969             (*str)++;
970             if(result = NextIntegerFromString( str, &temp2)) return -1;
971             *inc = temp2 * 1000;
972         } else *inc = 0;
973         *moves = 0; *tc = temp * 1000; 
974         return 0;
975     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
976
977     (*str)++; /* classical time control */
978     result = NextTimeControlFromString( str, &temp2);
979     if(result == 0) {
980         *moves = temp/60;
981         *tc    = temp2 * 1000;
982         *inc   = 0;
983     }
984     return result;
985 }
986
987 int GetTimeQuota(int movenr)
988 {   /* [HGM] get time to add from the multi-session time-control string */
989     int moves=1; /* kludge to force reading of first session */
990     long time, increment;
991     char *s = fullTimeControlString;
992
993     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
994     do {
995         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997         if(movenr == -1) return time;    /* last move before new session     */
998         if(!moves) return increment;     /* current session is incremental   */
999         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000     } while(movenr >= -1);               /* try again for next session       */
1001
1002     return 0; // no new time quota on this move
1003 }
1004
1005 int
1006 ParseTimeControl(tc, ti, mps)
1007      char *tc;
1008      int ti;
1009      int mps;
1010 {
1011   long tc1;
1012   long tc2;
1013   char buf[MSG_SIZ];
1014   
1015   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1016   if(ti > 0) {
1017     if(mps)
1018       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019     else sprintf(buf, "+%s+%d", tc, ti);
1020   } else {
1021     if(mps)
1022              sprintf(buf, "+%d/%s", mps, tc);
1023     else sprintf(buf, "+%s", tc);
1024   }
1025   fullTimeControlString = StrSave(buf);
1026   
1027   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1028     return FALSE;
1029   }
1030   
1031   if( *tc == '/' ) {
1032     /* Parse second time control */
1033     tc++;
1034     
1035     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1036       return FALSE;
1037     }
1038     
1039     if( tc2 == 0 ) {
1040       return FALSE;
1041     }
1042     
1043     timeControl_2 = tc2 * 1000;
1044   }
1045   else {
1046     timeControl_2 = 0;
1047   }
1048   
1049   if( tc1 == 0 ) {
1050     return FALSE;
1051   }
1052   
1053   timeControl = tc1 * 1000;
1054   
1055   if (ti >= 0) {
1056     timeIncrement = ti * 1000;  /* convert to ms */
1057     movesPerSession = 0;
1058   } else {
1059     timeIncrement = 0;
1060     movesPerSession = mps;
1061   }
1062   return TRUE;
1063 }
1064
1065 void
1066 InitBackEnd2()
1067 {
1068     if (appData.debugMode) {
1069         fprintf(debugFP, "%s\n", programVersion);
1070     }
1071
1072     set_cont_sequence(appData.wrapContSeq);
1073     if (appData.matchGames > 0) {
1074         appData.matchMode = TRUE;
1075     } else if (appData.matchMode) {
1076         appData.matchGames = 1;
1077     }
1078     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079         appData.matchGames = appData.sameColorGames;
1080     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1083     }
1084     Reset(TRUE, FALSE);
1085     if (appData.noChessProgram || first.protocolVersion == 1) {
1086       InitBackEnd3();
1087     } else {
1088       /* kludge: allow timeout for initial "feature" commands */
1089       FreezeUI();
1090       DisplayMessage("", _("Starting chess program"));
1091       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1092     }
1093 }
1094
1095 void
1096 InitBackEnd3 P((void))
1097 {
1098     GameMode initialMode;
1099     char buf[MSG_SIZ];
1100     int err;
1101
1102     InitChessProgram(&first, startedFromSetupPosition);
1103
1104
1105     if (appData.icsActive) {
1106 #ifdef WIN32
1107         /* [DM] Make a console window if needed [HGM] merged ifs */
1108         ConsoleCreate(); 
1109 #endif
1110         err = establish();
1111         if (err != 0) {
1112             if (*appData.icsCommPort != NULLCHAR) {
1113                 sprintf(buf, _("Could not open comm port %s"),  
1114                         appData.icsCommPort);
1115             } else {
1116                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1117                         appData.icsHost, appData.icsPort);
1118             }
1119             DisplayFatalError(buf, err, 1);
1120             return;
1121         }
1122         SetICSMode();
1123         telnetISR =
1124           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1125         fromUserISR =
1126           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1127         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1128             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1129     } else if (appData.noChessProgram) {
1130         SetNCPMode();
1131     } else {
1132         SetGNUMode();
1133     }
1134
1135     if (*appData.cmailGameName != NULLCHAR) {
1136         SetCmailMode();
1137         OpenLoopback(&cmailPR);
1138         cmailISR =
1139           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1140     }
1141     
1142     ThawUI();
1143     DisplayMessage("", "");
1144     if (StrCaseCmp(appData.initialMode, "") == 0) {
1145       initialMode = BeginningOfGame;
1146     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1147       initialMode = TwoMachinesPlay;
1148     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1149       initialMode = AnalyzeFile; 
1150     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1151       initialMode = AnalyzeMode;
1152     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1153       initialMode = MachinePlaysWhite;
1154     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1155       initialMode = MachinePlaysBlack;
1156     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1157       initialMode = EditGame;
1158     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1159       initialMode = EditPosition;
1160     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1161       initialMode = Training;
1162     } else {
1163       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1164       DisplayFatalError(buf, 0, 2);
1165       return;
1166     }
1167
1168     if (appData.matchMode) {
1169         /* Set up machine vs. machine match */
1170         if (appData.noChessProgram) {
1171             DisplayFatalError(_("Can't have a match with no chess programs"),
1172                               0, 2);
1173             return;
1174         }
1175         matchMode = TRUE;
1176         matchGame = 1;
1177         if (*appData.loadGameFile != NULLCHAR) {
1178             int index = appData.loadGameIndex; // [HGM] autoinc
1179             if(index<0) lastIndex = index = 1;
1180             if (!LoadGameFromFile(appData.loadGameFile,
1181                                   index,
1182                                   appData.loadGameFile, FALSE)) {
1183                 DisplayFatalError(_("Bad game file"), 0, 1);
1184                 return;
1185             }
1186         } else if (*appData.loadPositionFile != NULLCHAR) {
1187             int index = appData.loadPositionIndex; // [HGM] autoinc
1188             if(index<0) lastIndex = index = 1;
1189             if (!LoadPositionFromFile(appData.loadPositionFile,
1190                                       index,
1191                                       appData.loadPositionFile)) {
1192                 DisplayFatalError(_("Bad position file"), 0, 1);
1193                 return;
1194             }
1195         }
1196         TwoMachinesEvent();
1197     } else if (*appData.cmailGameName != NULLCHAR) {
1198         /* Set up cmail mode */
1199         ReloadCmailMsgEvent(TRUE);
1200     } else {
1201         /* Set up other modes */
1202         if (initialMode == AnalyzeFile) {
1203           if (*appData.loadGameFile == NULLCHAR) {
1204             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1205             return;
1206           }
1207         }
1208         if (*appData.loadGameFile != NULLCHAR) {
1209             (void) LoadGameFromFile(appData.loadGameFile,
1210                                     appData.loadGameIndex,
1211                                     appData.loadGameFile, TRUE);
1212         } else if (*appData.loadPositionFile != NULLCHAR) {
1213             (void) LoadPositionFromFile(appData.loadPositionFile,
1214                                         appData.loadPositionIndex,
1215                                         appData.loadPositionFile);
1216             /* [HGM] try to make self-starting even after FEN load */
1217             /* to allow automatic setup of fairy variants with wtm */
1218             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1219                 gameMode = BeginningOfGame;
1220                 setboardSpoiledMachineBlack = 1;
1221             }
1222             /* [HGM] loadPos: make that every new game uses the setup */
1223             /* from file as long as we do not switch variant          */
1224             if(!blackPlaysFirst) {
1225                 startedFromPositionFile = TRUE;
1226                 CopyBoard(filePosition, boards[0]);
1227             }
1228         }
1229         if (initialMode == AnalyzeMode) {
1230           if (appData.noChessProgram) {
1231             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1236             return;
1237           }
1238           AnalyzeModeEvent();
1239         } else if (initialMode == AnalyzeFile) {
1240           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1241           ShowThinkingEvent();
1242           AnalyzeFileEvent();
1243           AnalysisPeriodicEvent(1);
1244         } else if (initialMode == MachinePlaysWhite) {
1245           if (appData.noChessProgram) {
1246             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1247                               0, 2);
1248             return;
1249           }
1250           if (appData.icsActive) {
1251             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1252                               0, 2);
1253             return;
1254           }
1255           MachineWhiteEvent();
1256         } else if (initialMode == MachinePlaysBlack) {
1257           if (appData.noChessProgram) {
1258             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1259                               0, 2);
1260             return;
1261           }
1262           if (appData.icsActive) {
1263             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1264                               0, 2);
1265             return;
1266           }
1267           MachineBlackEvent();
1268         } else if (initialMode == TwoMachinesPlay) {
1269           if (appData.noChessProgram) {
1270             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1271                               0, 2);
1272             return;
1273           }
1274           if (appData.icsActive) {
1275             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1276                               0, 2);
1277             return;
1278           }
1279           TwoMachinesEvent();
1280         } else if (initialMode == EditGame) {
1281           EditGameEvent();
1282         } else if (initialMode == EditPosition) {
1283           EditPositionEvent();
1284         } else if (initialMode == Training) {
1285           if (*appData.loadGameFile == NULLCHAR) {
1286             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1287             return;
1288           }
1289           TrainingEvent();
1290         }
1291     }
1292 }
1293
1294 /*
1295  * Establish will establish a contact to a remote host.port.
1296  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1297  *  used to talk to the host.
1298  * Returns 0 if okay, error code if not.
1299  */
1300 int
1301 establish()
1302 {
1303     char buf[MSG_SIZ];
1304
1305     if (*appData.icsCommPort != NULLCHAR) {
1306         /* Talk to the host through a serial comm port */
1307         return OpenCommPort(appData.icsCommPort, &icsPR);
1308
1309     } else if (*appData.gateway != NULLCHAR) {
1310         if (*appData.remoteShell == NULLCHAR) {
1311             /* Use the rcmd protocol to run telnet program on a gateway host */
1312             snprintf(buf, sizeof(buf), "%s %s %s",
1313                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1314             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1315
1316         } else {
1317             /* Use the rsh program to run telnet program on a gateway host */
1318             if (*appData.remoteUser == NULLCHAR) {
1319                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1320                         appData.gateway, appData.telnetProgram,
1321                         appData.icsHost, appData.icsPort);
1322             } else {
1323                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1324                         appData.remoteShell, appData.gateway, 
1325                         appData.remoteUser, appData.telnetProgram,
1326                         appData.icsHost, appData.icsPort);
1327             }
1328             return StartChildProcess(buf, "", &icsPR);
1329
1330         }
1331     } else if (appData.useTelnet) {
1332         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1333
1334     } else {
1335         /* TCP socket interface differs somewhat between
1336            Unix and NT; handle details in the front end.
1337            */
1338         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1339     }
1340 }
1341
1342 void
1343 show_bytes(fp, buf, count)
1344      FILE *fp;
1345      char *buf;
1346      int count;
1347 {
1348     while (count--) {
1349         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1350             fprintf(fp, "\\%03o", *buf & 0xff);
1351         } else {
1352             putc(*buf, fp);
1353         }
1354         buf++;
1355     }
1356     fflush(fp);
1357 }
1358
1359 /* Returns an errno value */
1360 int
1361 OutputMaybeTelnet(pr, message, count, outError)
1362      ProcRef pr;
1363      char *message;
1364      int count;
1365      int *outError;
1366 {
1367     char buf[8192], *p, *q, *buflim;
1368     int left, newcount, outcount;
1369
1370     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1371         *appData.gateway != NULLCHAR) {
1372         if (appData.debugMode) {
1373             fprintf(debugFP, ">ICS: ");
1374             show_bytes(debugFP, message, count);
1375             fprintf(debugFP, "\n");
1376         }
1377         return OutputToProcess(pr, message, count, outError);
1378     }
1379
1380     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1381     p = message;
1382     q = buf;
1383     left = count;
1384     newcount = 0;
1385     while (left) {
1386         if (q >= buflim) {
1387             if (appData.debugMode) {
1388                 fprintf(debugFP, ">ICS: ");
1389                 show_bytes(debugFP, buf, newcount);
1390                 fprintf(debugFP, "\n");
1391             }
1392             outcount = OutputToProcess(pr, buf, newcount, outError);
1393             if (outcount < newcount) return -1; /* to be sure */
1394             q = buf;
1395             newcount = 0;
1396         }
1397         if (*p == '\n') {
1398             *q++ = '\r';
1399             newcount++;
1400         } else if (((unsigned char) *p) == TN_IAC) {
1401             *q++ = (char) TN_IAC;
1402             newcount ++;
1403         }
1404         *q++ = *p++;
1405         newcount++;
1406         left--;
1407     }
1408     if (appData.debugMode) {
1409         fprintf(debugFP, ">ICS: ");
1410         show_bytes(debugFP, buf, newcount);
1411         fprintf(debugFP, "\n");
1412     }
1413     outcount = OutputToProcess(pr, buf, newcount, outError);
1414     if (outcount < newcount) return -1; /* to be sure */
1415     return count;
1416 }
1417
1418 void
1419 read_from_player(isr, closure, message, count, error)
1420      InputSourceRef isr;
1421      VOIDSTAR closure;
1422      char *message;
1423      int count;
1424      int error;
1425 {
1426     int outError, outCount;
1427     static int gotEof = 0;
1428
1429     /* Pass data read from player on to ICS */
1430     if (count > 0) {
1431         gotEof = 0;
1432         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1433         if (outCount < count) {
1434             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1435         }
1436     } else if (count < 0) {
1437         RemoveInputSource(isr);
1438         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1439     } else if (gotEof++ > 0) {
1440         RemoveInputSource(isr);
1441         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1442     }
1443 }
1444
1445 void
1446 KeepAlive()
1447 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1448     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1449     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1450     SendToICS("date\n");
1451     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1452 }
1453
1454 /* added routine for printf style output to ics */
1455 void ics_printf(char *format, ...)
1456 {
1457     char buffer[MSG_SIZ];
1458     va_list args;
1459
1460     va_start(args, format);
1461     vsnprintf(buffer, sizeof(buffer), format, args);
1462     buffer[sizeof(buffer)-1] = '\0';
1463     SendToICS(buffer);
1464     va_end(args);
1465 }
1466
1467 void
1468 SendToICS(s)
1469      char *s;
1470 {
1471     int count, outCount, outError;
1472
1473     if (icsPR == NULL) return;
1474
1475     count = strlen(s);
1476     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1477     if (outCount < count) {
1478         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1479     }
1480 }
1481
1482 /* This is used for sending logon scripts to the ICS. Sending
1483    without a delay causes problems when using timestamp on ICC
1484    (at least on my machine). */
1485 void
1486 SendToICSDelayed(s,msdelay)
1487      char *s;
1488      long msdelay;
1489 {
1490     int count, outCount, outError;
1491
1492     if (icsPR == NULL) return;
1493
1494     count = strlen(s);
1495     if (appData.debugMode) {
1496         fprintf(debugFP, ">ICS: ");
1497         show_bytes(debugFP, s, count);
1498         fprintf(debugFP, "\n");
1499     }
1500     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1501                                       msdelay);
1502     if (outCount < count) {
1503         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1504     }
1505 }
1506
1507
1508 /* Remove all highlighting escape sequences in s
1509    Also deletes any suffix starting with '(' 
1510    */
1511 char *
1512 StripHighlightAndTitle(s)
1513      char *s;
1514 {
1515     static char retbuf[MSG_SIZ];
1516     char *p = retbuf;
1517
1518     while (*s != NULLCHAR) {
1519         while (*s == '\033') {
1520             while (*s != NULLCHAR && !isalpha(*s)) s++;
1521             if (*s != NULLCHAR) s++;
1522         }
1523         while (*s != NULLCHAR && *s != '\033') {
1524             if (*s == '(' || *s == '[') {
1525                 *p = NULLCHAR;
1526                 return retbuf;
1527             }
1528             *p++ = *s++;
1529         }
1530     }
1531     *p = NULLCHAR;
1532     return retbuf;
1533 }
1534
1535 /* Remove all highlighting escape sequences in s */
1536 char *
1537 StripHighlight(s)
1538      char *s;
1539 {
1540     static char retbuf[MSG_SIZ];
1541     char *p = retbuf;
1542
1543     while (*s != NULLCHAR) {
1544         while (*s == '\033') {
1545             while (*s != NULLCHAR && !isalpha(*s)) s++;
1546             if (*s != NULLCHAR) s++;
1547         }
1548         while (*s != NULLCHAR && *s != '\033') {
1549             *p++ = *s++;
1550         }
1551     }
1552     *p = NULLCHAR;
1553     return retbuf;
1554 }
1555
1556 char *variantNames[] = VARIANT_NAMES;
1557 char *
1558 VariantName(v)
1559      VariantClass v;
1560 {
1561     return variantNames[v];
1562 }
1563
1564
1565 /* Identify a variant from the strings the chess servers use or the
1566    PGN Variant tag names we use. */
1567 VariantClass
1568 StringToVariant(e)
1569      char *e;
1570 {
1571     char *p;
1572     int wnum = -1;
1573     VariantClass v = VariantNormal;
1574     int i, found = FALSE;
1575     char buf[MSG_SIZ];
1576
1577     if (!e) return v;
1578
1579     /* [HGM] skip over optional board-size prefixes */
1580     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1581         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1582         while( *e++ != '_');
1583     }
1584
1585     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1586         v = VariantNormal;
1587         found = TRUE;
1588     } else
1589     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1590       if (StrCaseStr(e, variantNames[i])) {
1591         v = (VariantClass) i;
1592         found = TRUE;
1593         break;
1594       }
1595     }
1596
1597     if (!found) {
1598       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1599           || StrCaseStr(e, "wild/fr") 
1600           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1601         v = VariantFischeRandom;
1602       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1603                  (i = 1, p = StrCaseStr(e, "w"))) {
1604         p += i;
1605         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1606         if (isdigit(*p)) {
1607           wnum = atoi(p);
1608         } else {
1609           wnum = -1;
1610         }
1611         switch (wnum) {
1612         case 0: /* FICS only, actually */
1613         case 1:
1614           /* Castling legal even if K starts on d-file */
1615           v = VariantWildCastle;
1616           break;
1617         case 2:
1618         case 3:
1619         case 4:
1620           /* Castling illegal even if K & R happen to start in
1621              normal positions. */
1622           v = VariantNoCastle;
1623           break;
1624         case 5:
1625         case 7:
1626         case 8:
1627         case 10:
1628         case 11:
1629         case 12:
1630         case 13:
1631         case 14:
1632         case 15:
1633         case 18:
1634         case 19:
1635           /* Castling legal iff K & R start in normal positions */
1636           v = VariantNormal;
1637           break;
1638         case 6:
1639         case 20:
1640         case 21:
1641           /* Special wilds for position setup; unclear what to do here */
1642           v = VariantLoadable;
1643           break;
1644         case 9:
1645           /* Bizarre ICC game */
1646           v = VariantTwoKings;
1647           break;
1648         case 16:
1649           v = VariantKriegspiel;
1650           break;
1651         case 17:
1652           v = VariantLosers;
1653           break;
1654         case 22:
1655           v = VariantFischeRandom;
1656           break;
1657         case 23:
1658           v = VariantCrazyhouse;
1659           break;
1660         case 24:
1661           v = VariantBughouse;
1662           break;
1663         case 25:
1664           v = Variant3Check;
1665           break;
1666         case 26:
1667           /* Not quite the same as FICS suicide! */
1668           v = VariantGiveaway;
1669           break;
1670         case 27:
1671           v = VariantAtomic;
1672           break;
1673         case 28:
1674           v = VariantShatranj;
1675           break;
1676
1677         /* Temporary names for future ICC types.  The name *will* change in 
1678            the next xboard/WinBoard release after ICC defines it. */
1679         case 29:
1680           v = Variant29;
1681           break;
1682         case 30:
1683           v = Variant30;
1684           break;
1685         case 31:
1686           v = Variant31;
1687           break;
1688         case 32:
1689           v = Variant32;
1690           break;
1691         case 33:
1692           v = Variant33;
1693           break;
1694         case 34:
1695           v = Variant34;
1696           break;
1697         case 35:
1698           v = Variant35;
1699           break;
1700         case 36:
1701           v = Variant36;
1702           break;
1703         case 37:
1704           v = VariantShogi;
1705           break;
1706         case 38:
1707           v = VariantXiangqi;
1708           break;
1709         case 39:
1710           v = VariantCourier;
1711           break;
1712         case 40:
1713           v = VariantGothic;
1714           break;
1715         case 41:
1716           v = VariantCapablanca;
1717           break;
1718         case 42:
1719           v = VariantKnightmate;
1720           break;
1721         case 43:
1722           v = VariantFairy;
1723           break;
1724         case 44:
1725           v = VariantCylinder;
1726           break;
1727         case 45:
1728           v = VariantFalcon;
1729           break;
1730         case 46:
1731           v = VariantCapaRandom;
1732           break;
1733         case 47:
1734           v = VariantBerolina;
1735           break;
1736         case 48:
1737           v = VariantJanus;
1738           break;
1739         case 49:
1740           v = VariantSuper;
1741           break;
1742         case 50:
1743           v = VariantGreat;
1744           break;
1745         case -1:
1746           /* Found "wild" or "w" in the string but no number;
1747              must assume it's normal chess. */
1748           v = VariantNormal;
1749           break;
1750         default:
1751           sprintf(buf, _("Unknown wild type %d"), wnum);
1752           DisplayError(buf, 0);
1753           v = VariantUnknown;
1754           break;
1755         }
1756       }
1757     }
1758     if (appData.debugMode) {
1759       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1760               e, wnum, VariantName(v));
1761     }
1762     return v;
1763 }
1764
1765 static int leftover_start = 0, leftover_len = 0;
1766 char star_match[STAR_MATCH_N][MSG_SIZ];
1767
1768 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1769    advance *index beyond it, and set leftover_start to the new value of
1770    *index; else return FALSE.  If pattern contains the character '*', it
1771    matches any sequence of characters not containing '\r', '\n', or the
1772    character following the '*' (if any), and the matched sequence(s) are
1773    copied into star_match.
1774    */
1775 int
1776 looking_at(buf, index, pattern)
1777      char *buf;
1778      int *index;
1779      char *pattern;
1780 {
1781     char *bufp = &buf[*index], *patternp = pattern;
1782     int star_count = 0;
1783     char *matchp = star_match[0];
1784     
1785     for (;;) {
1786         if (*patternp == NULLCHAR) {
1787             *index = leftover_start = bufp - buf;
1788             *matchp = NULLCHAR;
1789             return TRUE;
1790         }
1791         if (*bufp == NULLCHAR) return FALSE;
1792         if (*patternp == '*') {
1793             if (*bufp == *(patternp + 1)) {
1794                 *matchp = NULLCHAR;
1795                 matchp = star_match[++star_count];
1796                 patternp += 2;
1797                 bufp++;
1798                 continue;
1799             } else if (*bufp == '\n' || *bufp == '\r') {
1800                 patternp++;
1801                 if (*patternp == NULLCHAR)
1802                   continue;
1803                 else
1804                   return FALSE;
1805             } else {
1806                 *matchp++ = *bufp++;
1807                 continue;
1808             }
1809         }
1810         if (*patternp != *bufp) return FALSE;
1811         patternp++;
1812         bufp++;
1813     }
1814 }
1815
1816 void
1817 SendToPlayer(data, length)
1818      char *data;
1819      int length;
1820 {
1821     int error, outCount;
1822     outCount = OutputToProcess(NoProc, data, length, &error);
1823     if (outCount < length) {
1824         DisplayFatalError(_("Error writing to display"), error, 1);
1825     }
1826 }
1827
1828 void
1829 PackHolding(packed, holding)
1830      char packed[];
1831      char *holding;
1832 {
1833     char *p = holding;
1834     char *q = packed;
1835     int runlength = 0;
1836     int curr = 9999;
1837     do {
1838         if (*p == curr) {
1839             runlength++;
1840         } else {
1841             switch (runlength) {
1842               case 0:
1843                 break;
1844               case 1:
1845                 *q++ = curr;
1846                 break;
1847               case 2:
1848                 *q++ = curr;
1849                 *q++ = curr;
1850                 break;
1851               default:
1852                 sprintf(q, "%d", runlength);
1853                 while (*q) q++;
1854                 *q++ = curr;
1855                 break;
1856             }
1857             runlength = 1;
1858             curr = *p;
1859         }
1860     } while (*p++);
1861     *q = NULLCHAR;
1862 }
1863
1864 /* Telnet protocol requests from the front end */
1865 void
1866 TelnetRequest(ddww, option)
1867      unsigned char ddww, option;
1868 {
1869     unsigned char msg[3];
1870     int outCount, outError;
1871
1872     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1873
1874     if (appData.debugMode) {
1875         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1876         switch (ddww) {
1877           case TN_DO:
1878             ddwwStr = "DO";
1879             break;
1880           case TN_DONT:
1881             ddwwStr = "DONT";
1882             break;
1883           case TN_WILL:
1884             ddwwStr = "WILL";
1885             break;
1886           case TN_WONT:
1887             ddwwStr = "WONT";
1888             break;
1889           default:
1890             ddwwStr = buf1;
1891             sprintf(buf1, "%d", ddww);
1892             break;
1893         }
1894         switch (option) {
1895           case TN_ECHO:
1896             optionStr = "ECHO";
1897             break;
1898           default:
1899             optionStr = buf2;
1900             sprintf(buf2, "%d", option);
1901             break;
1902         }
1903         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1904     }
1905     msg[0] = TN_IAC;
1906     msg[1] = ddww;
1907     msg[2] = option;
1908     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1909     if (outCount < 3) {
1910         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1911     }
1912 }
1913
1914 void
1915 DoEcho()
1916 {
1917     if (!appData.icsActive) return;
1918     TelnetRequest(TN_DO, TN_ECHO);
1919 }
1920
1921 void
1922 DontEcho()
1923 {
1924     if (!appData.icsActive) return;
1925     TelnetRequest(TN_DONT, TN_ECHO);
1926 }
1927
1928 void
1929 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1930 {
1931     /* put the holdings sent to us by the server on the board holdings area */
1932     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1933     char p;
1934     ChessSquare piece;
1935
1936     if(gameInfo.holdingsWidth < 2)  return;
1937     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1938         return; // prevent overwriting by pre-board holdings
1939
1940     if( (int)lowestPiece >= BlackPawn ) {
1941         holdingsColumn = 0;
1942         countsColumn = 1;
1943         holdingsStartRow = BOARD_HEIGHT-1;
1944         direction = -1;
1945     } else {
1946         holdingsColumn = BOARD_WIDTH-1;
1947         countsColumn = BOARD_WIDTH-2;
1948         holdingsStartRow = 0;
1949         direction = 1;
1950     }
1951
1952     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1953         board[i][holdingsColumn] = EmptySquare;
1954         board[i][countsColumn]   = (ChessSquare) 0;
1955     }
1956     while( (p=*holdings++) != NULLCHAR ) {
1957         piece = CharToPiece( ToUpper(p) );
1958         if(piece == EmptySquare) continue;
1959         /*j = (int) piece - (int) WhitePawn;*/
1960         j = PieceToNumber(piece);
1961         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1962         if(j < 0) continue;               /* should not happen */
1963         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1964         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1965         board[holdingsStartRow+j*direction][countsColumn]++;
1966     }
1967 }
1968
1969
1970 void
1971 VariantSwitch(Board board, VariantClass newVariant)
1972 {
1973    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1974    Board oldBoard;
1975
1976    startedFromPositionFile = FALSE;
1977    if(gameInfo.variant == newVariant) return;
1978
1979    /* [HGM] This routine is called each time an assignment is made to
1980     * gameInfo.variant during a game, to make sure the board sizes
1981     * are set to match the new variant. If that means adding or deleting
1982     * holdings, we shift the playing board accordingly
1983     * This kludge is needed because in ICS observe mode, we get boards
1984     * of an ongoing game without knowing the variant, and learn about the
1985     * latter only later. This can be because of the move list we requested,
1986     * in which case the game history is refilled from the beginning anyway,
1987     * but also when receiving holdings of a crazyhouse game. In the latter
1988     * case we want to add those holdings to the already received position.
1989     */
1990
1991    
1992    if (appData.debugMode) {
1993      fprintf(debugFP, "Switch board from %s to %s\n",
1994              VariantName(gameInfo.variant), VariantName(newVariant));
1995      setbuf(debugFP, NULL);
1996    }
1997    shuffleOpenings = 0;       /* [HGM] shuffle */
1998    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1999    switch(newVariant) 
2000      {
2001      case VariantShogi:
2002        newWidth = 9;  newHeight = 9;
2003        gameInfo.holdingsSize = 7;
2004      case VariantBughouse:
2005      case VariantCrazyhouse:
2006        newHoldingsWidth = 2; break;
2007      case VariantGreat:
2008        newWidth = 10;
2009      case VariantSuper:
2010        newHoldingsWidth = 2;
2011        gameInfo.holdingsSize = 8;
2012        break;
2013      case VariantGothic:
2014      case VariantCapablanca:
2015      case VariantCapaRandom:
2016        newWidth = 10;
2017      default:
2018        newHoldingsWidth = gameInfo.holdingsSize = 0;
2019      };
2020    
2021    if(newWidth  != gameInfo.boardWidth  ||
2022       newHeight != gameInfo.boardHeight ||
2023       newHoldingsWidth != gameInfo.holdingsWidth ) {
2024      
2025      /* shift position to new playing area, if needed */
2026      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2027        for(i=0; i<BOARD_HEIGHT; i++) 
2028          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2029            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2030              board[i][j];
2031        for(i=0; i<newHeight; i++) {
2032          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2033          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2034        }
2035      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2036        for(i=0; i<BOARD_HEIGHT; i++)
2037          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2038            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2039              board[i][j];
2040      }
2041      gameInfo.boardWidth  = newWidth;
2042      gameInfo.boardHeight = newHeight;
2043      gameInfo.holdingsWidth = newHoldingsWidth;
2044      gameInfo.variant = newVariant;
2045      InitDrawingSizes(-2, 0);
2046    } else gameInfo.variant = newVariant;
2047    CopyBoard(oldBoard, board);   // remember correctly formatted board
2048      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2049    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2050 }
2051
2052 static int loggedOn = FALSE;
2053
2054 /*-- Game start info cache: --*/
2055 int gs_gamenum;
2056 char gs_kind[MSG_SIZ];
2057 static char player1Name[128] = "";
2058 static char player2Name[128] = "";
2059 static char cont_seq[] = "\n\\   ";
2060 static int player1Rating = -1;
2061 static int player2Rating = -1;
2062 /*----------------------------*/
2063
2064 ColorClass curColor = ColorNormal;
2065 int suppressKibitz = 0;
2066
2067 // [HGM] seekgraph
2068 Boolean soughtPending = FALSE;
2069 Boolean seekGraphUp;
2070 #define MAX_SEEK_ADS 200
2071 #define SQUARE 0x80
2072 char *seekAdList[MAX_SEEK_ADS];
2073 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2074 float tcList[MAX_SEEK_ADS];
2075 char colorList[MAX_SEEK_ADS];
2076 int nrOfSeekAds = 0;
2077 int minRating = 1010, maxRating = 2800;
2078 int hMargin = 10, vMargin = 20, h, w;
2079 extern int squareSize, lineGap;
2080
2081 void
2082 PlotSeekAd(int i)
2083 {
2084         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2085         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2086         if(r < minRating+100 && r >=0 ) r = minRating+100;
2087         if(r > maxRating) r = maxRating;
2088         if(tc < 1.) tc = 1.;
2089         if(tc > 95.) tc = 95.;
2090         x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2091         y = ((double)r - minRating)/(maxRating - minRating)
2092             * (h-vMargin-squareSize/8-1) + vMargin;
2093         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2094         if(strstr(seekAdList[i], " u ")) color = 1;
2095         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2096            !strstr(seekAdList[i], "bullet") &&
2097            !strstr(seekAdList[i], "blitz") &&
2098            !strstr(seekAdList[i], "standard") ) color = 2;
2099         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2100         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2101 }
2102
2103 void
2104 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2105 {
2106         char buf[MSG_SIZ], *ext = "";
2107         VariantClass v = StringToVariant(type);
2108         if(strstr(type, "wild")) {
2109             ext = type + 4; // append wild number
2110             if(v == VariantFischeRandom) type = "chess960"; else
2111             if(v == VariantLoadable) type = "setup"; else
2112             type = VariantName(v);
2113         }
2114         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2115         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2116             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2117             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2118             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2119             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2120             seekNrList[nrOfSeekAds] = nr;
2121             zList[nrOfSeekAds] = 0;
2122             seekAdList[nrOfSeekAds++] = StrSave(buf);
2123             if(plot) PlotSeekAd(nrOfSeekAds-1);
2124         }
2125 }
2126
2127 void
2128 EraseSeekDot(int i)
2129 {
2130     int x = xList[i], y = yList[i], d=squareSize/4, k;
2131     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2132     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2133     // now replot every dot that overlapped
2134     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2135         int xx = xList[k], yy = yList[k];
2136         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2137             DrawSeekDot(xx, yy, colorList[k]);
2138     }
2139 }
2140
2141 void
2142 RemoveSeekAd(int nr)
2143 {
2144         int i;
2145         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2146             EraseSeekDot(i);
2147             if(seekAdList[i]) free(seekAdList[i]);
2148             seekAdList[i] = seekAdList[--nrOfSeekAds];
2149             seekNrList[i] = seekNrList[nrOfSeekAds];
2150             ratingList[i] = ratingList[nrOfSeekAds];
2151             colorList[i]  = colorList[nrOfSeekAds];
2152             tcList[i] = tcList[nrOfSeekAds];
2153             xList[i]  = xList[nrOfSeekAds];
2154             yList[i]  = yList[nrOfSeekAds];
2155             zList[i]  = zList[nrOfSeekAds];
2156             seekAdList[nrOfSeekAds] = NULL;
2157             break;
2158         }
2159 }
2160
2161 Boolean
2162 MatchSoughtLine(char *line)
2163 {
2164     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2165     int nr, base, inc, u=0; char dummy;
2166
2167     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2168        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2169        (u=1) &&
2170        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2171         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2172         // match: compact and save the line
2173         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2174         return TRUE;
2175     }
2176     return FALSE;
2177 }
2178
2179 int
2180 DrawSeekGraph()
2181 {
2182     if(!seekGraphUp) return FALSE;
2183     int i;
2184     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2185     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2186
2187     DrawSeekBackground(0, 0, w, h);
2188     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2189     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2190     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2191         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2192         yy = h-1-yy;
2193         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2194         if(i%500 == 0) {
2195             char buf[MSG_SIZ];
2196             sprintf(buf, "%d", i);
2197             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2198         }
2199     }
2200     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2201     for(i=1; i<100; i+=(i<10?1:5)) {
2202         int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2203         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2204         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2205             char buf[MSG_SIZ];
2206             sprintf(buf, "%d", i);
2207             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2208         }
2209     }
2210     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2211     return TRUE;
2212 }
2213
2214 int SeekGraphClick(ClickType click, int x, int y, int moving)
2215 {
2216     static int lastDown = 0, displayed = 0, lastSecond;
2217     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2218         if(click == Release || moving) return FALSE;
2219         nrOfSeekAds = 0;
2220         soughtPending = TRUE;
2221         SendToICS(ics_prefix);
2222         SendToICS("sought\n"); // should this be "sought all"?
2223     } else { // issue challenge based on clicked ad
2224         int dist = 10000; int i, closest = 0, second = 0;
2225         for(i=0; i<nrOfSeekAds; i++) {
2226             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2227             if(d < dist) { dist = d; closest = i; }
2228             second += (d - zList[i] < 120); // count in-range ads
2229             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2230         }
2231         if(dist < 120) {
2232             char buf[MSG_SIZ];
2233             second = (second > 1);
2234             if(displayed != closest || second != lastSecond) {
2235                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2236                 lastSecond = second; displayed = closest;
2237             }
2238             sprintf(buf, "play %d\n", seekNrList[closest]);
2239             if(click == Press) {
2240                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2241                 lastDown = closest;
2242                 return TRUE;
2243             } // on press 'hit', only show info
2244             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2245             SendToICS(ics_prefix);
2246             SendToICS(buf); // should this be "sought all"?
2247         } else if(click == Release) { // release 'miss' is ignored
2248             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2249             if(moving == 2) { // right up-click
2250                 nrOfSeekAds = 0; // refresh graph
2251                 soughtPending = TRUE;
2252                 SendToICS(ics_prefix);
2253                 SendToICS("sought\n"); // should this be "sought all"?
2254             }
2255             return TRUE;
2256         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2257         // press miss or release hit 'pop down' seek graph
2258         seekGraphUp = FALSE;
2259         DrawPosition(TRUE, NULL);
2260     }
2261     return TRUE;
2262 }
2263
2264 void
2265 read_from_ics(isr, closure, data, count, error)
2266      InputSourceRef isr;
2267      VOIDSTAR closure;
2268      char *data;
2269      int count;
2270      int error;
2271 {
2272 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2273 #define STARTED_NONE 0
2274 #define STARTED_MOVES 1
2275 #define STARTED_BOARD 2
2276 #define STARTED_OBSERVE 3
2277 #define STARTED_HOLDINGS 4
2278 #define STARTED_CHATTER 5
2279 #define STARTED_COMMENT 6
2280 #define STARTED_MOVES_NOHIDE 7
2281     
2282     static int started = STARTED_NONE;
2283     static char parse[20000];
2284     static int parse_pos = 0;
2285     static char buf[BUF_SIZE + 1];
2286     static int firstTime = TRUE, intfSet = FALSE;
2287     static ColorClass prevColor = ColorNormal;
2288     static int savingComment = FALSE;
2289     static int cmatch = 0; // continuation sequence match
2290     char *bp;
2291     char str[500];
2292     int i, oldi;
2293     int buf_len;
2294     int next_out;
2295     int tkind;
2296     int backup;    /* [DM] For zippy color lines */
2297     char *p;
2298     char talker[MSG_SIZ]; // [HGM] chat
2299     int channel;
2300
2301     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2302
2303     if (appData.debugMode) {
2304       if (!error) {
2305         fprintf(debugFP, "<ICS: ");
2306         show_bytes(debugFP, data, count);
2307         fprintf(debugFP, "\n");
2308       }
2309     }
2310
2311     if (appData.debugMode) { int f = forwardMostMove;
2312         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2313                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2314                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2315     }
2316     if (count > 0) {
2317         /* If last read ended with a partial line that we couldn't parse,
2318            prepend it to the new read and try again. */
2319         if (leftover_len > 0) {
2320             for (i=0; i<leftover_len; i++)
2321               buf[i] = buf[leftover_start + i];
2322         }
2323
2324     /* copy new characters into the buffer */
2325     bp = buf + leftover_len;
2326     buf_len=leftover_len;
2327     for (i=0; i<count; i++)
2328     {
2329         // ignore these
2330         if (data[i] == '\r')
2331             continue;
2332
2333         // join lines split by ICS?
2334         if (!appData.noJoin)
2335         {
2336             /*
2337                 Joining just consists of finding matches against the
2338                 continuation sequence, and discarding that sequence
2339                 if found instead of copying it.  So, until a match
2340                 fails, there's nothing to do since it might be the
2341                 complete sequence, and thus, something we don't want
2342                 copied.
2343             */
2344             if (data[i] == cont_seq[cmatch])
2345             {
2346                 cmatch++;
2347                 if (cmatch == strlen(cont_seq))
2348                 {
2349                     cmatch = 0; // complete match.  just reset the counter
2350
2351                     /*
2352                         it's possible for the ICS to not include the space
2353                         at the end of the last word, making our [correct]
2354                         join operation fuse two separate words.  the server
2355                         does this when the space occurs at the width setting.
2356                     */
2357                     if (!buf_len || buf[buf_len-1] != ' ')
2358                     {
2359                         *bp++ = ' ';
2360                         buf_len++;
2361                     }
2362                 }
2363                 continue;
2364             }
2365             else if (cmatch)
2366             {
2367                 /*
2368                     match failed, so we have to copy what matched before
2369                     falling through and copying this character.  In reality,
2370                     this will only ever be just the newline character, but
2371                     it doesn't hurt to be precise.
2372                 */
2373                 strncpy(bp, cont_seq, cmatch);
2374                 bp += cmatch;
2375                 buf_len += cmatch;
2376                 cmatch = 0;
2377             }
2378         }
2379
2380         // copy this char
2381         *bp++ = data[i];
2382         buf_len++;
2383     }
2384
2385         buf[buf_len] = NULLCHAR;
2386 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2387         next_out = 0;
2388         leftover_start = 0;
2389         
2390         i = 0;
2391         while (i < buf_len) {
2392             /* Deal with part of the TELNET option negotiation
2393                protocol.  We refuse to do anything beyond the
2394                defaults, except that we allow the WILL ECHO option,
2395                which ICS uses to turn off password echoing when we are
2396                directly connected to it.  We reject this option
2397                if localLineEditing mode is on (always on in xboard)
2398                and we are talking to port 23, which might be a real
2399                telnet server that will try to keep WILL ECHO on permanently.
2400              */
2401             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2402                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2403                 unsigned char option;
2404                 oldi = i;
2405                 switch ((unsigned char) buf[++i]) {
2406                   case TN_WILL:
2407                     if (appData.debugMode)
2408                       fprintf(debugFP, "\n<WILL ");
2409                     switch (option = (unsigned char) buf[++i]) {
2410                       case TN_ECHO:
2411                         if (appData.debugMode)
2412                           fprintf(debugFP, "ECHO ");
2413                         /* Reply only if this is a change, according
2414                            to the protocol rules. */
2415                         if (remoteEchoOption) break;
2416                         if (appData.localLineEditing &&
2417                             atoi(appData.icsPort) == TN_PORT) {
2418                             TelnetRequest(TN_DONT, TN_ECHO);
2419                         } else {
2420                             EchoOff();
2421                             TelnetRequest(TN_DO, TN_ECHO);
2422                             remoteEchoOption = TRUE;
2423                         }
2424                         break;
2425                       default:
2426                         if (appData.debugMode)
2427                           fprintf(debugFP, "%d ", option);
2428                         /* Whatever this is, we don't want it. */
2429                         TelnetRequest(TN_DONT, option);
2430                         break;
2431                     }
2432                     break;
2433                   case TN_WONT:
2434                     if (appData.debugMode)
2435                       fprintf(debugFP, "\n<WONT ");
2436                     switch (option = (unsigned char) buf[++i]) {
2437                       case TN_ECHO:
2438                         if (appData.debugMode)
2439                           fprintf(debugFP, "ECHO ");
2440                         /* Reply only if this is a change, according
2441                            to the protocol rules. */
2442                         if (!remoteEchoOption) break;
2443                         EchoOn();
2444                         TelnetRequest(TN_DONT, TN_ECHO);
2445                         remoteEchoOption = FALSE;
2446                         break;
2447                       default:
2448                         if (appData.debugMode)
2449                           fprintf(debugFP, "%d ", (unsigned char) option);
2450                         /* Whatever this is, it must already be turned
2451                            off, because we never agree to turn on
2452                            anything non-default, so according to the
2453                            protocol rules, we don't reply. */
2454                         break;
2455                     }
2456                     break;
2457                   case TN_DO:
2458                     if (appData.debugMode)
2459                       fprintf(debugFP, "\n<DO ");
2460                     switch (option = (unsigned char) buf[++i]) {
2461                       default:
2462                         /* Whatever this is, we refuse to do it. */
2463                         if (appData.debugMode)
2464                           fprintf(debugFP, "%d ", option);
2465                         TelnetRequest(TN_WONT, option);
2466                         break;
2467                     }
2468                     break;
2469                   case TN_DONT:
2470                     if (appData.debugMode)
2471                       fprintf(debugFP, "\n<DONT ");
2472                     switch (option = (unsigned char) buf[++i]) {
2473                       default:
2474                         if (appData.debugMode)
2475                           fprintf(debugFP, "%d ", option);
2476                         /* Whatever this is, we are already not doing
2477                            it, because we never agree to do anything
2478                            non-default, so according to the protocol
2479                            rules, we don't reply. */
2480                         break;
2481                     }
2482                     break;
2483                   case TN_IAC:
2484                     if (appData.debugMode)
2485                       fprintf(debugFP, "\n<IAC ");
2486                     /* Doubled IAC; pass it through */
2487                     i--;
2488                     break;
2489                   default:
2490                     if (appData.debugMode)
2491                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2492                     /* Drop all other telnet commands on the floor */
2493                     break;
2494                 }
2495                 if (oldi > next_out)
2496                   SendToPlayer(&buf[next_out], oldi - next_out);
2497                 if (++i > next_out)
2498                   next_out = i;
2499                 continue;
2500             }
2501                 
2502             /* OK, this at least will *usually* work */
2503             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2504                 loggedOn = TRUE;
2505             }
2506             
2507             if (loggedOn && !intfSet) {
2508                 if (ics_type == ICS_ICC) {
2509                   sprintf(str,
2510                           "/set-quietly interface %s\n/set-quietly style 12\n",
2511                           programVersion);
2512                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2513                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2514                 } else if (ics_type == ICS_CHESSNET) {
2515                   sprintf(str, "/style 12\n");
2516                 } else {
2517                   strcpy(str, "alias $ @\n$set interface ");
2518                   strcat(str, programVersion);
2519                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2520                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2521                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2522 #ifdef WIN32
2523                   strcat(str, "$iset nohighlight 1\n");
2524 #endif
2525                   strcat(str, "$iset lock 1\n$style 12\n");
2526                 }
2527                 SendToICS(str);
2528                 NotifyFrontendLogin();
2529                 intfSet = TRUE;
2530             }
2531
2532             if (started == STARTED_COMMENT) {
2533                 /* Accumulate characters in comment */
2534                 parse[parse_pos++] = buf[i];
2535                 if (buf[i] == '\n') {
2536                     parse[parse_pos] = NULLCHAR;
2537                     if(chattingPartner>=0) {
2538                         char mess[MSG_SIZ];
2539                         sprintf(mess, "%s%s", talker, parse);
2540                         OutputChatMessage(chattingPartner, mess);
2541                         chattingPartner = -1;
2542                     } else
2543                     if(!suppressKibitz) // [HGM] kibitz
2544                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2545                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2546                         int nrDigit = 0, nrAlph = 0, j;
2547                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2548                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2549                         parse[parse_pos] = NULLCHAR;
2550                         // try to be smart: if it does not look like search info, it should go to
2551                         // ICS interaction window after all, not to engine-output window.
2552                         for(j=0; j<parse_pos; j++) { // count letters and digits
2553                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2554                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2555                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2556                         }
2557                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2558                             int depth=0; float score;
2559                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2560                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2561                                 pvInfoList[forwardMostMove-1].depth = depth;
2562                                 pvInfoList[forwardMostMove-1].score = 100*score;
2563                             }
2564                             OutputKibitz(suppressKibitz, parse);
2565                             next_out = i+1; // [HGM] suppress printing in ICS window
2566                         } else {
2567                             char tmp[MSG_SIZ];
2568                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2569                             SendToPlayer(tmp, strlen(tmp));
2570                         }
2571                     }
2572                     started = STARTED_NONE;
2573                 } else {
2574                     /* Don't match patterns against characters in comment */
2575                     i++;
2576                     continue;
2577                 }
2578             }
2579             if (started == STARTED_CHATTER) {
2580                 if (buf[i] != '\n') {
2581                     /* Don't match patterns against characters in chatter */
2582                     i++;
2583                     continue;
2584                 }
2585                 started = STARTED_NONE;
2586             }
2587
2588             /* Kludge to deal with rcmd protocol */
2589             if (firstTime && looking_at(buf, &i, "\001*")) {
2590                 DisplayFatalError(&buf[1], 0, 1);
2591                 continue;
2592             } else {
2593                 firstTime = FALSE;
2594             }
2595
2596             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2597                 ics_type = ICS_ICC;
2598                 ics_prefix = "/";
2599                 if (appData.debugMode)
2600                   fprintf(debugFP, "ics_type %d\n", ics_type);
2601                 continue;
2602             }
2603             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2604                 ics_type = ICS_FICS;
2605                 ics_prefix = "$";
2606                 if (appData.debugMode)
2607                   fprintf(debugFP, "ics_type %d\n", ics_type);
2608                 continue;
2609             }
2610             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2611                 ics_type = ICS_CHESSNET;
2612                 ics_prefix = "/";
2613                 if (appData.debugMode)
2614                   fprintf(debugFP, "ics_type %d\n", ics_type);
2615                 continue;
2616             }
2617
2618             if (!loggedOn &&
2619                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2620                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2621                  looking_at(buf, &i, "will be \"*\""))) {
2622               strcpy(ics_handle, star_match[0]);
2623               continue;
2624             }
2625
2626             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2627               char buf[MSG_SIZ];
2628               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2629               DisplayIcsInteractionTitle(buf);
2630               have_set_title = TRUE;
2631             }
2632
2633             /* skip finger notes */
2634             if (started == STARTED_NONE &&
2635                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2636                  (buf[i] == '1' && buf[i+1] == '0')) &&
2637                 buf[i+2] == ':' && buf[i+3] == ' ') {
2638               started = STARTED_CHATTER;
2639               i += 3;
2640               continue;
2641             }
2642
2643             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2644             if(appData.seekGraph) {
2645                 if(soughtPending && MatchSoughtLine(buf+i)) {
2646                     i = strstr(buf+i, "rated") - buf;
2647                     next_out = leftover_start = i;
2648                     started = STARTED_CHATTER;
2649                     suppressKibitz = TRUE;
2650                     continue;
2651                 }
2652                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2653                         && looking_at(buf, &i, "* ads displayed")) {
2654                     soughtPending = FALSE;
2655                     seekGraphUp = TRUE;
2656                     DrawSeekGraph();
2657                     continue;
2658                 }
2659                 if(appData.autoRefresh) {
2660                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2661                         int s = (ics_type == ICS_ICC); // ICC format differs
2662                         if(seekGraphUp)
2663                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2664                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2665                         looking_at(buf, &i, "*% "); // eat prompt
2666                         next_out = i; // suppress
2667                         continue;
2668                     }
2669                     if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2670                         char *p = star_match[0];
2671                         while(*p) {
2672                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2673                             while(*p && *p++ != ' '); // next
2674                         }
2675                         looking_at(buf, &i, "*% "); // eat prompt
2676                         next_out = i;
2677                         continue;
2678                     }
2679                 }
2680             }
2681
2682             /* skip formula vars */
2683             if (started == STARTED_NONE &&
2684                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2685               started = STARTED_CHATTER;
2686               i += 3;
2687               continue;
2688             }
2689
2690             oldi = i;
2691             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2692             if (appData.autoKibitz && started == STARTED_NONE && 
2693                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2694                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2695                 if(looking_at(buf, &i, "* kibitzes: ") &&
2696                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2697                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2698                         suppressKibitz = TRUE;
2699                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2700                                 && (gameMode == IcsPlayingWhite)) ||
2701                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2702                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2703                             started = STARTED_CHATTER; // own kibitz we simply discard
2704                         else {
2705                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2706                             parse_pos = 0; parse[0] = NULLCHAR;
2707                             savingComment = TRUE;
2708                             suppressKibitz = gameMode != IcsObserving ? 2 :
2709                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2710                         } 
2711                         continue;
2712                 } else
2713                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2714                     // suppress the acknowledgements of our own autoKibitz
2715                     char *p;
2716                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2717                     SendToPlayer(star_match[0], strlen(star_match[0]));
2718                     looking_at(buf, &i, "*% "); // eat prompt
2719                     next_out = i;
2720                 }
2721             } // [HGM] kibitz: end of patch
2722
2723 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2724
2725             // [HGM] chat: intercept tells by users for which we have an open chat window
2726             channel = -1;
2727             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2728                                            looking_at(buf, &i, "* whispers:") ||
2729                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2730                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2731                 int p;
2732                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2733                 chattingPartner = -1;
2734
2735                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2736                 for(p=0; p<MAX_CHAT; p++) {
2737                     if(channel == atoi(chatPartner[p])) {
2738                     talker[0] = '['; strcat(talker, "] ");
2739                     chattingPartner = p; break;
2740                     }
2741                 } else
2742                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2743                 for(p=0; p<MAX_CHAT; p++) {
2744                     if(!strcmp("WHISPER", chatPartner[p])) {
2745                         talker[0] = '['; strcat(talker, "] ");
2746                         chattingPartner = p; break;
2747                     }
2748                 }
2749                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2750                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2751                     talker[0] = 0;
2752                     chattingPartner = p; break;
2753                 }
2754                 if(chattingPartner<0) i = oldi; else {
2755                     started = STARTED_COMMENT;
2756                     parse_pos = 0; parse[0] = NULLCHAR;
2757                     savingComment = 3 + chattingPartner; // counts as TRUE
2758                     suppressKibitz = TRUE;
2759                 }
2760             } // [HGM] chat: end of patch
2761
2762             if (appData.zippyTalk || appData.zippyPlay) {
2763                 /* [DM] Backup address for color zippy lines */
2764                 backup = i;
2765 #if ZIPPY
2766        #ifdef WIN32
2767                if (loggedOn == TRUE)
2768                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2769                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2770        #else
2771                 if (ZippyControl(buf, &i) ||
2772                     ZippyConverse(buf, &i) ||
2773                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2774                       loggedOn = TRUE;
2775                       if (!appData.colorize) continue;
2776                 }
2777        #endif
2778 #endif
2779             } // [DM] 'else { ' deleted
2780                 if (
2781                     /* Regular tells and says */
2782                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2783                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2784                     looking_at(buf, &i, "* says: ") ||
2785                     /* Don't color "message" or "messages" output */
2786                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2787                     looking_at(buf, &i, "*. * at *:*: ") ||
2788                     looking_at(buf, &i, "--* (*:*): ") ||
2789                     /* Message notifications (same color as tells) */
2790                     looking_at(buf, &i, "* has left a message ") ||
2791                     looking_at(buf, &i, "* just sent you a message:\n") ||
2792                     /* Whispers and kibitzes */
2793                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2794                     looking_at(buf, &i, "* kibitzes: ") ||
2795                     /* Channel tells */
2796                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2797
2798                   if (tkind == 1 && strchr(star_match[0], ':')) {
2799                       /* Avoid "tells you:" spoofs in channels */
2800                      tkind = 3;
2801                   }
2802                   if (star_match[0][0] == NULLCHAR ||
2803                       strchr(star_match[0], ' ') ||
2804                       (tkind == 3 && strchr(star_match[1], ' '))) {
2805                     /* Reject bogus matches */
2806                     i = oldi;
2807                   } else {
2808                     if (appData.colorize) {
2809                       if (oldi > next_out) {
2810                         SendToPlayer(&buf[next_out], oldi - next_out);
2811                         next_out = oldi;
2812                       }
2813                       switch (tkind) {
2814                       case 1:
2815                         Colorize(ColorTell, FALSE);
2816                         curColor = ColorTell;
2817                         break;
2818                       case 2:
2819                         Colorize(ColorKibitz, FALSE);
2820                         curColor = ColorKibitz;
2821                         break;
2822                       case 3:
2823                         p = strrchr(star_match[1], '(');
2824                         if (p == NULL) {
2825                           p = star_match[1];
2826                         } else {
2827                           p++;
2828                         }
2829                         if (atoi(p) == 1) {
2830                           Colorize(ColorChannel1, FALSE);
2831                           curColor = ColorChannel1;
2832                         } else {
2833                           Colorize(ColorChannel, FALSE);
2834                           curColor = ColorChannel;
2835                         }
2836                         break;
2837                       case 5:
2838                         curColor = ColorNormal;
2839                         break;
2840                       }
2841                     }
2842                     if (started == STARTED_NONE && appData.autoComment &&
2843                         (gameMode == IcsObserving ||
2844                          gameMode == IcsPlayingWhite ||
2845                          gameMode == IcsPlayingBlack)) {
2846                       parse_pos = i - oldi;
2847                       memcpy(parse, &buf[oldi], parse_pos);
2848                       parse[parse_pos] = NULLCHAR;
2849                       started = STARTED_COMMENT;
2850                       savingComment = TRUE;
2851                     } else {
2852                       started = STARTED_CHATTER;
2853                       savingComment = FALSE;
2854                     }
2855                     loggedOn = TRUE;
2856                     continue;
2857                   }
2858                 }
2859
2860                 if (looking_at(buf, &i, "* s-shouts: ") ||
2861                     looking_at(buf, &i, "* c-shouts: ")) {
2862                     if (appData.colorize) {
2863                         if (oldi > next_out) {
2864                             SendToPlayer(&buf[next_out], oldi - next_out);
2865                             next_out = oldi;
2866                         }
2867                         Colorize(ColorSShout, FALSE);
2868                         curColor = ColorSShout;
2869                     }
2870                     loggedOn = TRUE;
2871                     started = STARTED_CHATTER;
2872                     continue;
2873                 }
2874
2875                 if (looking_at(buf, &i, "--->")) {
2876                     loggedOn = TRUE;
2877                     continue;
2878                 }
2879
2880                 if (looking_at(buf, &i, "* shouts: ") ||
2881                     looking_at(buf, &i, "--> ")) {
2882                     if (appData.colorize) {
2883                         if (oldi > next_out) {
2884                             SendToPlayer(&buf[next_out], oldi - next_out);
2885                             next_out = oldi;
2886                         }
2887                         Colorize(ColorShout, FALSE);
2888                         curColor = ColorShout;
2889                     }
2890                     loggedOn = TRUE;
2891                     started = STARTED_CHATTER;
2892                     continue;
2893                 }
2894
2895                 if (looking_at( buf, &i, "Challenge:")) {
2896                     if (appData.colorize) {
2897                         if (oldi > next_out) {
2898                             SendToPlayer(&buf[next_out], oldi - next_out);
2899                             next_out = oldi;
2900                         }
2901                         Colorize(ColorChallenge, FALSE);
2902                         curColor = ColorChallenge;
2903                     }
2904                     loggedOn = TRUE;
2905                     continue;
2906                 }
2907
2908                 if (looking_at(buf, &i, "* offers you") ||
2909                     looking_at(buf, &i, "* offers to be") ||
2910                     looking_at(buf, &i, "* would like to") ||
2911                     looking_at(buf, &i, "* requests to") ||
2912                     looking_at(buf, &i, "Your opponent offers") ||
2913                     looking_at(buf, &i, "Your opponent requests")) {
2914
2915                     if (appData.colorize) {
2916                         if (oldi > next_out) {
2917                             SendToPlayer(&buf[next_out], oldi - next_out);
2918                             next_out = oldi;
2919                         }
2920                         Colorize(ColorRequest, FALSE);
2921                         curColor = ColorRequest;
2922                     }
2923                     continue;
2924                 }
2925
2926                 if (looking_at(buf, &i, "* (*) seeking")) {
2927                     if (appData.colorize) {
2928                         if (oldi > next_out) {
2929                             SendToPlayer(&buf[next_out], oldi - next_out);
2930                             next_out = oldi;
2931                         }
2932                         Colorize(ColorSeek, FALSE);
2933                         curColor = ColorSeek;
2934                     }
2935                     continue;
2936             }
2937
2938             if (looking_at(buf, &i, "\\   ")) {
2939                 if (prevColor != ColorNormal) {
2940                     if (oldi > next_out) {
2941                         SendToPlayer(&buf[next_out], oldi - next_out);
2942                         next_out = oldi;
2943                     }
2944                     Colorize(prevColor, TRUE);
2945                     curColor = prevColor;
2946                 }
2947                 if (savingComment) {
2948                     parse_pos = i - oldi;
2949                     memcpy(parse, &buf[oldi], parse_pos);
2950                     parse[parse_pos] = NULLCHAR;
2951                     started = STARTED_COMMENT;
2952                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2953                         chattingPartner = savingComment - 3; // kludge to remember the box
2954                 } else {
2955                     started = STARTED_CHATTER;
2956                 }
2957                 continue;
2958             }
2959
2960             if (looking_at(buf, &i, "Black Strength :") ||
2961                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2962                 looking_at(buf, &i, "<10>") ||
2963                 looking_at(buf, &i, "#@#")) {
2964                 /* Wrong board style */
2965                 loggedOn = TRUE;
2966                 SendToICS(ics_prefix);
2967                 SendToICS("set style 12\n");
2968                 SendToICS(ics_prefix);
2969                 SendToICS("refresh\n");
2970                 continue;
2971             }
2972             
2973             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2974                 ICSInitScript();
2975                 have_sent_ICS_logon = 1;
2976                 continue;
2977             }
2978               
2979             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2980                 (looking_at(buf, &i, "\n<12> ") ||
2981                  looking_at(buf, &i, "<12> "))) {
2982                 loggedOn = TRUE;
2983                 if (oldi > next_out) {
2984                     SendToPlayer(&buf[next_out], oldi - next_out);
2985                 }
2986                 next_out = i;
2987                 started = STARTED_BOARD;
2988                 parse_pos = 0;
2989                 continue;
2990             }
2991
2992             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2993                 looking_at(buf, &i, "<b1> ")) {
2994                 if (oldi > next_out) {
2995                     SendToPlayer(&buf[next_out], oldi - next_out);
2996                 }
2997                 next_out = i;
2998                 started = STARTED_HOLDINGS;
2999                 parse_pos = 0;
3000                 continue;
3001             }
3002
3003             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3004                 loggedOn = TRUE;
3005                 /* Header for a move list -- first line */
3006
3007                 switch (ics_getting_history) {
3008                   case H_FALSE:
3009                     switch (gameMode) {
3010                       case IcsIdle:
3011                       case BeginningOfGame:
3012                         /* User typed "moves" or "oldmoves" while we
3013                            were idle.  Pretend we asked for these
3014                            moves and soak them up so user can step
3015                            through them and/or save them.
3016                            */
3017                         Reset(FALSE, TRUE);
3018                         gameMode = IcsObserving;
3019                         ModeHighlight();
3020                         ics_gamenum = -1;
3021                         ics_getting_history = H_GOT_UNREQ_HEADER;
3022                         break;
3023                       case EditGame: /*?*/
3024                       case EditPosition: /*?*/
3025                         /* Should above feature work in these modes too? */
3026                         /* For now it doesn't */
3027                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3028                         break;
3029                       default:
3030                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3031                         break;
3032                     }
3033                     break;
3034                   case H_REQUESTED:
3035                     /* Is this the right one? */
3036                     if (gameInfo.white && gameInfo.black &&
3037                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3038                         strcmp(gameInfo.black, star_match[2]) == 0) {
3039                         /* All is well */
3040                         ics_getting_history = H_GOT_REQ_HEADER;
3041                     }
3042                     break;
3043                   case H_GOT_REQ_HEADER:
3044                   case H_GOT_UNREQ_HEADER:
3045                   case H_GOT_UNWANTED_HEADER:
3046                   case H_GETTING_MOVES:
3047                     /* Should not happen */
3048                     DisplayError(_("Error gathering move list: two headers"), 0);
3049                     ics_getting_history = H_FALSE;
3050                     break;
3051                 }
3052
3053                 /* Save player ratings into gameInfo if needed */
3054                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3055                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3056                     (gameInfo.whiteRating == -1 ||
3057                      gameInfo.blackRating == -1)) {
3058
3059                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3060                     gameInfo.blackRating = string_to_rating(star_match[3]);
3061                     if (appData.debugMode)
3062                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3063                               gameInfo.whiteRating, gameInfo.blackRating);
3064                 }
3065                 continue;
3066             }
3067
3068             if (looking_at(buf, &i,
3069               "* * match, initial time: * minute*, increment: * second")) {
3070                 /* Header for a move list -- second line */
3071                 /* Initial board will follow if this is a wild game */
3072                 if (gameInfo.event != NULL) free(gameInfo.event);
3073                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3074                 gameInfo.event = StrSave(str);
3075                 /* [HGM] we switched variant. Translate boards if needed. */
3076                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3077                 continue;
3078             }
3079
3080             if (looking_at(buf, &i, "Move  ")) {
3081                 /* Beginning of a move list */
3082                 switch (ics_getting_history) {
3083                   case H_FALSE:
3084                     /* Normally should not happen */
3085                     /* Maybe user hit reset while we were parsing */
3086                     break;
3087                   case H_REQUESTED:
3088                     /* Happens if we are ignoring a move list that is not
3089                      * the one we just requested.  Common if the user
3090                      * tries to observe two games without turning off
3091                      * getMoveList */
3092                     break;
3093                   case H_GETTING_MOVES:
3094                     /* Should not happen */
3095                     DisplayError(_("Error gathering move list: nested"), 0);
3096                     ics_getting_history = H_FALSE;
3097                     break;
3098                   case H_GOT_REQ_HEADER:
3099                     ics_getting_history = H_GETTING_MOVES;
3100                     started = STARTED_MOVES;
3101                     parse_pos = 0;
3102                     if (oldi > next_out) {
3103                         SendToPlayer(&buf[next_out], oldi - next_out);
3104                     }
3105                     break;
3106                   case H_GOT_UNREQ_HEADER:
3107                     ics_getting_history = H_GETTING_MOVES;
3108                     started = STARTED_MOVES_NOHIDE;
3109                     parse_pos = 0;
3110                     break;
3111                   case H_GOT_UNWANTED_HEADER:
3112                     ics_getting_history = H_FALSE;
3113                     break;
3114                 }
3115                 continue;
3116             }                           
3117             
3118             if (looking_at(buf, &i, "% ") ||
3119                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3120                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3121                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3122                     soughtPending = FALSE;
3123                     seekGraphUp = TRUE;
3124                     DrawSeekGraph();
3125                 }
3126                 if(suppressKibitz) next_out = i;
3127                 savingComment = FALSE;
3128                 suppressKibitz = 0;
3129                 switch (started) {
3130                   case STARTED_MOVES:
3131                   case STARTED_MOVES_NOHIDE:
3132                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3133                     parse[parse_pos + i - oldi] = NULLCHAR;
3134                     ParseGameHistory(parse);
3135 #if ZIPPY
3136                     if (appData.zippyPlay && first.initDone) {
3137                         FeedMovesToProgram(&first, forwardMostMove);
3138                         if (gameMode == IcsPlayingWhite) {
3139                             if (WhiteOnMove(forwardMostMove)) {
3140                                 if (first.sendTime) {
3141                                   if (first.useColors) {
3142                                     SendToProgram("black\n", &first); 
3143                                   }
3144                                   SendTimeRemaining(&first, TRUE);
3145                                 }
3146                                 if (first.useColors) {
3147                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3148                                 }
3149                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3150                                 first.maybeThinking = TRUE;
3151                             } else {
3152                                 if (first.usePlayother) {
3153                                   if (first.sendTime) {
3154                                     SendTimeRemaining(&first, TRUE);
3155                                   }
3156                                   SendToProgram("playother\n", &first);
3157                                   firstMove = FALSE;
3158                                 } else {
3159                                   firstMove = TRUE;
3160                                 }
3161                             }
3162                         } else if (gameMode == IcsPlayingBlack) {
3163                             if (!WhiteOnMove(forwardMostMove)) {
3164                                 if (first.sendTime) {
3165                                   if (first.useColors) {
3166                                     SendToProgram("white\n", &first);
3167                                   }
3168                                   SendTimeRemaining(&first, FALSE);
3169                                 }
3170                                 if (first.useColors) {
3171                                   SendToProgram("black\n", &first);
3172                                 }
3173                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3174                                 first.maybeThinking = TRUE;
3175                             } else {
3176                                 if (first.usePlayother) {
3177                                   if (first.sendTime) {
3178                                     SendTimeRemaining(&first, FALSE);
3179                                   }
3180                                   SendToProgram("playother\n", &first);
3181                                   firstMove = FALSE;
3182                                 } else {
3183                                   firstMove = TRUE;
3184                                 }
3185                             }
3186                         }                       
3187                     }
3188 #endif
3189                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3190                         /* Moves came from oldmoves or moves command
3191                            while we weren't doing anything else.
3192                            */
3193                         currentMove = forwardMostMove;
3194                         ClearHighlights();/*!!could figure this out*/
3195                         flipView = appData.flipView;
3196                         DrawPosition(TRUE, boards[currentMove]);
3197                         DisplayBothClocks();
3198                         sprintf(str, "%s vs. %s",
3199                                 gameInfo.white, gameInfo.black);
3200                         DisplayTitle(str);
3201                         gameMode = IcsIdle;
3202                     } else {
3203                         /* Moves were history of an active game */
3204                         if (gameInfo.resultDetails != NULL) {
3205                             free(gameInfo.resultDetails);
3206                             gameInfo.resultDetails = NULL;
3207                         }
3208                     }
3209                     HistorySet(parseList, backwardMostMove,
3210                                forwardMostMove, currentMove-1);
3211                     DisplayMove(currentMove - 1);
3212                     if (started == STARTED_MOVES) next_out = i;
3213                     started = STARTED_NONE;
3214                     ics_getting_history = H_FALSE;
3215                     break;
3216
3217                   case STARTED_OBSERVE:
3218                     started = STARTED_NONE;
3219                     SendToICS(ics_prefix);
3220                     SendToICS("refresh\n");
3221                     break;
3222
3223                   default:
3224                     break;
3225                 }
3226                 if(bookHit) { // [HGM] book: simulate book reply
3227                     static char bookMove[MSG_SIZ]; // a bit generous?
3228
3229                     programStats.nodes = programStats.depth = programStats.time = 
3230                     programStats.score = programStats.got_only_move = 0;
3231                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3232
3233                     strcpy(bookMove, "move ");
3234                     strcat(bookMove, bookHit);
3235                     HandleMachineMove(bookMove, &first);
3236                 }
3237                 continue;
3238             }
3239             
3240             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3241                  started == STARTED_HOLDINGS ||
3242                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3243                 /* Accumulate characters in move list or board */
3244                 parse[parse_pos++] = buf[i];
3245             }
3246             
3247             /* Start of game messages.  Mostly we detect start of game
3248                when the first board image arrives.  On some versions
3249                of the ICS, though, we need to do a "refresh" after starting
3250                to observe in order to get the current board right away. */
3251             if (looking_at(buf, &i, "Adding game * to observation list")) {
3252                 started = STARTED_OBSERVE;
3253                 continue;
3254             }
3255
3256             /* Handle auto-observe */
3257             if (appData.autoObserve &&
3258                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3259                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3260                 char *player;
3261                 /* Choose the player that was highlighted, if any. */
3262                 if (star_match[0][0] == '\033' ||
3263                     star_match[1][0] != '\033') {
3264                     player = star_match[0];
3265                 } else {
3266                     player = star_match[2];
3267                 }
3268                 sprintf(str, "%sobserve %s\n",
3269                         ics_prefix, StripHighlightAndTitle(player));
3270                 SendToICS(str);
3271
3272                 /* Save ratings from notify string */
3273                 strcpy(player1Name, star_match[0]);
3274                 player1Rating = string_to_rating(star_match[1]);
3275                 strcpy(player2Name, star_match[2]);
3276                 player2Rating = string_to_rating(star_match[3]);
3277
3278                 if (appData.debugMode)
3279                   fprintf(debugFP, 
3280                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3281                           player1Name, player1Rating,
3282                           player2Name, player2Rating);
3283
3284                 continue;
3285             }
3286
3287             /* Deal with automatic examine mode after a game,
3288                and with IcsObserving -> IcsExamining transition */
3289             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3290                 looking_at(buf, &i, "has made you an examiner of game *")) {
3291
3292                 int gamenum = atoi(star_match[0]);
3293                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3294                     gamenum == ics_gamenum) {
3295                     /* We were already playing or observing this game;
3296                        no need to refetch history */
3297                     gameMode = IcsExamining;
3298                     if (pausing) {
3299                         pauseExamForwardMostMove = forwardMostMove;
3300                     } else if (currentMove < forwardMostMove) {
3301                         ForwardInner(forwardMostMove);
3302                     }
3303                 } else {
3304                     /* I don't think this case really can happen */
3305                     SendToICS(ics_prefix);
3306                     SendToICS("refresh\n");
3307                 }
3308                 continue;
3309             }    
3310             
3311             /* Error messages */
3312 //          if (ics_user_moved) {
3313             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3314                 if (looking_at(buf, &i, "Illegal move") ||
3315                     looking_at(buf, &i, "Not a legal move") ||
3316                     looking_at(buf, &i, "Your king is in check") ||
3317                     looking_at(buf, &i, "It isn't your turn") ||
3318                     looking_at(buf, &i, "It is not your move")) {
3319                     /* Illegal move */
3320                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3321                         currentMove = --forwardMostMove;
3322                         DisplayMove(currentMove - 1); /* before DMError */
3323                         DrawPosition(FALSE, boards[currentMove]);
3324                         SwitchClocks();
3325                         DisplayBothClocks();
3326                     }
3327                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3328                     ics_user_moved = 0;
3329                     continue;
3330                 }
3331             }
3332
3333             if (looking_at(buf, &i, "still have time") ||
3334                 looking_at(buf, &i, "not out of time") ||
3335                 looking_at(buf, &i, "either player is out of time") ||
3336                 looking_at(buf, &i, "has timeseal; checking")) {
3337                 /* We must have called his flag a little too soon */
3338                 whiteFlag = blackFlag = FALSE;
3339                 continue;
3340             }
3341
3342             if (looking_at(buf, &i, "added * seconds to") ||
3343                 looking_at(buf, &i, "seconds were added to")) {
3344                 /* Update the clocks */
3345                 SendToICS(ics_prefix);
3346                 SendToICS("refresh\n");
3347                 continue;
3348             }
3349
3350             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3351                 ics_clock_paused = TRUE;
3352                 StopClocks();
3353                 continue;
3354             }
3355
3356             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3357                 ics_clock_paused = FALSE;
3358                 StartClocks();
3359                 continue;
3360             }
3361
3362             /* Grab player ratings from the Creating: message.
3363                Note we have to check for the special case when
3364                the ICS inserts things like [white] or [black]. */
3365             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3366                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3367                 /* star_matches:
3368                    0    player 1 name (not necessarily white)
3369                    1    player 1 rating
3370                    2    empty, white, or black (IGNORED)
3371                    3    player 2 name (not necessarily black)
3372                    4    player 2 rating
3373                    
3374                    The names/ratings are sorted out when the game
3375                    actually starts (below).
3376                 */
3377                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3378                 player1Rating = string_to_rating(star_match[1]);
3379                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3380                 player2Rating = string_to_rating(star_match[4]);
3381
3382                 if (appData.debugMode)
3383                   fprintf(debugFP, 
3384                           "Ratings from 'Creating:' %s %d, %s %d\n",
3385                           player1Name, player1Rating,
3386                           player2Name, player2Rating);
3387
3388                 continue;
3389             }
3390             
3391             /* Improved generic start/end-of-game messages */
3392             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3393                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3394                 /* If tkind == 0: */
3395                 /* star_match[0] is the game number */
3396                 /*           [1] is the white player's name */
3397                 /*           [2] is the black player's name */
3398                 /* For end-of-game: */
3399                 /*           [3] is the reason for the game end */
3400                 /*           [4] is a PGN end game-token, preceded by " " */
3401                 /* For start-of-game: */
3402                 /*           [3] begins with "Creating" or "Continuing" */
3403                 /*           [4] is " *" or empty (don't care). */
3404                 int gamenum = atoi(star_match[0]);
3405                 char *whitename, *blackname, *why, *endtoken;
3406                 ChessMove endtype = (ChessMove) 0;
3407
3408                 if (tkind == 0) {
3409                   whitename = star_match[1];
3410                   blackname = star_match[2];
3411                   why = star_match[3];
3412                   endtoken = star_match[4];
3413                 } else {
3414                   whitename = star_match[1];
3415                   blackname = star_match[3];
3416                   why = star_match[5];
3417                   endtoken = star_match[6];
3418                 }
3419
3420                 /* Game start messages */
3421                 if (strncmp(why, "Creating ", 9) == 0 ||
3422                     strncmp(why, "Continuing ", 11) == 0) {
3423                     gs_gamenum = gamenum;
3424                     strcpy(gs_kind, strchr(why, ' ') + 1);
3425                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3426 #if ZIPPY
3427                     if (appData.zippyPlay) {
3428                         ZippyGameStart(whitename, blackname);
3429                     }
3430 #endif /*ZIPPY*/
3431                     continue;
3432                 }
3433
3434                 /* Game end messages */
3435                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3436                     ics_gamenum != gamenum) {
3437                     continue;
3438                 }
3439                 while (endtoken[0] == ' ') endtoken++;
3440                 switch (endtoken[0]) {
3441                   case '*':
3442                   default:
3443                     endtype = GameUnfinished;
3444                     break;
3445                   case '0':
3446                     endtype = BlackWins;
3447                     break;
3448                   case '1':
3449                     if (endtoken[1] == '/')
3450                       endtype = GameIsDrawn;
3451                     else
3452                       endtype = WhiteWins;
3453                     break;
3454                 }
3455                 GameEnds(endtype, why, GE_ICS);
3456 #if ZIPPY
3457                 if (appData.zippyPlay && first.initDone) {
3458                     ZippyGameEnd(endtype, why);
3459                     if (first.pr == NULL) {
3460                       /* Start the next process early so that we'll
3461                          be ready for the next challenge */
3462                       StartChessProgram(&first);
3463                     }
3464                     /* Send "new" early, in case this command takes
3465                        a long time to finish, so that we'll be ready
3466                        for the next challenge. */
3467                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3468                     Reset(TRUE, TRUE);
3469                 }
3470 #endif /*ZIPPY*/
3471                 continue;
3472             }
3473
3474             if (looking_at(buf, &i, "Removing game * from observation") ||
3475                 looking_at(buf, &i, "no longer observing game *") ||
3476                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3477                 if (gameMode == IcsObserving &&
3478                     atoi(star_match[0]) == ics_gamenum)
3479                   {
3480                       /* icsEngineAnalyze */
3481                       if (appData.icsEngineAnalyze) {
3482                             ExitAnalyzeMode();
3483                             ModeHighlight();
3484                       }
3485                       StopClocks();
3486                       gameMode = IcsIdle;
3487                       ics_gamenum = -1;
3488                       ics_user_moved = FALSE;
3489                   }
3490                 continue;
3491             }
3492
3493             if (looking_at(buf, &i, "no longer examining game *")) {
3494                 if (gameMode == IcsExamining &&
3495                     atoi(star_match[0]) == ics_gamenum)
3496                   {
3497                       gameMode = IcsIdle;
3498                       ics_gamenum = -1;
3499                       ics_user_moved = FALSE;
3500                   }
3501                 continue;
3502             }
3503
3504             /* Advance leftover_start past any newlines we find,
3505                so only partial lines can get reparsed */
3506             if (looking_at(buf, &i, "\n")) {
3507                 prevColor = curColor;
3508                 if (curColor != ColorNormal) {
3509                     if (oldi > next_out) {
3510                         SendToPlayer(&buf[next_out], oldi - next_out);
3511                         next_out = oldi;
3512                     }
3513                     Colorize(ColorNormal, FALSE);
3514                     curColor = ColorNormal;
3515                 }
3516                 if (started == STARTED_BOARD) {
3517                     started = STARTED_NONE;
3518                     parse[parse_pos] = NULLCHAR;
3519                     ParseBoard12(parse);
3520                     ics_user_moved = 0;
3521
3522                     /* Send premove here */
3523                     if (appData.premove) {
3524                       char str[MSG_SIZ];
3525                       if (currentMove == 0 &&
3526                           gameMode == IcsPlayingWhite &&
3527                           appData.premoveWhite) {
3528                         sprintf(str, "%s\n", appData.premoveWhiteText);
3529                         if (appData.debugMode)
3530                           fprintf(debugFP, "Sending premove:\n");
3531                         SendToICS(str);
3532                       } else if (currentMove == 1 &&
3533                                  gameMode == IcsPlayingBlack &&
3534                                  appData.premoveBlack) {
3535                         sprintf(str, "%s\n", appData.premoveBlackText);
3536                         if (appData.debugMode)
3537                           fprintf(debugFP, "Sending premove:\n");
3538                         SendToICS(str);
3539                       } else if (gotPremove) {
3540                         gotPremove = 0;
3541                         ClearPremoveHighlights();
3542                         if (appData.debugMode)
3543                           fprintf(debugFP, "Sending premove:\n");
3544                           UserMoveEvent(premoveFromX, premoveFromY, 
3545                                         premoveToX, premoveToY, 
3546                                         premovePromoChar);
3547                       }
3548                     }
3549
3550                     /* Usually suppress following prompt */
3551                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3552                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3553                         if (looking_at(buf, &i, "*% ")) {
3554                             savingComment = FALSE;
3555                             suppressKibitz = 0;
3556                         }
3557                     }
3558                     next_out = i;
3559                 } else if (started == STARTED_HOLDINGS) {
3560                     int gamenum;
3561                     char new_piece[MSG_SIZ];
3562                     started = STARTED_NONE;
3563                     parse[parse_pos] = NULLCHAR;
3564                     if (appData.debugMode)
3565                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3566                                                         parse, currentMove);
3567                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3568                         gamenum == ics_gamenum) {
3569                         if (gameInfo.variant == VariantNormal) {
3570                           /* [HGM] We seem to switch variant during a game!
3571                            * Presumably no holdings were displayed, so we have
3572                            * to move the position two files to the right to
3573                            * create room for them!
3574                            */
3575                           VariantClass newVariant;
3576                           switch(gameInfo.boardWidth) { // base guess on board width
3577                                 case 9:  newVariant = VariantShogi; break;
3578                                 case 10: newVariant = VariantGreat; break;
3579                                 default: newVariant = VariantCrazyhouse; break;
3580                           }
3581                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3582                           /* Get a move list just to see the header, which
3583                              will tell us whether this is really bug or zh */
3584                           if (ics_getting_history == H_FALSE) {
3585                             ics_getting_history = H_REQUESTED;
3586                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3587                             SendToICS(str);
3588                           }
3589                         }
3590                         new_piece[0] = NULLCHAR;
3591                         sscanf(parse, "game %d white [%s black [%s <- %s",
3592                                &gamenum, white_holding, black_holding,
3593                                new_piece);
3594                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3595                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3596                         /* [HGM] copy holdings to board holdings area */
3597                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3598                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3599                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3600 #if ZIPPY
3601                         if (appData.zippyPlay && first.initDone) {
3602                             ZippyHoldings(white_holding, black_holding,
3603                                           new_piece);
3604                         }
3605 #endif /*ZIPPY*/
3606                         if (tinyLayout || smallLayout) {
3607                             char wh[16], bh[16];
3608                             PackHolding(wh, white_holding);
3609                             PackHolding(bh, black_holding);
3610                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3611                                     gameInfo.white, gameInfo.black);
3612                         } else {
3613                             sprintf(str, "%s [%s] vs. %s [%s]",
3614                                     gameInfo.white, white_holding,
3615                                     gameInfo.black, black_holding);
3616                         }
3617
3618                         DrawPosition(FALSE, boards[currentMove]);
3619                         DisplayTitle(str);
3620                     }
3621                     /* Suppress following prompt */
3622                     if (looking_at(buf, &i, "*% ")) {
3623                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3624                         savingComment = FALSE;
3625                         suppressKibitz = 0;
3626                     }
3627                     next_out = i;
3628                 }
3629                 continue;
3630             }
3631
3632             i++;                /* skip unparsed character and loop back */
3633         }
3634         
3635         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3636 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3637 //          SendToPlayer(&buf[next_out], i - next_out);
3638             started != STARTED_HOLDINGS && leftover_start > next_out) {
3639             SendToPlayer(&buf[next_out], leftover_start - next_out);
3640             next_out = i;
3641         }
3642         
3643         leftover_len = buf_len - leftover_start;
3644         /* if buffer ends with something we couldn't parse,
3645            reparse it after appending the next read */
3646         
3647     } else if (count == 0) {
3648         RemoveInputSource(isr);
3649         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3650     } else {
3651         DisplayFatalError(_("Error reading from ICS"), error, 1);
3652     }
3653 }
3654
3655
3656 /* Board style 12 looks like this:
3657    
3658    <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
3659    
3660  * The "<12> " is stripped before it gets to this routine.  The two
3661  * trailing 0's (flip state and clock ticking) are later addition, and
3662  * some chess servers may not have them, or may have only the first.
3663  * Additional trailing fields may be added in the future.  
3664  */
3665
3666 #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"
3667
3668 #define RELATION_OBSERVING_PLAYED    0
3669 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3670 #define RELATION_PLAYING_MYMOVE      1
3671 #define RELATION_PLAYING_NOTMYMOVE  -1
3672 #define RELATION_EXAMINING           2
3673 #define RELATION_ISOLATED_BOARD     -3
3674 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3675
3676 void
3677 ParseBoard12(string)
3678      char *string;
3679
3680     GameMode newGameMode;
3681     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3682     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3683     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3684     char to_play, board_chars[200];
3685     char move_str[500], str[500], elapsed_time[500];
3686     char black[32], white[32];
3687     Board board;
3688     int prevMove = currentMove;
3689     int ticking = 2;
3690     ChessMove moveType;
3691     int fromX, fromY, toX, toY;
3692     char promoChar;
3693     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3694     char *bookHit = NULL; // [HGM] book
3695     Boolean weird = FALSE, reqFlag = FALSE;
3696
3697     fromX = fromY = toX = toY = -1;
3698     
3699     newGame = FALSE;
3700
3701     if (appData.debugMode)
3702       fprintf(debugFP, _("Parsing board: %s\n"), string);
3703
3704     move_str[0] = NULLCHAR;
3705     elapsed_time[0] = NULLCHAR;
3706     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3707         int  i = 0, j;
3708         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3709             if(string[i] == ' ') { ranks++; files = 0; }
3710             else files++;
3711             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3712             i++;
3713         }
3714         for(j = 0; j <i; j++) board_chars[j] = string[j];
3715         board_chars[i] = '\0';
3716         string += i + 1;
3717     }
3718     n = sscanf(string, PATTERN, &to_play, &double_push,
3719                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3720                &gamenum, white, black, &relation, &basetime, &increment,
3721                &white_stren, &black_stren, &white_time, &black_time,
3722                &moveNum, str, elapsed_time, move_str, &ics_flip,
3723                &ticking);
3724
3725     if (n < 21) {
3726         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3727         DisplayError(str, 0);
3728         return;
3729     }
3730
3731     /* Convert the move number to internal form */
3732     moveNum = (moveNum - 1) * 2;
3733     if (to_play == 'B') moveNum++;
3734     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3735       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3736                         0, 1);
3737       return;
3738     }
3739     
3740     switch (relation) {
3741       case RELATION_OBSERVING_PLAYED:
3742       case RELATION_OBSERVING_STATIC:
3743         if (gamenum == -1) {
3744             /* Old ICC buglet */
3745             relation = RELATION_OBSERVING_STATIC;
3746         }
3747         newGameMode = IcsObserving;
3748         break;
3749       case RELATION_PLAYING_MYMOVE:
3750       case RELATION_PLAYING_NOTMYMOVE:
3751         newGameMode =
3752           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3753             IcsPlayingWhite : IcsPlayingBlack;
3754         break;
3755       case RELATION_EXAMINING:
3756         newGameMode = IcsExamining;
3757         break;
3758       case RELATION_ISOLATED_BOARD:
3759       default:
3760         /* Just display this board.  If user was doing something else,
3761            we will forget about it until the next board comes. */ 
3762         newGameMode = IcsIdle;
3763         break;
3764       case RELATION_STARTING_POSITION:
3765         newGameMode = gameMode;
3766         break;
3767     }
3768     
3769     /* Modify behavior for initial board display on move listing
3770        of wild games.
3771        */
3772     switch (ics_getting_history) {
3773       case H_FALSE:
3774       case H_REQUESTED:
3775         break;
3776       case H_GOT_REQ_HEADER:
3777       case H_GOT_UNREQ_HEADER:
3778         /* This is the initial position of the current game */
3779         gamenum = ics_gamenum;
3780         moveNum = 0;            /* old ICS bug workaround */
3781         if (to_play == 'B') {
3782           startedFromSetupPosition = TRUE;
3783           blackPlaysFirst = TRUE;
3784           moveNum = 1;
3785           if (forwardMostMove == 0) forwardMostMove = 1;
3786           if (backwardMostMove == 0) backwardMostMove = 1;
3787           if (currentMove == 0) currentMove = 1;
3788         }
3789         newGameMode = gameMode;
3790         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3791         break;
3792       case H_GOT_UNWANTED_HEADER:
3793         /* This is an initial board that we don't want */
3794         return;
3795       case H_GETTING_MOVES:
3796         /* Should not happen */
3797         DisplayError(_("Error gathering move list: extra board"), 0);
3798         ics_getting_history = H_FALSE;
3799         return;
3800     }
3801
3802    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3803                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3804      /* [HGM] We seem to have switched variant unexpectedly
3805       * Try to guess new variant from board size
3806       */
3807           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3808           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3809           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3810           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3811           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3812           if(!weird) newVariant = VariantNormal;
3813           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3814           /* Get a move list just to see the header, which
3815              will tell us whether this is really bug or zh */
3816           if (ics_getting_history == H_FALSE) {
3817             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3818             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3819             SendToICS(str);
3820           }
3821     }
3822     
3823     /* Take action if this is the first board of a new game, or of a
3824        different game than is currently being displayed.  */
3825     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3826         relation == RELATION_ISOLATED_BOARD) {
3827         
3828         /* Forget the old game and get the history (if any) of the new one */
3829         if (gameMode != BeginningOfGame) {
3830           Reset(TRUE, TRUE);
3831         }
3832         newGame = TRUE;
3833         if (appData.autoRaiseBoard) BoardToTop();
3834         prevMove = -3;
3835         if (gamenum == -1) {
3836             newGameMode = IcsIdle;
3837         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3838                    appData.getMoveList && !reqFlag) {
3839             /* Need to get game history */
3840             ics_getting_history = H_REQUESTED;
3841             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3842             SendToICS(str);
3843         }
3844         
3845         /* Initially flip the board to have black on the bottom if playing
3846            black or if the ICS flip flag is set, but let the user change
3847            it with the Flip View button. */
3848         flipView = appData.autoFlipView ? 
3849           (newGameMode == IcsPlayingBlack) || ics_flip :
3850           appData.flipView;
3851         
3852         /* Done with values from previous mode; copy in new ones */
3853         gameMode = newGameMode;
3854         ModeHighlight();
3855         ics_gamenum = gamenum;
3856         if (gamenum == gs_gamenum) {
3857             int klen = strlen(gs_kind);
3858             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3859             sprintf(str, "ICS %s", gs_kind);
3860             gameInfo.event = StrSave(str);
3861         } else {
3862             gameInfo.event = StrSave("ICS game");
3863         }
3864         gameInfo.site = StrSave(appData.icsHost);
3865         gameInfo.date = PGNDate();
3866         gameInfo.round = StrSave("-");
3867         gameInfo.white = StrSave(white);
3868         gameInfo.black = StrSave(black);
3869         timeControl = basetime * 60 * 1000;
3870         timeControl_2 = 0;
3871         timeIncrement = increment * 1000;
3872         movesPerSession = 0;
3873         gameInfo.timeControl = TimeControlTagValue();
3874         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3875   if (appData.debugMode) {
3876     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3877     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3878     setbuf(debugFP, NULL);
3879   }
3880
3881         gameInfo.outOfBook = NULL;
3882         
3883         /* Do we have the ratings? */
3884         if (strcmp(player1Name, white) == 0 &&
3885             strcmp(player2Name, black) == 0) {
3886             if (appData.debugMode)
3887               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3888                       player1Rating, player2Rating);
3889             gameInfo.whiteRating = player1Rating;
3890             gameInfo.blackRating = player2Rating;
3891         } else if (strcmp(player2Name, white) == 0 &&
3892                    strcmp(player1Name, black) == 0) {
3893             if (appData.debugMode)
3894               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3895                       player2Rating, player1Rating);
3896             gameInfo.whiteRating = player2Rating;
3897             gameInfo.blackRating = player1Rating;
3898         }
3899         player1Name[0] = player2Name[0] = NULLCHAR;
3900
3901         /* Silence shouts if requested */
3902         if (appData.quietPlay &&
3903             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3904             SendToICS(ics_prefix);
3905             SendToICS("set shout 0\n");
3906         }
3907     }
3908     
3909     /* Deal with midgame name changes */
3910     if (!newGame) {
3911         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3912             if (gameInfo.white) free(gameInfo.white);
3913             gameInfo.white = StrSave(white);
3914         }
3915         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3916             if (gameInfo.black) free(gameInfo.black);
3917             gameInfo.black = StrSave(black);
3918         }
3919     }
3920     
3921     /* Throw away game result if anything actually changes in examine mode */
3922     if (gameMode == IcsExamining && !newGame) {
3923         gameInfo.result = GameUnfinished;
3924         if (gameInfo.resultDetails != NULL) {
3925             free(gameInfo.resultDetails);
3926             gameInfo.resultDetails = NULL;
3927         }
3928     }
3929     
3930     /* In pausing && IcsExamining mode, we ignore boards coming
3931        in if they are in a different variation than we are. */
3932     if (pauseExamInvalid) return;
3933     if (pausing && gameMode == IcsExamining) {
3934         if (moveNum <= pauseExamForwardMostMove) {
3935             pauseExamInvalid = TRUE;
3936             forwardMostMove = pauseExamForwardMostMove;
3937             return;
3938         }
3939     }
3940     
3941   if (appData.debugMode) {
3942     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3943   }
3944     /* Parse the board */
3945     for (k = 0; k < ranks; k++) {
3946       for (j = 0; j < files; j++)
3947         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3948       if(gameInfo.holdingsWidth > 1) {
3949            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3950            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3951       }
3952     }
3953     CopyBoard(boards[moveNum], board);
3954     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3955     if (moveNum == 0) {
3956         startedFromSetupPosition =
3957           !CompareBoards(board, initialPosition);
3958         if(startedFromSetupPosition)
3959             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3960     }
3961
3962     /* [HGM] Set castling rights. Take the outermost Rooks,
3963        to make it also work for FRC opening positions. Note that board12
3964        is really defective for later FRC positions, as it has no way to
3965        indicate which Rook can castle if they are on the same side of King.
3966        For the initial position we grant rights to the outermost Rooks,
3967        and remember thos rights, and we then copy them on positions
3968        later in an FRC game. This means WB might not recognize castlings with
3969        Rooks that have moved back to their original position as illegal,
3970        but in ICS mode that is not its job anyway.
3971     */
3972     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3973     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3974
3975         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3976             if(board[0][i] == WhiteRook) j = i;
3977         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3978         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3979             if(board[0][i] == WhiteRook) j = i;
3980         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3981         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3982             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3983         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3984         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3985             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3986         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3987
3988         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3989         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3990             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3991         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3992             if(board[BOARD_HEIGHT-1][k] == bKing)
3993                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3994         if(gameInfo.variant == VariantTwoKings) {
3995             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3996             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3997             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3998         }
3999     } else { int r;
4000         r = boards[moveNum][CASTLING][0] = initialRights[0];
4001         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4002         r = boards[moveNum][CASTLING][1] = initialRights[1];
4003         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4004         r = boards[moveNum][CASTLING][3] = initialRights[3];
4005         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4006         r = boards[moveNum][CASTLING][4] = initialRights[4];
4007         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4008         /* wildcastle kludge: always assume King has rights */
4009         r = boards[moveNum][CASTLING][2] = initialRights[2];
4010         r = boards[moveNum][CASTLING][5] = initialRights[5];
4011     }
4012     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4013     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4014
4015     
4016     if (ics_getting_history == H_GOT_REQ_HEADER ||
4017         ics_getting_history == H_GOT_UNREQ_HEADER) {
4018         /* This was an initial position from a move list, not
4019            the current position */
4020         return;
4021     }
4022     
4023     /* Update currentMove and known move number limits */
4024     newMove = newGame || moveNum > forwardMostMove;
4025
4026     if (newGame) {
4027         forwardMostMove = backwardMostMove = currentMove = moveNum;
4028         if (gameMode == IcsExamining && moveNum == 0) {
4029           /* Workaround for ICS limitation: we are not told the wild
4030              type when starting to examine a game.  But if we ask for
4031              the move list, the move list header will tell us */
4032             ics_getting_history = H_REQUESTED;
4033             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4034             SendToICS(str);
4035         }
4036     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4037                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4038 #if ZIPPY
4039         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4040         /* [HGM] applied this also to an engine that is silently watching        */
4041         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4042             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4043             gameInfo.variant == currentlyInitializedVariant) {
4044           takeback = forwardMostMove - moveNum;
4045           for (i = 0; i < takeback; i++) {
4046             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4047             SendToProgram("undo\n", &first);
4048           }
4049         }
4050 #endif
4051
4052         forwardMostMove = moveNum;
4053         if (!pausing || currentMove > forwardMostMove)
4054           currentMove = forwardMostMove;
4055     } else {
4056         /* New part of history that is not contiguous with old part */ 
4057         if (pausing && gameMode == IcsExamining) {
4058             pauseExamInvalid = TRUE;
4059             forwardMostMove = pauseExamForwardMostMove;
4060             return;
4061         }
4062         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4063 #if ZIPPY
4064             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4065                 // [HGM] when we will receive the move list we now request, it will be
4066                 // fed to the engine from the first move on. So if the engine is not
4067                 // in the initial position now, bring it there.
4068                 InitChessProgram(&first, 0);
4069             }
4070 #endif
4071             ics_getting_history = H_REQUESTED;
4072             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4073             SendToICS(str);
4074         }
4075         forwardMostMove = backwardMostMove = currentMove = moveNum;
4076     }
4077     
4078     /* Update the clocks */
4079     if (strchr(elapsed_time, '.')) {
4080       /* Time is in ms */
4081       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4082       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4083     } else {
4084       /* Time is in seconds */
4085       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4086       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4087     }
4088       
4089
4090 #if ZIPPY
4091     if (appData.zippyPlay && newGame &&
4092         gameMode != IcsObserving && gameMode != IcsIdle &&
4093         gameMode != IcsExamining)
4094       ZippyFirstBoard(moveNum, basetime, increment);
4095 #endif
4096     
4097     /* Put the move on the move list, first converting
4098        to canonical algebraic form. */
4099     if (moveNum > 0) {
4100   if (appData.debugMode) {
4101     if (appData.debugMode) { int f = forwardMostMove;
4102         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4103                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4104                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4105     }
4106     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4107     fprintf(debugFP, "moveNum = %d\n", moveNum);
4108     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4109     setbuf(debugFP, NULL);
4110   }
4111         if (moveNum <= backwardMostMove) {
4112             /* We don't know what the board looked like before
4113                this move.  Punt. */
4114             strcpy(parseList[moveNum - 1], move_str);
4115             strcat(parseList[moveNum - 1], " ");
4116             strcat(parseList[moveNum - 1], elapsed_time);
4117             moveList[moveNum - 1][0] = NULLCHAR;
4118         } else if (strcmp(move_str, "none") == 0) {
4119             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4120             /* Again, we don't know what the board looked like;
4121                this is really the start of the game. */
4122             parseList[moveNum - 1][0] = NULLCHAR;
4123             moveList[moveNum - 1][0] = NULLCHAR;
4124             backwardMostMove = moveNum;
4125             startedFromSetupPosition = TRUE;
4126             fromX = fromY = toX = toY = -1;
4127         } else {
4128           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4129           //                 So we parse the long-algebraic move string in stead of the SAN move
4130           int valid; char buf[MSG_SIZ], *prom;
4131
4132           // str looks something like "Q/a1-a2"; kill the slash
4133           if(str[1] == '/') 
4134                 sprintf(buf, "%c%s", str[0], str+2);
4135           else  strcpy(buf, str); // might be castling
4136           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4137                 strcat(buf, prom); // long move lacks promo specification!
4138           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4139                 if(appData.debugMode) 
4140                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4141                 strcpy(move_str, buf);
4142           }
4143           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4144                                 &fromX, &fromY, &toX, &toY, &promoChar)
4145                || ParseOneMove(buf, moveNum - 1, &moveType,
4146                                 &fromX, &fromY, &toX, &toY, &promoChar);
4147           // end of long SAN patch
4148           if (valid) {
4149             (void) CoordsToAlgebraic(boards[moveNum - 1],
4150                                      PosFlags(moveNum - 1),
4151                                      fromY, fromX, toY, toX, promoChar,
4152                                      parseList[moveNum-1]);
4153             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4154               case MT_NONE:
4155               case MT_STALEMATE:
4156               default:
4157                 break;
4158               case MT_CHECK:
4159                 if(gameInfo.variant != VariantShogi)
4160                     strcat(parseList[moveNum - 1], "+");
4161                 break;
4162               case MT_CHECKMATE:
4163               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4164                 strcat(parseList[moveNum - 1], "#");
4165                 break;
4166             }
4167             strcat(parseList[moveNum - 1], " ");
4168             strcat(parseList[moveNum - 1], elapsed_time);
4169             /* currentMoveString is set as a side-effect of ParseOneMove */
4170             strcpy(moveList[moveNum - 1], currentMoveString);
4171             strcat(moveList[moveNum - 1], "\n");
4172           } else {
4173             /* Move from ICS was illegal!?  Punt. */
4174   if (appData.debugMode) {
4175     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4176     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4177   }
4178             strcpy(parseList[moveNum - 1], move_str);
4179             strcat(parseList[moveNum - 1], " ");
4180             strcat(parseList[moveNum - 1], elapsed_time);
4181             moveList[moveNum - 1][0] = NULLCHAR;
4182             fromX = fromY = toX = toY = -1;
4183           }
4184         }
4185   if (appData.debugMode) {
4186     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4187     setbuf(debugFP, NULL);
4188   }
4189
4190 #if ZIPPY
4191         /* Send move to chess program (BEFORE animating it). */
4192         if (appData.zippyPlay && !newGame && newMove && 
4193            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4194
4195             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4196                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4197                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4198                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4199                             move_str);
4200                     DisplayError(str, 0);
4201                 } else {
4202                     if (first.sendTime) {
4203                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4204                     }
4205                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4206                     if (firstMove && !bookHit) {
4207                         firstMove = FALSE;
4208                         if (first.useColors) {
4209                           SendToProgram(gameMode == IcsPlayingWhite ?
4210                                         "white\ngo\n" :
4211                                         "black\ngo\n", &first);
4212                         } else {
4213                           SendToProgram("go\n", &first);
4214                         }
4215                         first.maybeThinking = TRUE;
4216                     }
4217                 }
4218             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4219               if (moveList[moveNum - 1][0] == NULLCHAR) {
4220                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4221                 DisplayError(str, 0);
4222               } else {
4223                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4224                 SendMoveToProgram(moveNum - 1, &first);
4225               }
4226             }
4227         }
4228 #endif
4229     }
4230
4231     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4232         /* If move comes from a remote source, animate it.  If it
4233            isn't remote, it will have already been animated. */
4234         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4235             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4236         }
4237         if (!pausing && appData.highlightLastMove) {
4238             SetHighlights(fromX, fromY, toX, toY);
4239         }
4240     }
4241     
4242     /* Start the clocks */
4243     whiteFlag = blackFlag = FALSE;
4244     appData.clockMode = !(basetime == 0 && increment == 0);
4245     if (ticking == 0) {
4246       ics_clock_paused = TRUE;
4247       StopClocks();
4248     } else if (ticking == 1) {
4249       ics_clock_paused = FALSE;
4250     }
4251     if (gameMode == IcsIdle ||
4252         relation == RELATION_OBSERVING_STATIC ||
4253         relation == RELATION_EXAMINING ||
4254         ics_clock_paused)
4255       DisplayBothClocks();
4256     else
4257       StartClocks();
4258     
4259     /* Display opponents and material strengths */
4260     if (gameInfo.variant != VariantBughouse &&
4261         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4262         if (tinyLayout || smallLayout) {
4263             if(gameInfo.variant == VariantNormal)
4264                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4265                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4266                     basetime, increment);
4267             else
4268                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4269                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4270                     basetime, increment, (int) gameInfo.variant);
4271         } else {
4272             if(gameInfo.variant == VariantNormal)
4273                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4274                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4275                     basetime, increment);
4276             else
4277                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4278                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4279                     basetime, increment, VariantName(gameInfo.variant));
4280         }
4281         DisplayTitle(str);
4282   if (appData.debugMode) {
4283     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4284   }
4285     }
4286
4287
4288     /* Display the board */
4289     if (!pausing && !appData.noGUI) {
4290       
4291       if (appData.premove)
4292           if (!gotPremove || 
4293              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4294              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4295               ClearPremoveHighlights();
4296
4297       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4298       DrawPosition(j, boards[currentMove]);
4299
4300       DisplayMove(moveNum - 1);
4301       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4302             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4303               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4304         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4305       }
4306     }
4307
4308     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4309 #if ZIPPY
4310     if(bookHit) { // [HGM] book: simulate book reply
4311         static char bookMove[MSG_SIZ]; // a bit generous?
4312
4313         programStats.nodes = programStats.depth = programStats.time = 
4314         programStats.score = programStats.got_only_move = 0;
4315         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4316
4317         strcpy(bookMove, "move ");
4318         strcat(bookMove, bookHit);
4319         HandleMachineMove(bookMove, &first);
4320     }
4321 #endif
4322 }
4323
4324 void
4325 GetMoveListEvent()
4326 {
4327     char buf[MSG_SIZ];
4328     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4329         ics_getting_history = H_REQUESTED;
4330         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4331         SendToICS(buf);
4332     }
4333 }
4334
4335 void
4336 AnalysisPeriodicEvent(force)
4337      int force;
4338 {
4339     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4340          && !force) || !appData.periodicUpdates)
4341       return;
4342
4343     /* Send . command to Crafty to collect stats */
4344     SendToProgram(".\n", &first);
4345
4346     /* Don't send another until we get a response (this makes
4347        us stop sending to old Crafty's which don't understand
4348        the "." command (sending illegal cmds resets node count & time,
4349        which looks bad)) */
4350     programStats.ok_to_send = 0;
4351 }
4352
4353 void ics_update_width(new_width)
4354         int new_width;
4355 {
4356         ics_printf("set width %d\n", new_width);
4357 }
4358
4359 void
4360 SendMoveToProgram(moveNum, cps)
4361      int moveNum;
4362      ChessProgramState *cps;
4363 {
4364     char buf[MSG_SIZ];
4365
4366     if (cps->useUsermove) {
4367       SendToProgram("usermove ", cps);
4368     }
4369     if (cps->useSAN) {
4370       char *space;
4371       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4372         int len = space - parseList[moveNum];
4373         memcpy(buf, parseList[moveNum], len);
4374         buf[len++] = '\n';
4375         buf[len] = NULLCHAR;
4376       } else {
4377         sprintf(buf, "%s\n", parseList[moveNum]);
4378       }
4379       SendToProgram(buf, cps);
4380     } else {
4381       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4382         AlphaRank(moveList[moveNum], 4);
4383         SendToProgram(moveList[moveNum], cps);
4384         AlphaRank(moveList[moveNum], 4); // and back
4385       } else
4386       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4387        * the engine. It would be nice to have a better way to identify castle 
4388        * moves here. */
4389       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4390                                                                          && cps->useOOCastle) {
4391         int fromX = moveList[moveNum][0] - AAA; 
4392         int fromY = moveList[moveNum][1] - ONE;
4393         int toX = moveList[moveNum][2] - AAA; 
4394         int toY = moveList[moveNum][3] - ONE;
4395         if((boards[moveNum][fromY][fromX] == WhiteKing 
4396             && boards[moveNum][toY][toX] == WhiteRook)
4397            || (boards[moveNum][fromY][fromX] == BlackKing 
4398                && boards[moveNum][toY][toX] == BlackRook)) {
4399           if(toX > fromX) SendToProgram("O-O\n", cps);
4400           else SendToProgram("O-O-O\n", cps);
4401         }
4402         else SendToProgram(moveList[moveNum], cps);
4403       }
4404       else SendToProgram(moveList[moveNum], cps);
4405       /* End of additions by Tord */
4406     }
4407
4408     /* [HGM] setting up the opening has brought engine in force mode! */
4409     /*       Send 'go' if we are in a mode where machine should play. */
4410     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4411         (gameMode == TwoMachinesPlay   ||
4412 #ifdef ZIPPY
4413          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4414 #endif
4415          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4416         SendToProgram("go\n", cps);
4417   if (appData.debugMode) {
4418     fprintf(debugFP, "(extra)\n");
4419   }
4420     }
4421     setboardSpoiledMachineBlack = 0;
4422 }
4423
4424 void
4425 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4426      ChessMove moveType;
4427      int fromX, fromY, toX, toY;
4428 {
4429     char user_move[MSG_SIZ];
4430
4431     switch (moveType) {
4432       default:
4433         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4434                 (int)moveType, fromX, fromY, toX, toY);
4435         DisplayError(user_move + strlen("say "), 0);
4436         break;
4437       case WhiteKingSideCastle:
4438       case BlackKingSideCastle:
4439       case WhiteQueenSideCastleWild:
4440       case BlackQueenSideCastleWild:
4441       /* PUSH Fabien */
4442       case WhiteHSideCastleFR:
4443       case BlackHSideCastleFR:
4444       /* POP Fabien */
4445         sprintf(user_move, "o-o\n");
4446         break;
4447       case WhiteQueenSideCastle:
4448       case BlackQueenSideCastle:
4449       case WhiteKingSideCastleWild:
4450       case BlackKingSideCastleWild:
4451       /* PUSH Fabien */
4452       case WhiteASideCastleFR:
4453       case BlackASideCastleFR:
4454       /* POP Fabien */
4455         sprintf(user_move, "o-o-o\n");
4456         break;
4457       case WhitePromotionQueen:
4458       case BlackPromotionQueen:
4459       case WhitePromotionRook:
4460       case BlackPromotionRook:
4461       case WhitePromotionBishop:
4462       case BlackPromotionBishop:
4463       case WhitePromotionKnight:
4464       case BlackPromotionKnight:
4465       case WhitePromotionKing:
4466       case BlackPromotionKing:
4467       case WhitePromotionChancellor:
4468       case BlackPromotionChancellor:
4469       case WhitePromotionArchbishop:
4470       case BlackPromotionArchbishop:
4471         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4472             sprintf(user_move, "%c%c%c%c=%c\n",
4473                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4474                 PieceToChar(WhiteFerz));
4475         else if(gameInfo.variant == VariantGreat)
4476             sprintf(user_move, "%c%c%c%c=%c\n",
4477                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4478                 PieceToChar(WhiteMan));
4479         else
4480             sprintf(user_move, "%c%c%c%c=%c\n",
4481                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4482                 PieceToChar(PromoPiece(moveType)));
4483         break;
4484       case WhiteDrop:
4485       case BlackDrop:
4486         sprintf(user_move, "%c@%c%c\n",
4487                 ToUpper(PieceToChar((ChessSquare) fromX)),
4488                 AAA + toX, ONE + toY);
4489         break;
4490       case NormalMove:
4491       case WhiteCapturesEnPassant:
4492       case BlackCapturesEnPassant:
4493       case IllegalMove:  /* could be a variant we don't quite understand */
4494         sprintf(user_move, "%c%c%c%c\n",
4495                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4496         break;
4497     }
4498     SendToICS(user_move);
4499     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4500         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4501 }
4502
4503 void
4504 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4505      int rf, ff, rt, ft;
4506      char promoChar;
4507      char move[7];
4508 {
4509     if (rf == DROP_RANK) {
4510         sprintf(move, "%c@%c%c\n",
4511                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4512     } else {
4513         if (promoChar == 'x' || promoChar == NULLCHAR) {
4514             sprintf(move, "%c%c%c%c\n",
4515                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4516         } else {
4517             sprintf(move, "%c%c%c%c%c\n",
4518                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4519         }
4520     }
4521 }
4522
4523 void
4524 ProcessICSInitScript(f)
4525      FILE *f;
4526 {
4527     char buf[MSG_SIZ];
4528
4529     while (fgets(buf, MSG_SIZ, f)) {
4530         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4531     }
4532
4533     fclose(f);
4534 }
4535
4536
4537 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4538 void
4539 AlphaRank(char *move, int n)
4540 {
4541 //    char *p = move, c; int x, y;
4542
4543     if (appData.debugMode) {
4544         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4545     }
4546
4547     if(move[1]=='*' && 
4548        move[2]>='0' && move[2]<='9' &&
4549        move[3]>='a' && move[3]<='x'    ) {
4550         move[1] = '@';
4551         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4552         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4553     } else
4554     if(move[0]>='0' && move[0]<='9' &&
4555        move[1]>='a' && move[1]<='x' &&
4556        move[2]>='0' && move[2]<='9' &&
4557        move[3]>='a' && move[3]<='x'    ) {
4558         /* input move, Shogi -> normal */
4559         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4560         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4561         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4562         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4563     } else
4564     if(move[1]=='@' &&
4565        move[3]>='0' && move[3]<='9' &&
4566        move[2]>='a' && move[2]<='x'    ) {
4567         move[1] = '*';
4568         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4569         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4570     } else
4571     if(
4572        move[0]>='a' && move[0]<='x' &&
4573        move[3]>='0' && move[3]<='9' &&
4574        move[2]>='a' && move[2]<='x'    ) {
4575          /* output move, normal -> Shogi */
4576         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4577         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4578         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4579         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4580         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4581     }
4582     if (appData.debugMode) {
4583         fprintf(debugFP, "   out = '%s'\n", move);
4584     }
4585 }
4586
4587 /* Parser for moves from gnuchess, ICS, or user typein box */
4588 Boolean
4589 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4590      char *move;
4591      int moveNum;
4592      ChessMove *moveType;
4593      int *fromX, *fromY, *toX, *toY;
4594      char *promoChar;
4595 {       
4596     if (appData.debugMode) {
4597         fprintf(debugFP, "move to parse: %s\n", move);
4598     }
4599     *moveType = yylexstr(moveNum, move);
4600
4601     switch (*moveType) {
4602       case WhitePromotionChancellor:
4603       case BlackPromotionChancellor:
4604       case WhitePromotionArchbishop:
4605       case BlackPromotionArchbishop:
4606       case WhitePromotionQueen:
4607       case BlackPromotionQueen:
4608       case WhitePromotionRook:
4609       case BlackPromotionRook:
4610       case WhitePromotionBishop:
4611       case BlackPromotionBishop:
4612       case WhitePromotionKnight:
4613       case BlackPromotionKnight:
4614       case WhitePromotionKing:
4615       case BlackPromotionKing:
4616       case NormalMove:
4617       case WhiteCapturesEnPassant:
4618       case BlackCapturesEnPassant:
4619       case WhiteKingSideCastle:
4620       case WhiteQueenSideCastle:
4621       case BlackKingSideCastle:
4622       case BlackQueenSideCastle:
4623       case WhiteKingSideCastleWild:
4624       case WhiteQueenSideCastleWild:
4625       case BlackKingSideCastleWild:
4626       case BlackQueenSideCastleWild:
4627       /* Code added by Tord: */
4628       case WhiteHSideCastleFR:
4629       case WhiteASideCastleFR:
4630       case BlackHSideCastleFR:
4631       case BlackASideCastleFR:
4632       /* End of code added by Tord */
4633       case IllegalMove:         /* bug or odd chess variant */
4634         *fromX = currentMoveString[0] - AAA;
4635         *fromY = currentMoveString[1] - ONE;
4636         *toX = currentMoveString[2] - AAA;
4637         *toY = currentMoveString[3] - ONE;
4638         *promoChar = currentMoveString[4];
4639         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4640             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4641     if (appData.debugMode) {
4642         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4643     }
4644             *fromX = *fromY = *toX = *toY = 0;
4645             return FALSE;
4646         }
4647         if (appData.testLegality) {
4648           return (*moveType != IllegalMove);
4649         } else {
4650           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4651                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4652         }
4653
4654       case WhiteDrop:
4655       case BlackDrop:
4656         *fromX = *moveType == WhiteDrop ?
4657           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4658           (int) CharToPiece(ToLower(currentMoveString[0]));
4659         *fromY = DROP_RANK;
4660         *toX = currentMoveString[2] - AAA;
4661         *toY = currentMoveString[3] - ONE;
4662         *promoChar = NULLCHAR;
4663         return TRUE;
4664
4665       case AmbiguousMove:
4666       case ImpossibleMove:
4667       case (ChessMove) 0:       /* end of file */
4668       case ElapsedTime:
4669       case Comment:
4670       case PGNTag:
4671       case NAG:
4672       case WhiteWins:
4673       case BlackWins:
4674       case GameIsDrawn:
4675       default:
4676     if (appData.debugMode) {
4677         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4678     }
4679         /* bug? */
4680         *fromX = *fromY = *toX = *toY = 0;
4681         *promoChar = NULLCHAR;
4682         return FALSE;
4683     }
4684 }
4685
4686
4687 void
4688 ParsePV(char *pv)
4689 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4690   int fromX, fromY, toX, toY; char promoChar;
4691   ChessMove moveType;
4692   Boolean valid;
4693   int nr = 0;
4694
4695   endPV = forwardMostMove;
4696   do {
4697     while(*pv == ' ') pv++;
4698     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4699     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4700 if(appData.debugMode){
4701 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4702 }
4703     if(!valid && nr == 0 &&
4704        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4705         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4706     }
4707     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4708     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4709     nr++;
4710     if(endPV+1 > framePtr) break; // no space, truncate
4711     if(!valid) break;
4712     endPV++;
4713     CopyBoard(boards[endPV], boards[endPV-1]);
4714     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4715     moveList[endPV-1][0] = fromX + AAA;
4716     moveList[endPV-1][1] = fromY + ONE;
4717     moveList[endPV-1][2] = toX + AAA;
4718     moveList[endPV-1][3] = toY + ONE;
4719     parseList[endPV-1][0] = NULLCHAR;
4720   } while(valid);
4721   currentMove = endPV;
4722   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4723   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4724                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4725   DrawPosition(TRUE, boards[currentMove]);
4726 }
4727
4728 static int lastX, lastY;
4729
4730 Boolean
4731 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4732 {
4733         int startPV;
4734
4735         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4736         lastX = x; lastY = y;
4737         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4738         startPV = index;
4739       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4740       index = startPV;
4741         while(buf[index] && buf[index] != '\n') index++;
4742         buf[index] = 0;
4743         ParsePV(buf+startPV);
4744         *start = startPV; *end = index-1;
4745         return TRUE;
4746 }
4747
4748 Boolean
4749 LoadPV(int x, int y)
4750 { // called on right mouse click to load PV
4751   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4752   lastX = x; lastY = y;
4753   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4754   return TRUE;
4755 }
4756
4757 void
4758 UnLoadPV()
4759 {
4760   if(endPV < 0) return;
4761   endPV = -1;
4762   currentMove = forwardMostMove;
4763   ClearPremoveHighlights();
4764   DrawPosition(TRUE, boards[currentMove]);
4765 }
4766
4767 void
4768 MovePV(int x, int y, int h)
4769 { // step through PV based on mouse coordinates (called on mouse move)
4770   int margin = h>>3, step = 0;
4771
4772   if(endPV < 0) return;
4773   // we must somehow check if right button is still down (might be released off board!)
4774   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4775   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4776   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4777   if(!step) return;
4778   lastX = x; lastY = y;
4779   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4780   currentMove += step;
4781   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4782   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4783                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4784   DrawPosition(FALSE, boards[currentMove]);
4785 }
4786
4787
4788 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4789 // All positions will have equal probability, but the current method will not provide a unique
4790 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4791 #define DARK 1
4792 #define LITE 2
4793 #define ANY 3
4794
4795 int squaresLeft[4];
4796 int piecesLeft[(int)BlackPawn];
4797 int seed, nrOfShuffles;
4798
4799 void GetPositionNumber()
4800 {       // sets global variable seed
4801         int i;
4802
4803         seed = appData.defaultFrcPosition;
4804         if(seed < 0) { // randomize based on time for negative FRC position numbers
4805                 for(i=0; i<50; i++) seed += random();
4806                 seed = random() ^ random() >> 8 ^ random() << 8;
4807                 if(seed<0) seed = -seed;
4808         }
4809 }
4810
4811 int put(Board board, int pieceType, int rank, int n, int shade)
4812 // put the piece on the (n-1)-th empty squares of the given shade
4813 {
4814         int i;
4815
4816         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4817                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4818                         board[rank][i] = (ChessSquare) pieceType;
4819                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4820                         squaresLeft[ANY]--;
4821                         piecesLeft[pieceType]--; 
4822                         return i;
4823                 }
4824         }
4825         return -1;
4826 }
4827
4828
4829 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4830 // calculate where the next piece goes, (any empty square), and put it there
4831 {
4832         int i;
4833
4834         i = seed % squaresLeft[shade];
4835         nrOfShuffles *= squaresLeft[shade];
4836         seed /= squaresLeft[shade];
4837         put(board, pieceType, rank, i, shade);
4838 }
4839
4840 void AddTwoPieces(Board board, int pieceType, int rank)
4841 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4842 {
4843         int i, n=squaresLeft[ANY], j=n-1, k;
4844
4845         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4846         i = seed % k;  // pick one
4847         nrOfShuffles *= k;
4848         seed /= k;
4849         while(i >= j) i -= j--;
4850         j = n - 1 - j; i += j;
4851         put(board, pieceType, rank, j, ANY);
4852         put(board, pieceType, rank, i, ANY);
4853 }
4854
4855 void SetUpShuffle(Board board, int number)
4856 {
4857         int i, p, first=1;
4858
4859         GetPositionNumber(); nrOfShuffles = 1;
4860
4861         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4862         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4863         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4864
4865         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4866
4867         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4868             p = (int) board[0][i];
4869             if(p < (int) BlackPawn) piecesLeft[p] ++;
4870             board[0][i] = EmptySquare;
4871         }
4872
4873         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4874             // shuffles restricted to allow normal castling put KRR first
4875             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4876                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4877             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4878                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4879             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4880                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4881             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4882                 put(board, WhiteRook, 0, 0, ANY);
4883             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4884         }
4885
4886         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4887             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4888             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4889                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4890                 while(piecesLeft[p] >= 2) {
4891                     AddOnePiece(board, p, 0, LITE);
4892                     AddOnePiece(board, p, 0, DARK);
4893                 }
4894                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4895             }
4896
4897         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4898             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4899             // but we leave King and Rooks for last, to possibly obey FRC restriction
4900             if(p == (int)WhiteRook) continue;
4901             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4902             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4903         }
4904
4905         // now everything is placed, except perhaps King (Unicorn) and Rooks
4906
4907         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4908             // Last King gets castling rights
4909             while(piecesLeft[(int)WhiteUnicorn]) {
4910                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4911                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4912             }
4913
4914             while(piecesLeft[(int)WhiteKing]) {
4915                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4916                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4917             }
4918
4919
4920         } else {
4921             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4922             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4923         }
4924
4925         // Only Rooks can be left; simply place them all
4926         while(piecesLeft[(int)WhiteRook]) {
4927                 i = put(board, WhiteRook, 0, 0, ANY);
4928                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4929                         if(first) {
4930                                 first=0;
4931                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4932                         }
4933                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4934                 }
4935         }
4936         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4937             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4938         }
4939
4940         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4941 }
4942
4943 int SetCharTable( char *table, const char * map )
4944 /* [HGM] moved here from winboard.c because of its general usefulness */
4945 /*       Basically a safe strcpy that uses the last character as King */
4946 {
4947     int result = FALSE; int NrPieces;
4948
4949     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4950                     && NrPieces >= 12 && !(NrPieces&1)) {
4951         int i; /* [HGM] Accept even length from 12 to 34 */
4952
4953         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4954         for( i=0; i<NrPieces/2-1; i++ ) {
4955             table[i] = map[i];
4956             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4957         }
4958         table[(int) WhiteKing]  = map[NrPieces/2-1];
4959         table[(int) BlackKing]  = map[NrPieces-1];
4960
4961         result = TRUE;
4962     }
4963
4964     return result;
4965 }
4966
4967 void Prelude(Board board)
4968 {       // [HGM] superchess: random selection of exo-pieces
4969         int i, j, k; ChessSquare p; 
4970         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4971
4972         GetPositionNumber(); // use FRC position number
4973
4974         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4975             SetCharTable(pieceToChar, appData.pieceToCharTable);
4976             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4977                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4978         }
4979
4980         j = seed%4;                 seed /= 4; 
4981         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4982         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4983         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4984         j = seed%3 + (seed%3 >= j); seed /= 3; 
4985         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4986         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4987         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4988         j = seed%3;                 seed /= 3; 
4989         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4990         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4991         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4992         j = seed%2 + (seed%2 >= j); seed /= 2; 
4993         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4994         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4995         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4996         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4997         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4998         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4999         put(board, exoPieces[0],    0, 0, ANY);
5000         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5001 }
5002
5003 void
5004 InitPosition(redraw)
5005      int redraw;
5006 {
5007     ChessSquare (* pieces)[BOARD_FILES];
5008     int i, j, pawnRow, overrule,
5009     oldx = gameInfo.boardWidth,
5010     oldy = gameInfo.boardHeight,
5011     oldh = gameInfo.holdingsWidth,
5012     oldv = gameInfo.variant;
5013
5014     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5015
5016     /* [AS] Initialize pv info list [HGM] and game status */
5017     {
5018         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5019             pvInfoList[i].depth = 0;
5020             boards[i][EP_STATUS] = EP_NONE;
5021             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5022         }
5023
5024         initialRulePlies = 0; /* 50-move counter start */
5025
5026         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5027         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5028     }
5029
5030     
5031     /* [HGM] logic here is completely changed. In stead of full positions */
5032     /* the initialized data only consist of the two backranks. The switch */
5033     /* selects which one we will use, which is than copied to the Board   */
5034     /* initialPosition, which for the rest is initialized by Pawns and    */
5035     /* empty squares. This initial position is then copied to boards[0],  */
5036     /* possibly after shuffling, so that it remains available.            */
5037
5038     gameInfo.holdingsWidth = 0; /* default board sizes */
5039     gameInfo.boardWidth    = 8;
5040     gameInfo.boardHeight   = 8;
5041     gameInfo.holdingsSize  = 0;
5042     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5043     for(i=0; i<BOARD_FILES-2; i++)
5044       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5045     initialPosition[EP_STATUS] = EP_NONE;
5046     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5047
5048     switch (gameInfo.variant) {
5049     case VariantFischeRandom:
5050       shuffleOpenings = TRUE;
5051     default:
5052       pieces = FIDEArray;
5053       break;
5054     case VariantShatranj:
5055       pieces = ShatranjArray;
5056       nrCastlingRights = 0;
5057       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5058       break;
5059     case VariantMakruk:
5060       pieces = makrukArray;
5061       nrCastlingRights = 0;
5062       startedFromSetupPosition = TRUE;
5063       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5064       break;
5065     case VariantTwoKings:
5066       pieces = twoKingsArray;
5067       break;
5068     case VariantCapaRandom:
5069       shuffleOpenings = TRUE;
5070     case VariantCapablanca:
5071       pieces = CapablancaArray;
5072       gameInfo.boardWidth = 10;
5073       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5074       break;
5075     case VariantGothic:
5076       pieces = GothicArray;
5077       gameInfo.boardWidth = 10;
5078       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5079       break;
5080     case VariantJanus:
5081       pieces = JanusArray;
5082       gameInfo.boardWidth = 10;
5083       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5084       nrCastlingRights = 6;
5085         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5086         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5087         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5088         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5089         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5090         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5091       break;
5092     case VariantFalcon:
5093       pieces = FalconArray;
5094       gameInfo.boardWidth = 10;
5095       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5096       break;
5097     case VariantXiangqi:
5098       pieces = XiangqiArray;
5099       gameInfo.boardWidth  = 9;
5100       gameInfo.boardHeight = 10;
5101       nrCastlingRights = 0;
5102       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5103       break;
5104     case VariantShogi:
5105       pieces = ShogiArray;
5106       gameInfo.boardWidth  = 9;
5107       gameInfo.boardHeight = 9;
5108       gameInfo.holdingsSize = 7;
5109       nrCastlingRights = 0;
5110       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5111       break;
5112     case VariantCourier:
5113       pieces = CourierArray;
5114       gameInfo.boardWidth  = 12;
5115       nrCastlingRights = 0;
5116       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5117       break;
5118     case VariantKnightmate:
5119       pieces = KnightmateArray;
5120       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5121       break;
5122     case VariantFairy:
5123       pieces = fairyArray;
5124       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5125       break;
5126     case VariantGreat:
5127       pieces = GreatArray;
5128       gameInfo.boardWidth = 10;
5129       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5130       gameInfo.holdingsSize = 8;
5131       break;
5132     case VariantSuper:
5133       pieces = FIDEArray;
5134       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5135       gameInfo.holdingsSize = 8;
5136       startedFromSetupPosition = TRUE;
5137       break;
5138     case VariantCrazyhouse:
5139     case VariantBughouse:
5140       pieces = FIDEArray;
5141       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5142       gameInfo.holdingsSize = 5;
5143       break;
5144     case VariantWildCastle:
5145       pieces = FIDEArray;
5146       /* !!?shuffle with kings guaranteed to be on d or e file */
5147       shuffleOpenings = 1;
5148       break;
5149     case VariantNoCastle:
5150       pieces = FIDEArray;
5151       nrCastlingRights = 0;
5152       /* !!?unconstrained back-rank shuffle */
5153       shuffleOpenings = 1;
5154       break;
5155     }
5156
5157     overrule = 0;
5158     if(appData.NrFiles >= 0) {
5159         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5160         gameInfo.boardWidth = appData.NrFiles;
5161     }
5162     if(appData.NrRanks >= 0) {
5163         gameInfo.boardHeight = appData.NrRanks;
5164     }
5165     if(appData.holdingsSize >= 0) {
5166         i = appData.holdingsSize;
5167         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5168         gameInfo.holdingsSize = i;
5169     }
5170     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5171     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5172         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5173
5174     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5175     if(pawnRow < 1) pawnRow = 1;
5176     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5177
5178     /* User pieceToChar list overrules defaults */
5179     if(appData.pieceToCharTable != NULL)
5180         SetCharTable(pieceToChar, appData.pieceToCharTable);
5181
5182     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5183
5184         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5185             s = (ChessSquare) 0; /* account holding counts in guard band */
5186         for( i=0; i<BOARD_HEIGHT; i++ )
5187             initialPosition[i][j] = s;
5188
5189         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5190         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5191         initialPosition[pawnRow][j] = WhitePawn;
5192         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5193         if(gameInfo.variant == VariantXiangqi) {
5194             if(j&1) {
5195                 initialPosition[pawnRow][j] = 
5196                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5197                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5198                    initialPosition[2][j] = WhiteCannon;
5199                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5200                 }
5201             }
5202         }
5203         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5204     }
5205     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5206
5207             j=BOARD_LEFT+1;
5208             initialPosition[1][j] = WhiteBishop;
5209             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5210             j=BOARD_RGHT-2;
5211             initialPosition[1][j] = WhiteRook;
5212             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5213     }
5214
5215     if( nrCastlingRights == -1) {
5216         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5217         /*       This sets default castling rights from none to normal corners   */
5218         /* Variants with other castling rights must set them themselves above    */
5219         nrCastlingRights = 6;
5220        
5221         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5222         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5223         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5224         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5225         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5226         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5227      }
5228
5229      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5230      if(gameInfo.variant == VariantGreat) { // promotion commoners
5231         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5232         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5233         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5234         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5235      }
5236   if (appData.debugMode) {
5237     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5238   }
5239     if(shuffleOpenings) {
5240         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5241         startedFromSetupPosition = TRUE;
5242     }
5243     if(startedFromPositionFile) {
5244       /* [HGM] loadPos: use PositionFile for every new game */
5245       CopyBoard(initialPosition, filePosition);
5246       for(i=0; i<nrCastlingRights; i++)
5247           initialRights[i] = filePosition[CASTLING][i];
5248       startedFromSetupPosition = TRUE;
5249     }
5250
5251     CopyBoard(boards[0], initialPosition);
5252
5253     if(oldx != gameInfo.boardWidth ||
5254        oldy != gameInfo.boardHeight ||
5255        oldh != gameInfo.holdingsWidth
5256 #ifdef GOTHIC
5257        || oldv == VariantGothic ||        // For licensing popups
5258        gameInfo.variant == VariantGothic
5259 #endif
5260 #ifdef FALCON
5261        || oldv == VariantFalcon ||
5262        gameInfo.variant == VariantFalcon
5263 #endif
5264                                          )
5265             InitDrawingSizes(-2 ,0);
5266
5267     if (redraw)
5268       DrawPosition(TRUE, boards[currentMove]);
5269 }
5270
5271 void
5272 SendBoard(cps, moveNum)
5273      ChessProgramState *cps;
5274      int moveNum;
5275 {
5276     char message[MSG_SIZ];
5277     
5278     if (cps->useSetboard) {
5279       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5280       sprintf(message, "setboard %s\n", fen);
5281       SendToProgram(message, cps);
5282       free(fen);
5283
5284     } else {
5285       ChessSquare *bp;
5286       int i, j;
5287       /* Kludge to set black to move, avoiding the troublesome and now
5288        * deprecated "black" command.
5289        */
5290       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5291
5292       SendToProgram("edit\n", cps);
5293       SendToProgram("#\n", cps);
5294       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5295         bp = &boards[moveNum][i][BOARD_LEFT];
5296         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5297           if ((int) *bp < (int) BlackPawn) {
5298             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5299                     AAA + j, ONE + i);
5300             if(message[0] == '+' || message[0] == '~') {
5301                 sprintf(message, "%c%c%c+\n",
5302                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5303                         AAA + j, ONE + i);
5304             }
5305             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5306                 message[1] = BOARD_RGHT   - 1 - j + '1';
5307                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5308             }
5309             SendToProgram(message, cps);
5310           }
5311         }
5312       }
5313     
5314       SendToProgram("c\n", cps);
5315       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5316         bp = &boards[moveNum][i][BOARD_LEFT];
5317         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5318           if (((int) *bp != (int) EmptySquare)
5319               && ((int) *bp >= (int) BlackPawn)) {
5320             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5321                     AAA + j, ONE + i);
5322             if(message[0] == '+' || message[0] == '~') {
5323                 sprintf(message, "%c%c%c+\n",
5324                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5325                         AAA + j, ONE + i);
5326             }
5327             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5328                 message[1] = BOARD_RGHT   - 1 - j + '1';
5329                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5330             }
5331             SendToProgram(message, cps);
5332           }
5333         }
5334       }
5335     
5336       SendToProgram(".\n", cps);
5337     }
5338     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5339 }
5340
5341 int
5342 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5343 {
5344     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5345     /* [HGM] add Shogi promotions */
5346     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5347     ChessSquare piece;
5348     ChessMove moveType;
5349     Boolean premove;
5350
5351     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5352     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5353
5354     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5355       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5356         return FALSE;
5357
5358     piece = boards[currentMove][fromY][fromX];
5359     if(gameInfo.variant == VariantShogi) {
5360         promotionZoneSize = 3;
5361         highestPromotingPiece = (int)WhiteFerz;
5362     } else if(gameInfo.variant == VariantMakruk) {
5363         promotionZoneSize = 3;
5364     }
5365
5366     // next weed out all moves that do not touch the promotion zone at all
5367     if((int)piece >= BlackPawn) {
5368         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5369              return FALSE;
5370         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5371     } else {
5372         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5373            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5374     }
5375
5376     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5377
5378     // weed out mandatory Shogi promotions
5379     if(gameInfo.variant == VariantShogi) {
5380         if(piece >= BlackPawn) {
5381             if(toY == 0 && piece == BlackPawn ||
5382                toY == 0 && piece == BlackQueen ||
5383                toY <= 1 && piece == BlackKnight) {
5384                 *promoChoice = '+';
5385                 return FALSE;
5386             }
5387         } else {
5388             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5389                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5390                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5391                 *promoChoice = '+';
5392                 return FALSE;
5393             }
5394         }
5395     }
5396
5397     // weed out obviously illegal Pawn moves
5398     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5399         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5400         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5401         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5402         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5403         // note we are not allowed to test for valid (non-)capture, due to premove
5404     }
5405
5406     // we either have a choice what to promote to, or (in Shogi) whether to promote
5407     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5408         *promoChoice = PieceToChar(BlackFerz);  // no choice
5409         return FALSE;
5410     }
5411     if(appData.alwaysPromoteToQueen) { // predetermined
5412         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5413              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5414         else *promoChoice = PieceToChar(BlackQueen);
5415         return FALSE;
5416     }
5417
5418     // suppress promotion popup on illegal moves that are not premoves
5419     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5420               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5421     if(appData.testLegality && !premove) {
5422         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5423                         fromY, fromX, toY, toX, NULLCHAR);
5424         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5425            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5426             return FALSE;
5427     }
5428
5429     return TRUE;
5430 }
5431
5432 int
5433 InPalace(row, column)
5434      int row, column;
5435 {   /* [HGM] for Xiangqi */
5436     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5437          column < (BOARD_WIDTH + 4)/2 &&
5438          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5439     return FALSE;
5440 }
5441
5442 int
5443 PieceForSquare (x, y)
5444      int x;
5445      int y;
5446 {
5447   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5448      return -1;
5449   else
5450      return boards[currentMove][y][x];
5451 }
5452
5453 int
5454 OKToStartUserMove(x, y)
5455      int x, y;
5456 {
5457     ChessSquare from_piece;
5458     int white_piece;
5459
5460     if (matchMode) return FALSE;
5461     if (gameMode == EditPosition) return TRUE;
5462
5463     if (x >= 0 && y >= 0)
5464       from_piece = boards[currentMove][y][x];
5465     else
5466       from_piece = EmptySquare;
5467
5468     if (from_piece == EmptySquare) return FALSE;
5469
5470     white_piece = (int)from_piece >= (int)WhitePawn &&
5471       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5472
5473     switch (gameMode) {
5474       case PlayFromGameFile:
5475       case AnalyzeFile:
5476       case TwoMachinesPlay:
5477       case EndOfGame:
5478         return FALSE;
5479
5480       case IcsObserving:
5481       case IcsIdle:
5482         return FALSE;
5483
5484       case MachinePlaysWhite:
5485       case IcsPlayingBlack:
5486         if (appData.zippyPlay) return FALSE;
5487         if (white_piece) {
5488             DisplayMoveError(_("You are playing Black"));
5489             return FALSE;
5490         }
5491         break;
5492
5493       case MachinePlaysBlack:
5494       case IcsPlayingWhite:
5495         if (appData.zippyPlay) return FALSE;
5496         if (!white_piece) {
5497             DisplayMoveError(_("You are playing White"));
5498             return FALSE;
5499         }
5500         break;
5501
5502       case EditGame:
5503         if (!white_piece && WhiteOnMove(currentMove)) {
5504             DisplayMoveError(_("It is White's turn"));
5505             return FALSE;
5506         }           
5507         if (white_piece && !WhiteOnMove(currentMove)) {
5508             DisplayMoveError(_("It is Black's turn"));
5509             return FALSE;
5510         }           
5511         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5512             /* Editing correspondence game history */
5513             /* Could disallow this or prompt for confirmation */
5514             cmailOldMove = -1;
5515         }
5516         break;
5517
5518       case BeginningOfGame:
5519         if (appData.icsActive) return FALSE;
5520         if (!appData.noChessProgram) {
5521             if (!white_piece) {
5522                 DisplayMoveError(_("You are playing White"));
5523                 return FALSE;
5524             }
5525         }
5526         break;
5527         
5528       case Training:
5529         if (!white_piece && WhiteOnMove(currentMove)) {
5530             DisplayMoveError(_("It is White's turn"));
5531             return FALSE;
5532         }           
5533         if (white_piece && !WhiteOnMove(currentMove)) {
5534             DisplayMoveError(_("It is Black's turn"));
5535             return FALSE;
5536         }           
5537         break;
5538
5539       default:
5540       case IcsExamining:
5541         break;
5542     }
5543     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5544         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5545         && gameMode != AnalyzeFile && gameMode != Training) {
5546         DisplayMoveError(_("Displayed position is not current"));
5547         return FALSE;
5548     }
5549     return TRUE;
5550 }
5551
5552 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5553 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5554 int lastLoadGameUseList = FALSE;
5555 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5556 ChessMove lastLoadGameStart = (ChessMove) 0;
5557
5558 ChessMove
5559 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5560      int fromX, fromY, toX, toY;
5561      int promoChar;
5562      Boolean captureOwn;
5563 {
5564     ChessMove moveType;
5565     ChessSquare pdown, pup;
5566
5567     /* Check if the user is playing in turn.  This is complicated because we
5568        let the user "pick up" a piece before it is his turn.  So the piece he
5569        tried to pick up may have been captured by the time he puts it down!
5570        Therefore we use the color the user is supposed to be playing in this
5571        test, not the color of the piece that is currently on the starting
5572        square---except in EditGame mode, where the user is playing both
5573        sides; fortunately there the capture race can't happen.  (It can
5574        now happen in IcsExamining mode, but that's just too bad.  The user
5575        will get a somewhat confusing message in that case.)
5576        */
5577
5578     switch (gameMode) {
5579       case PlayFromGameFile:
5580       case AnalyzeFile:
5581       case TwoMachinesPlay:
5582       case EndOfGame:
5583       case IcsObserving:
5584       case IcsIdle:
5585         /* We switched into a game mode where moves are not accepted,
5586            perhaps while the mouse button was down. */
5587         return ImpossibleMove;
5588
5589       case MachinePlaysWhite:
5590         /* User is moving for Black */
5591         if (WhiteOnMove(currentMove)) {
5592             DisplayMoveError(_("It is White's turn"));
5593             return ImpossibleMove;
5594         }
5595         break;
5596
5597       case MachinePlaysBlack:
5598         /* User is moving for White */
5599         if (!WhiteOnMove(currentMove)) {
5600             DisplayMoveError(_("It is Black's turn"));
5601             return ImpossibleMove;
5602         }
5603         break;
5604
5605       case EditGame:
5606       case IcsExamining:
5607       case BeginningOfGame:
5608       case AnalyzeMode:
5609       case Training:
5610         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5611             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5612             /* User is moving for Black */
5613             if (WhiteOnMove(currentMove)) {
5614                 DisplayMoveError(_("It is White's turn"));
5615                 return ImpossibleMove;
5616             }
5617         } else {
5618             /* User is moving for White */
5619             if (!WhiteOnMove(currentMove)) {
5620                 DisplayMoveError(_("It is Black's turn"));
5621                 return ImpossibleMove;
5622             }
5623         }
5624         break;
5625
5626       case IcsPlayingBlack:
5627         /* User is moving for Black */
5628         if (WhiteOnMove(currentMove)) {
5629             if (!appData.premove) {
5630                 DisplayMoveError(_("It is White's turn"));
5631             } else if (toX >= 0 && toY >= 0) {
5632                 premoveToX = toX;
5633                 premoveToY = toY;
5634                 premoveFromX = fromX;
5635                 premoveFromY = fromY;
5636                 premovePromoChar = promoChar;
5637                 gotPremove = 1;
5638                 if (appData.debugMode) 
5639                     fprintf(debugFP, "Got premove: fromX %d,"
5640                             "fromY %d, toX %d, toY %d\n",
5641                             fromX, fromY, toX, toY);
5642             }
5643             return ImpossibleMove;
5644         }
5645         break;
5646
5647       case IcsPlayingWhite:
5648         /* User is moving for White */
5649         if (!WhiteOnMove(currentMove)) {
5650             if (!appData.premove) {
5651                 DisplayMoveError(_("It is Black's turn"));
5652             } else if (toX >= 0 && toY >= 0) {
5653                 premoveToX = toX;
5654                 premoveToY = toY;
5655                 premoveFromX = fromX;
5656                 premoveFromY = fromY;
5657                 premovePromoChar = promoChar;
5658                 gotPremove = 1;
5659                 if (appData.debugMode) 
5660                     fprintf(debugFP, "Got premove: fromX %d,"
5661                             "fromY %d, toX %d, toY %d\n",
5662                             fromX, fromY, toX, toY);
5663             }
5664             return ImpossibleMove;
5665         }
5666         break;
5667
5668       default:
5669         break;
5670
5671       case EditPosition:
5672         /* EditPosition, empty square, or different color piece;
5673            click-click move is possible */
5674         if (toX == -2 || toY == -2) {
5675             boards[0][fromY][fromX] = EmptySquare;
5676             return AmbiguousMove;
5677         } else if (toX >= 0 && toY >= 0) {
5678             boards[0][toY][toX] = boards[0][fromY][fromX];
5679             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5680                 if(boards[0][fromY][0] != EmptySquare) {
5681                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5682                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5683                 }
5684             } else
5685             if(fromX == BOARD_RGHT+1) {
5686                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5687                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5688                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5689                 }
5690             } else
5691             boards[0][fromY][fromX] = EmptySquare;
5692             return AmbiguousMove;
5693         }
5694         return ImpossibleMove;
5695     }
5696
5697     if(toX < 0 || toY < 0) return ImpossibleMove;
5698     pdown = boards[currentMove][fromY][fromX];
5699     pup = boards[currentMove][toY][toX];
5700
5701     /* [HGM] If move started in holdings, it means a drop */
5702     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5703          if( pup != EmptySquare ) return ImpossibleMove;
5704          if(appData.testLegality) {
5705              /* it would be more logical if LegalityTest() also figured out
5706               * which drops are legal. For now we forbid pawns on back rank.
5707               * Shogi is on its own here...
5708               */
5709              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5710                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5711                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5712          }
5713          return WhiteDrop; /* Not needed to specify white or black yet */
5714     }
5715
5716     /* [HGM] always test for legality, to get promotion info */
5717     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5718                                          fromY, fromX, toY, toX, promoChar);
5719     /* [HGM] but possibly ignore an IllegalMove result */
5720     if (appData.testLegality) {
5721         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5722             DisplayMoveError(_("Illegal move"));
5723             return ImpossibleMove;
5724         }
5725     }
5726
5727     return moveType;
5728     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5729        function is made into one that returns an OK move type if FinishMove
5730        should be called. This to give the calling driver routine the
5731        opportunity to finish the userMove input with a promotion popup,
5732        without bothering the user with this for invalid or illegal moves */
5733
5734 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5735 }
5736
5737 /* Common tail of UserMoveEvent and DropMenuEvent */
5738 int
5739 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5740      ChessMove moveType;
5741      int fromX, fromY, toX, toY;
5742      /*char*/int promoChar;
5743 {
5744     char *bookHit = 0;
5745
5746     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5747         // [HGM] superchess: suppress promotions to non-available piece
5748         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5749         if(WhiteOnMove(currentMove)) {
5750             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5751         } else {
5752             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5753         }
5754     }
5755
5756     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5757        move type in caller when we know the move is a legal promotion */
5758     if(moveType == NormalMove && promoChar)
5759         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5760
5761     /* [HGM] convert drag-and-drop piece drops to standard form */
5762     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5763          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5764            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5765                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5766            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5767            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5768            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5769            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5770          fromY = DROP_RANK;
5771     }
5772
5773     /* [HGM] <popupFix> The following if has been moved here from
5774        UserMoveEvent(). Because it seemed to belong here (why not allow
5775        piece drops in training games?), and because it can only be
5776        performed after it is known to what we promote. */
5777     if (gameMode == Training) {
5778       /* compare the move played on the board to the next move in the
5779        * game. If they match, display the move and the opponent's response. 
5780        * If they don't match, display an error message.
5781        */
5782       int saveAnimate;
5783       Board testBoard;
5784       CopyBoard(testBoard, boards[currentMove]);
5785       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5786
5787       if (CompareBoards(testBoard, boards[currentMove+1])) {
5788         ForwardInner(currentMove+1);
5789
5790         /* Autoplay the opponent's response.
5791          * if appData.animate was TRUE when Training mode was entered,
5792          * the response will be animated.
5793          */
5794         saveAnimate = appData.animate;
5795         appData.animate = animateTraining;
5796         ForwardInner(currentMove+1);
5797         appData.animate = saveAnimate;
5798
5799         /* check for the end of the game */
5800         if (currentMove >= forwardMostMove) {
5801           gameMode = PlayFromGameFile;
5802           ModeHighlight();
5803           SetTrainingModeOff();
5804           DisplayInformation(_("End of game"));
5805         }
5806       } else {
5807         DisplayError(_("Incorrect move"), 0);
5808       }
5809       return 1;
5810     }
5811
5812   /* Ok, now we know that the move is good, so we can kill
5813      the previous line in Analysis Mode */
5814   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5815                                 && currentMove < forwardMostMove) {
5816     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5817   }
5818
5819   /* If we need the chess program but it's dead, restart it */
5820   ResurrectChessProgram();
5821
5822   /* A user move restarts a paused game*/
5823   if (pausing)
5824     PauseEvent();
5825
5826   thinkOutput[0] = NULLCHAR;
5827
5828   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5829
5830   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5831
5832   if (gameMode == BeginningOfGame) {
5833     if (appData.noChessProgram) {
5834       gameMode = EditGame;
5835       SetGameInfo();
5836     } else {
5837       char buf[MSG_SIZ];
5838       gameMode = MachinePlaysBlack;
5839       StartClocks();
5840       SetGameInfo();
5841       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5842       DisplayTitle(buf);
5843       if (first.sendName) {
5844         sprintf(buf, "name %s\n", gameInfo.white);
5845         SendToProgram(buf, &first);
5846       }
5847       StartClocks();
5848     }
5849     ModeHighlight();
5850   }
5851
5852   /* Relay move to ICS or chess engine */
5853   if (appData.icsActive) {
5854     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5855         gameMode == IcsExamining) {
5856       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5857         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5858         SendToICS("draw ");
5859         SendMoveToICS(moveType, fromX, fromY, toX, toY);
5860       }
5861       // also send plain move, in case ICS does not understand atomic claims
5862       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5863       ics_user_moved = 1;
5864     }
5865   } else {
5866     if (first.sendTime && (gameMode == BeginningOfGame ||
5867                            gameMode == MachinePlaysWhite ||
5868                            gameMode == MachinePlaysBlack)) {
5869       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5870     }
5871     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5872          // [HGM] book: if program might be playing, let it use book
5873         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5874         first.maybeThinking = TRUE;
5875     } else SendMoveToProgram(forwardMostMove-1, &first);
5876     if (currentMove == cmailOldMove + 1) {
5877       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5878     }
5879   }
5880
5881   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5882
5883   switch (gameMode) {
5884   case EditGame:
5885     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5886     case MT_NONE:
5887     case MT_CHECK:
5888       break;
5889     case MT_CHECKMATE:
5890     case MT_STAINMATE:
5891       if (WhiteOnMove(currentMove)) {
5892         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5893       } else {
5894         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5895       }
5896       break;
5897     case MT_STALEMATE:
5898       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5899       break;
5900     }
5901     break;
5902     
5903   case MachinePlaysBlack:
5904   case MachinePlaysWhite:
5905     /* disable certain menu options while machine is thinking */
5906     SetMachineThinkingEnables();
5907     break;
5908
5909   default:
5910     break;
5911   }
5912
5913   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5914         
5915   if(bookHit) { // [HGM] book: simulate book reply
5916         static char bookMove[MSG_SIZ]; // a bit generous?
5917
5918         programStats.nodes = programStats.depth = programStats.time = 
5919         programStats.score = programStats.got_only_move = 0;
5920         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5921
5922         strcpy(bookMove, "move ");
5923         strcat(bookMove, bookHit);
5924         HandleMachineMove(bookMove, &first);
5925   }
5926   return 1;
5927 }
5928
5929 void
5930 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5931      int fromX, fromY, toX, toY;
5932      int promoChar;
5933 {
5934     /* [HGM] This routine was added to allow calling of its two logical
5935        parts from other modules in the old way. Before, UserMoveEvent()
5936        automatically called FinishMove() if the move was OK, and returned
5937        otherwise. I separated the two, in order to make it possible to
5938        slip a promotion popup in between. But that it always needs two
5939        calls, to the first part, (now called UserMoveTest() ), and to
5940        FinishMove if the first part succeeded. Calls that do not need
5941        to do anything in between, can call this routine the old way. 
5942     */
5943     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5944 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5945     if(moveType == AmbiguousMove)
5946         DrawPosition(FALSE, boards[currentMove]);
5947     else if(moveType != ImpossibleMove && moveType != Comment)
5948         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5949 }
5950
5951 void
5952 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5953      Board board;
5954      int flags;
5955      ChessMove kind;
5956      int rf, ff, rt, ft;
5957      VOIDSTAR closure;
5958 {
5959     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5960     Markers *m = (Markers *) closure;
5961     if(rf == fromY && ff == fromX)
5962         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5963                          || kind == WhiteCapturesEnPassant
5964                          || kind == BlackCapturesEnPassant);
5965     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5966 }
5967
5968 void
5969 MarkTargetSquares(int clear)
5970 {
5971   int x, y;
5972   if(!appData.markers || !appData.highlightDragging || 
5973      !appData.testLegality || gameMode == EditPosition) return;
5974   if(clear) {
5975     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5976   } else {
5977     int capt = 0;
5978     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5979     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5980       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5981       if(capt)
5982       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5983     }
5984   }
5985   DrawPosition(TRUE, NULL);
5986 }
5987
5988 void LeftClick(ClickType clickType, int xPix, int yPix)
5989 {
5990     int x, y;
5991     Boolean saveAnimate;
5992     static int second = 0, promotionChoice = 0;
5993     char promoChoice = NULLCHAR;
5994
5995     if(appData.seekGraph && appData.icsActive && loggedOn &&
5996         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
5997         SeekGraphClick(clickType, xPix, yPix, 0);
5998         return;
5999     }
6000
6001     if (clickType == Press) ErrorPopDown();
6002     MarkTargetSquares(1);
6003
6004     x = EventToSquare(xPix, BOARD_WIDTH);
6005     y = EventToSquare(yPix, BOARD_HEIGHT);
6006     if (!flipView && y >= 0) {
6007         y = BOARD_HEIGHT - 1 - y;
6008     }
6009     if (flipView && x >= 0) {
6010         x = BOARD_WIDTH - 1 - x;
6011     }
6012
6013     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6014         if(clickType == Release) return; // ignore upclick of click-click destination
6015         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6016         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6017         if(gameInfo.holdingsWidth && 
6018                 (WhiteOnMove(currentMove) 
6019                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6020                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6021             // click in right holdings, for determining promotion piece
6022             ChessSquare p = boards[currentMove][y][x];
6023             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6024             if(p != EmptySquare) {
6025                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6026                 fromX = fromY = -1;
6027                 return;
6028             }
6029         }
6030         DrawPosition(FALSE, boards[currentMove]);
6031         return;
6032     }
6033
6034     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6035     if(clickType == Press
6036             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6037               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6038               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6039         return;
6040
6041     if (fromX == -1) {
6042         if (clickType == Press) {
6043             /* First square */
6044             if (OKToStartUserMove(x, y)) {
6045                 fromX = x;
6046                 fromY = y;
6047                 second = 0;
6048                 MarkTargetSquares(0);
6049                 DragPieceBegin(xPix, yPix);
6050                 if (appData.highlightDragging) {
6051                     SetHighlights(x, y, -1, -1);
6052                 }
6053             }
6054         }
6055         return;
6056     }
6057
6058     /* fromX != -1 */
6059     if (clickType == Press && gameMode != EditPosition) {
6060         ChessSquare fromP;
6061         ChessSquare toP;
6062         int frc;
6063
6064         // ignore off-board to clicks
6065         if(y < 0 || x < 0) return;
6066
6067         /* Check if clicking again on the same color piece */
6068         fromP = boards[currentMove][fromY][fromX];
6069         toP = boards[currentMove][y][x];
6070         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6071         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6072              WhitePawn <= toP && toP <= WhiteKing &&
6073              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6074              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6075             (BlackPawn <= fromP && fromP <= BlackKing && 
6076              BlackPawn <= toP && toP <= BlackKing &&
6077              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6078              !(fromP == BlackKing && toP == BlackRook && frc))) {
6079             /* Clicked again on same color piece -- changed his mind */
6080             second = (x == fromX && y == fromY);
6081             if (appData.highlightDragging) {
6082                 SetHighlights(x, y, -1, -1);
6083             } else {
6084                 ClearHighlights();
6085             }
6086             if (OKToStartUserMove(x, y)) {
6087                 fromX = x;
6088                 fromY = y;
6089                 MarkTargetSquares(0);
6090                 DragPieceBegin(xPix, yPix);
6091             }
6092             return;
6093         }
6094         // ignore clicks on holdings
6095         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6096     }
6097
6098     if (clickType == Release && x == fromX && y == fromY) {
6099         DragPieceEnd(xPix, yPix);
6100         if (appData.animateDragging) {
6101             /* Undo animation damage if any */
6102             DrawPosition(FALSE, NULL);
6103         }
6104         if (second) {
6105             /* Second up/down in same square; just abort move */
6106             second = 0;
6107             fromX = fromY = -1;
6108             ClearHighlights();
6109             gotPremove = 0;
6110             ClearPremoveHighlights();
6111         } else {
6112             /* First upclick in same square; start click-click mode */
6113             SetHighlights(x, y, -1, -1);
6114         }
6115         return;
6116     }
6117
6118     /* we now have a different from- and (possibly off-board) to-square */
6119     /* Completed move */
6120     toX = x;
6121     toY = y;
6122     saveAnimate = appData.animate;
6123     if (clickType == Press) {
6124         /* Finish clickclick move */
6125         if (appData.animate || appData.highlightLastMove) {
6126             SetHighlights(fromX, fromY, toX, toY);
6127         } else {
6128             ClearHighlights();
6129         }
6130     } else {
6131         /* Finish drag move */
6132         if (appData.highlightLastMove) {
6133             SetHighlights(fromX, fromY, toX, toY);
6134         } else {
6135             ClearHighlights();
6136         }
6137         DragPieceEnd(xPix, yPix);
6138         /* Don't animate move and drag both */
6139         appData.animate = FALSE;
6140     }
6141
6142     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6143     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6144         ChessSquare piece = boards[currentMove][fromY][fromX];
6145         if(gameMode == EditPosition && piece != EmptySquare &&
6146            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6147             int n;
6148              
6149             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6150                 n = PieceToNumber(piece - (int)BlackPawn);
6151                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6152                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6153                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6154             } else
6155             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6156                 n = PieceToNumber(piece);
6157                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6158                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6159                 boards[currentMove][n][BOARD_WIDTH-2]++;
6160             }
6161             boards[currentMove][fromY][fromX] = EmptySquare;
6162         }
6163         ClearHighlights();
6164         fromX = fromY = -1;
6165         DrawPosition(TRUE, boards[currentMove]);
6166         return;
6167     }
6168
6169     // off-board moves should not be highlighted
6170     if(x < 0 || x < 0) ClearHighlights();
6171
6172     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6173         SetHighlights(fromX, fromY, toX, toY);
6174         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6175             // [HGM] super: promotion to captured piece selected from holdings
6176             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6177             promotionChoice = TRUE;
6178             // kludge follows to temporarily execute move on display, without promoting yet
6179             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6180             boards[currentMove][toY][toX] = p;
6181             DrawPosition(FALSE, boards[currentMove]);
6182             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6183             boards[currentMove][toY][toX] = q;
6184             DisplayMessage("Click in holdings to choose piece", "");
6185             return;
6186         }
6187         PromotionPopUp();
6188     } else {
6189         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6190         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6191         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6192         fromX = fromY = -1;
6193     }
6194     appData.animate = saveAnimate;
6195     if (appData.animate || appData.animateDragging) {
6196         /* Undo animation damage if needed */
6197         DrawPosition(FALSE, NULL);
6198     }
6199 }
6200
6201 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6202 {   // front-end-free part taken out of PieceMenuPopup
6203     int whichMenu; int xSqr, ySqr;
6204
6205     if(seekGraphUp) { // [HGM] seekgraph
6206         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6207         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6208         return -2;
6209     }
6210
6211     xSqr = EventToSquare(x, BOARD_WIDTH);
6212     ySqr = EventToSquare(y, BOARD_HEIGHT);
6213     if (action == Release) UnLoadPV(); // [HGM] pv
6214     if (action != Press) return -2; // return code to be ignored
6215     switch (gameMode) {
6216       case IcsExamining:
6217         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6218       case EditPosition:
6219         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6220         if (xSqr < 0 || ySqr < 0) return -1;\r
6221         whichMenu = 0; // edit-position menu
6222         break;
6223       case IcsObserving:
6224         if(!appData.icsEngineAnalyze) return -1;
6225       case IcsPlayingWhite:
6226       case IcsPlayingBlack:
6227         if(!appData.zippyPlay) goto noZip;
6228       case AnalyzeMode:
6229       case AnalyzeFile:
6230       case MachinePlaysWhite:
6231       case MachinePlaysBlack:
6232       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6233         if (!appData.dropMenu) {
6234           LoadPV(x, y);
6235           return 2; // flag front-end to grab mouse events
6236         }
6237         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6238            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6239       case EditGame:
6240       noZip:
6241         if (xSqr < 0 || ySqr < 0) return -1;
6242         if (!appData.dropMenu || appData.testLegality &&
6243             gameInfo.variant != VariantBughouse &&
6244             gameInfo.variant != VariantCrazyhouse) return -1;
6245         whichMenu = 1; // drop menu
6246         break;
6247       default:
6248         return -1;
6249     }
6250
6251     if (((*fromX = xSqr) < 0) ||
6252         ((*fromY = ySqr) < 0)) {
6253         *fromX = *fromY = -1;
6254         return -1;
6255     }
6256     if (flipView)
6257       *fromX = BOARD_WIDTH - 1 - *fromX;
6258     else
6259       *fromY = BOARD_HEIGHT - 1 - *fromY;
6260
6261     return whichMenu;
6262 }
6263
6264 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6265 {
6266 //    char * hint = lastHint;
6267     FrontEndProgramStats stats;
6268
6269     stats.which = cps == &first ? 0 : 1;
6270     stats.depth = cpstats->depth;
6271     stats.nodes = cpstats->nodes;
6272     stats.score = cpstats->score;
6273     stats.time = cpstats->time;
6274     stats.pv = cpstats->movelist;
6275     stats.hint = lastHint;
6276     stats.an_move_index = 0;
6277     stats.an_move_count = 0;
6278
6279     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6280         stats.hint = cpstats->move_name;
6281         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6282         stats.an_move_count = cpstats->nr_moves;
6283     }
6284
6285     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6286
6287     SetProgramStats( &stats );
6288 }
6289
6290 int
6291 Adjudicate(ChessProgramState *cps)
6292 {       // [HGM] some adjudications useful with buggy engines
6293         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6294         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6295         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6296         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6297         int k, count = 0; static int bare = 1;
6298         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6299         Boolean canAdjudicate = !appData.icsActive;
6300
6301         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6302         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6303             if( appData.testLegality )
6304             {   /* [HGM] Some more adjudications for obstinate engines */
6305                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6306                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6307                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6308                 static int moveCount = 6;
6309                 ChessMove result;
6310                 char *reason = NULL;
6311
6312                 /* Count what is on board. */
6313                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6314                 {   ChessSquare p = boards[forwardMostMove][i][j];
6315                     int m=i;
6316
6317                     switch((int) p)
6318                     {   /* count B,N,R and other of each side */
6319                         case WhiteKing:
6320                         case BlackKing:
6321                              NrK++; break; // [HGM] atomic: count Kings
6322                         case WhiteKnight:
6323                              NrWN++; break;
6324                         case WhiteBishop:
6325                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6326                              bishopsColor |= 1 << ((i^j)&1);
6327                              NrWB++; break;
6328                         case BlackKnight:
6329                              NrBN++; break;
6330                         case BlackBishop:
6331                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6332                              bishopsColor |= 1 << ((i^j)&1);
6333                              NrBB++; break;
6334                         case WhiteRook:
6335                              NrWR++; break;
6336                         case BlackRook:
6337                              NrBR++; break;
6338                         case WhiteQueen:
6339                              NrWQ++; break;
6340                         case BlackQueen:
6341                              NrBQ++; break;
6342                         case EmptySquare: 
6343                              break;
6344                         case BlackPawn:
6345                              m = 7-i;
6346                         case WhitePawn:
6347                              PawnAdvance += m; NrPawns++;
6348                     }
6349                     NrPieces += (p != EmptySquare);
6350                     NrW += ((int)p < (int)BlackPawn);
6351                     if(gameInfo.variant == VariantXiangqi && 
6352                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6353                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6354                         NrW -= ((int)p < (int)BlackPawn);
6355                     }
6356                 }
6357
6358                 /* Some material-based adjudications that have to be made before stalemate test */
6359                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6360                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6361                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6362                      if(canAdjudicate && appData.checkMates) {
6363                          if(engineOpponent)
6364                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6365                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6366                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6367                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6368                          return 1;
6369                      }
6370                 }
6371
6372                 /* Bare King in Shatranj (loses) or Losers (wins) */
6373                 if( NrW == 1 || NrPieces - NrW == 1) {
6374                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6375                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6376                      if(canAdjudicate && appData.checkMates) {
6377                          if(engineOpponent)
6378                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6379                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6380                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6381                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6382                          return 1;
6383                      }
6384                   } else
6385                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6386                   {    /* bare King */
6387                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6388                         if(canAdjudicate && appData.checkMates) {
6389                             /* but only adjudicate if adjudication enabled */
6390                             if(engineOpponent)
6391                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6392                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6393                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6394                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6395                             return 1;
6396                         }
6397                   }
6398                 } else bare = 1;
6399
6400
6401             // don't wait for engine to announce game end if we can judge ourselves
6402             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6403               case MT_CHECK:
6404                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6405                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6406                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6407                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6408                             checkCnt++;
6409                         if(checkCnt >= 2) {
6410                             reason = "Xboard adjudication: 3rd check";
6411                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6412                             break;
6413                         }
6414                     }
6415                 }
6416               case MT_NONE:
6417               default:
6418                 break;
6419               case MT_STALEMATE:
6420               case MT_STAINMATE:
6421                 reason = "Xboard adjudication: Stalemate";
6422                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6423                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6424                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6425                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6426                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6427                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6428                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6429                                                                         EP_CHECKMATE : EP_WINS);
6430                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6431                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6432                 }
6433                 break;
6434               case MT_CHECKMATE:
6435                 reason = "Xboard adjudication: Checkmate";
6436                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6437                 break;
6438             }
6439
6440                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6441                     case EP_STALEMATE:
6442                         result = GameIsDrawn; break;
6443                     case EP_CHECKMATE:
6444                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6445                     case EP_WINS:
6446                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6447                     default:
6448                         result = (ChessMove) 0;
6449                 }
6450                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6451                     if(engineOpponent)
6452                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6453                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6454                     GameEnds( result, reason, GE_XBOARD );
6455                     return 1;
6456                 }
6457
6458                 /* Next absolutely insufficient mating material. */
6459                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6460                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6461                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6462                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6463                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6464
6465                      /* always flag draws, for judging claims */
6466                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6467
6468                      if(canAdjudicate && appData.materialDraws) {
6469                          /* but only adjudicate them if adjudication enabled */
6470                          if(engineOpponent) {
6471                            SendToProgram("force\n", engineOpponent); // suppress reply
6472                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6473                          }
6474                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6475                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6476                          return 1;
6477                      }
6478                 }
6479
6480                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6481                 if(NrPieces == 4 && 
6482                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6483                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6484                    || NrWN==2 || NrBN==2     /* KNNK */
6485                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6486                   ) ) {
6487                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6488                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6489                           if(engineOpponent) {
6490                             SendToProgram("force\n", engineOpponent); // suppress reply
6491                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6492                           }
6493                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6494                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6495                           return 1;
6496                      }
6497                 } else moveCount = 6;
6498             }
6499         }
6500           
6501         if (appData.debugMode) { int i;
6502             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6503                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6504                     appData.drawRepeats);
6505             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6506               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6507             
6508         }
6509
6510         // Repetition draws and 50-move rule can be applied independently of legality testing
6511
6512                 /* Check for rep-draws */
6513                 count = 0;
6514                 for(k = forwardMostMove-2;
6515                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6516                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6517                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6518                     k-=2)
6519                 {   int rights=0;
6520                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6521                         /* compare castling rights */
6522                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6523                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6524                                 rights++; /* King lost rights, while rook still had them */
6525                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6526                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6527                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6528                                    rights++; /* but at least one rook lost them */
6529                         }
6530                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6531                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6532                                 rights++; 
6533                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6534                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6535                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6536                                    rights++;
6537                         }
6538                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6539                             && appData.drawRepeats > 1) {
6540                              /* adjudicate after user-specified nr of repeats */
6541                              if(engineOpponent) {
6542                                SendToProgram("force\n", engineOpponent); // suppress reply
6543                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6544                              }
6545                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6546                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6547                                 // [HGM] xiangqi: check for forbidden perpetuals
6548                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6549                                 for(m=forwardMostMove; m>k; m-=2) {
6550                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6551                                         ourPerpetual = 0; // the current mover did not always check
6552                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6553                                         hisPerpetual = 0; // the opponent did not always check
6554                                 }
6555                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6556                                                                         ourPerpetual, hisPerpetual);
6557                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6558                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6559                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6560                                     return 1;
6561                                 }
6562                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6563                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6564                                 // Now check for perpetual chases
6565                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6566                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6567                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6568                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6569                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6570                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6571                                         return 1;
6572                                     }
6573                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6574                                         break; // Abort repetition-checking loop.
6575                                 }
6576                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6577                              }
6578                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6579                              return 1;
6580                         }
6581                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6582                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6583                     }
6584                 }
6585
6586                 /* Now we test for 50-move draws. Determine ply count */
6587                 count = forwardMostMove;
6588                 /* look for last irreversble move */
6589                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6590                     count--;
6591                 /* if we hit starting position, add initial plies */
6592                 if( count == backwardMostMove )
6593                     count -= initialRulePlies;
6594                 count = forwardMostMove - count; 
6595                 if( count >= 100)
6596                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6597                          /* this is used to judge if draw claims are legal */
6598                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6599                          if(engineOpponent) {
6600                            SendToProgram("force\n", engineOpponent); // suppress reply
6601                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6602                          }
6603                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6604                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6605                          return 1;
6606                 }
6607
6608                 /* if draw offer is pending, treat it as a draw claim
6609                  * when draw condition present, to allow engines a way to
6610                  * claim draws before making their move to avoid a race
6611                  * condition occurring after their move
6612                  */
6613                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6614                          char *p = NULL;
6615                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6616                              p = "Draw claim: 50-move rule";
6617                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6618                              p = "Draw claim: 3-fold repetition";
6619                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6620                              p = "Draw claim: insufficient mating material";
6621                          if( p != NULL && canAdjudicate) {
6622                              if(engineOpponent) {
6623                                SendToProgram("force\n", engineOpponent); // suppress reply
6624                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6625                              }
6626                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6627                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6628                              return 1;
6629                          }
6630                 }
6631
6632                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6633                     if(engineOpponent) {
6634                       SendToProgram("force\n", engineOpponent); // suppress reply
6635                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6636                     }
6637                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6638                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6639                     return 1;
6640                 }
6641         return 0;
6642 }
6643
6644 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6645 {   // [HGM] book: this routine intercepts moves to simulate book replies
6646     char *bookHit = NULL;
6647
6648     //first determine if the incoming move brings opponent into his book
6649     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6650         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6651     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6652     if(bookHit != NULL && !cps->bookSuspend) {
6653         // make sure opponent is not going to reply after receiving move to book position
6654         SendToProgram("force\n", cps);
6655         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6656     }
6657     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6658     // now arrange restart after book miss
6659     if(bookHit) {
6660         // after a book hit we never send 'go', and the code after the call to this routine
6661         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6662         char buf[MSG_SIZ];
6663         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6664         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6665         SendToProgram(buf, cps);
6666         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6667     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6668         SendToProgram("go\n", cps);
6669         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6670     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6671         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6672             SendToProgram("go\n", cps); 
6673         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6674     }
6675     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6676 }
6677
6678 char *savedMessage;
6679 ChessProgramState *savedState;
6680 void DeferredBookMove(void)
6681 {
6682         if(savedState->lastPing != savedState->lastPong)
6683                     ScheduleDelayedEvent(DeferredBookMove, 10);
6684         else
6685         HandleMachineMove(savedMessage, savedState);
6686 }
6687
6688 void
6689 HandleMachineMove(message, cps)
6690      char *message;
6691      ChessProgramState *cps;
6692 {
6693     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6694     char realname[MSG_SIZ];
6695     int fromX, fromY, toX, toY;
6696     ChessMove moveType;
6697     char promoChar;
6698     char *p;
6699     int machineWhite;
6700     char *bookHit;
6701
6702     cps->userError = 0;
6703
6704 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6705     /*
6706      * Kludge to ignore BEL characters
6707      */
6708     while (*message == '\007') message++;
6709
6710     /*
6711      * [HGM] engine debug message: ignore lines starting with '#' character
6712      */
6713     if(cps->debug && *message == '#') return;
6714
6715     /*
6716      * Look for book output
6717      */
6718     if (cps == &first && bookRequested) {
6719         if (message[0] == '\t' || message[0] == ' ') {
6720             /* Part of the book output is here; append it */
6721             strcat(bookOutput, message);
6722             strcat(bookOutput, "  \n");
6723             return;
6724         } else if (bookOutput[0] != NULLCHAR) {
6725             /* All of book output has arrived; display it */
6726             char *p = bookOutput;
6727             while (*p != NULLCHAR) {
6728                 if (*p == '\t') *p = ' ';
6729                 p++;
6730             }
6731             DisplayInformation(bookOutput);
6732             bookRequested = FALSE;
6733             /* Fall through to parse the current output */
6734         }
6735     }
6736
6737     /*
6738      * Look for machine move.
6739      */
6740     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6741         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6742     {
6743         /* This method is only useful on engines that support ping */
6744         if (cps->lastPing != cps->lastPong) {
6745           if (gameMode == BeginningOfGame) {
6746             /* Extra move from before last new; ignore */
6747             if (appData.debugMode) {
6748                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6749             }
6750           } else {
6751             if (appData.debugMode) {
6752                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6753                         cps->which, gameMode);
6754             }
6755
6756             SendToProgram("undo\n", cps);
6757           }
6758           return;
6759         }
6760
6761         switch (gameMode) {
6762           case BeginningOfGame:
6763             /* Extra move from before last reset; ignore */
6764             if (appData.debugMode) {
6765                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6766             }
6767             return;
6768
6769           case EndOfGame:
6770           case IcsIdle:
6771           default:
6772             /* Extra move after we tried to stop.  The mode test is
6773                not a reliable way of detecting this problem, but it's
6774                the best we can do on engines that don't support ping.
6775             */
6776             if (appData.debugMode) {
6777                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6778                         cps->which, gameMode);
6779             }
6780             SendToProgram("undo\n", cps);
6781             return;
6782
6783           case MachinePlaysWhite:
6784           case IcsPlayingWhite:
6785             machineWhite = TRUE;
6786             break;
6787
6788           case MachinePlaysBlack:
6789           case IcsPlayingBlack:
6790             machineWhite = FALSE;
6791             break;
6792
6793           case TwoMachinesPlay:
6794             machineWhite = (cps->twoMachinesColor[0] == 'w');
6795             break;
6796         }
6797         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6798             if (appData.debugMode) {
6799                 fprintf(debugFP,
6800                         "Ignoring move out of turn by %s, gameMode %d"
6801                         ", forwardMost %d\n",
6802                         cps->which, gameMode, forwardMostMove);
6803             }
6804             return;
6805         }
6806
6807     if (appData.debugMode) { int f = forwardMostMove;
6808         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6809                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6810                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6811     }
6812         if(cps->alphaRank) AlphaRank(machineMove, 4);
6813         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6814                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6815             /* Machine move could not be parsed; ignore it. */
6816             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6817                     machineMove, cps->which);
6818             DisplayError(buf1, 0);
6819             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6820                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6821             if (gameMode == TwoMachinesPlay) {
6822               GameEnds(machineWhite ? BlackWins : WhiteWins,
6823                        buf1, GE_XBOARD);
6824             }
6825             return;
6826         }
6827
6828         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6829         /* So we have to redo legality test with true e.p. status here,  */
6830         /* to make sure an illegal e.p. capture does not slip through,   */
6831         /* to cause a forfeit on a justified illegal-move complaint      */
6832         /* of the opponent.                                              */
6833         if( gameMode==TwoMachinesPlay && appData.testLegality
6834             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6835                                                               ) {
6836            ChessMove moveType;
6837            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6838                              fromY, fromX, toY, toX, promoChar);
6839             if (appData.debugMode) {
6840                 int i;
6841                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6842                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6843                 fprintf(debugFP, "castling rights\n");
6844             }
6845             if(moveType == IllegalMove) {
6846                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6847                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6848                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6849                            buf1, GE_XBOARD);
6850                 return;
6851            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6852            /* [HGM] Kludge to handle engines that send FRC-style castling
6853               when they shouldn't (like TSCP-Gothic) */
6854            switch(moveType) {
6855              case WhiteASideCastleFR:
6856              case BlackASideCastleFR:
6857                toX+=2;
6858                currentMoveString[2]++;
6859                break;
6860              case WhiteHSideCastleFR:
6861              case BlackHSideCastleFR:
6862                toX--;
6863                currentMoveString[2]--;
6864                break;
6865              default: ; // nothing to do, but suppresses warning of pedantic compilers
6866            }
6867         }
6868         hintRequested = FALSE;
6869         lastHint[0] = NULLCHAR;
6870         bookRequested = FALSE;
6871         /* Program may be pondering now */
6872         cps->maybeThinking = TRUE;
6873         if (cps->sendTime == 2) cps->sendTime = 1;
6874         if (cps->offeredDraw) cps->offeredDraw--;
6875
6876         /* currentMoveString is set as a side-effect of ParseOneMove */
6877         strcpy(machineMove, currentMoveString);
6878         strcat(machineMove, "\n");
6879         strcpy(moveList[forwardMostMove], machineMove);
6880
6881         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6882
6883         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6884         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6885             int count = 0;
6886
6887             while( count < adjudicateLossPlies ) {
6888                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6889
6890                 if( count & 1 ) {
6891                     score = -score; /* Flip score for winning side */
6892                 }
6893
6894                 if( score > adjudicateLossThreshold ) {
6895                     break;
6896                 }
6897
6898                 count++;
6899             }
6900
6901             if( count >= adjudicateLossPlies ) {
6902                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6903
6904                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6905                     "Xboard adjudication", 
6906                     GE_XBOARD );
6907
6908                 return;
6909             }
6910         }
6911
6912         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6913
6914 #if ZIPPY
6915         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6916             first.initDone) {
6917           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6918                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6919                 SendToICS("draw ");
6920                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6921           }
6922           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6923           ics_user_moved = 1;
6924           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6925                 char buf[3*MSG_SIZ];
6926
6927                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6928                         programStats.score / 100.,
6929                         programStats.depth,
6930                         programStats.time / 100.,
6931                         (unsigned int)programStats.nodes,
6932                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6933                         programStats.movelist);
6934                 SendToICS(buf);
6935 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6936           }
6937         }
6938 #endif
6939
6940         /* [AS] Save move info and clear stats for next move */
6941         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
6942         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
6943         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6944         ClearProgramStats();
6945         thinkOutput[0] = NULLCHAR;
6946         hiddenThinkOutputState = 0;
6947
6948         bookHit = NULL;
6949         if (gameMode == TwoMachinesPlay) {
6950             /* [HGM] relaying draw offers moved to after reception of move */
6951             /* and interpreting offer as claim if it brings draw condition */
6952             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6953                 SendToProgram("draw\n", cps->other);
6954             }
6955             if (cps->other->sendTime) {
6956                 SendTimeRemaining(cps->other,
6957                                   cps->other->twoMachinesColor[0] == 'w');
6958             }
6959             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6960             if (firstMove && !bookHit) {
6961                 firstMove = FALSE;
6962                 if (cps->other->useColors) {
6963                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6964                 }
6965                 SendToProgram("go\n", cps->other);
6966             }
6967             cps->other->maybeThinking = TRUE;
6968         }
6969
6970         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6971         
6972         if (!pausing && appData.ringBellAfterMoves) {
6973             RingBell();
6974         }
6975
6976         /* 
6977          * Reenable menu items that were disabled while
6978          * machine was thinking
6979          */
6980         if (gameMode != TwoMachinesPlay)
6981             SetUserThinkingEnables();
6982
6983         // [HGM] book: after book hit opponent has received move and is now in force mode
6984         // force the book reply into it, and then fake that it outputted this move by jumping
6985         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6986         if(bookHit) {
6987                 static char bookMove[MSG_SIZ]; // a bit generous?
6988
6989                 strcpy(bookMove, "move ");
6990                 strcat(bookMove, bookHit);
6991                 message = bookMove;
6992                 cps = cps->other;
6993                 programStats.nodes = programStats.depth = programStats.time = 
6994                 programStats.score = programStats.got_only_move = 0;
6995                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6996
6997                 if(cps->lastPing != cps->lastPong) {
6998                     savedMessage = message; // args for deferred call
6999                     savedState = cps;
7000                     ScheduleDelayedEvent(DeferredBookMove, 10);
7001                     return;
7002                 }
7003                 goto FakeBookMove;
7004         }
7005
7006         return;
7007     }
7008
7009     /* Set special modes for chess engines.  Later something general
7010      *  could be added here; for now there is just one kludge feature,
7011      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7012      *  when "xboard" is given as an interactive command.
7013      */
7014     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7015         cps->useSigint = FALSE;
7016         cps->useSigterm = FALSE;
7017     }
7018     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7019       ParseFeatures(message+8, cps);
7020       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7021     }
7022
7023     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7024      * want this, I was asked to put it in, and obliged.
7025      */
7026     if (!strncmp(message, "setboard ", 9)) {
7027         Board initial_position;
7028
7029         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7030
7031         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7032             DisplayError(_("Bad FEN received from engine"), 0);
7033             return ;
7034         } else {
7035            Reset(TRUE, FALSE);
7036            CopyBoard(boards[0], initial_position);
7037            initialRulePlies = FENrulePlies;
7038            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7039            else gameMode = MachinePlaysBlack;                 
7040            DrawPosition(FALSE, boards[currentMove]);
7041         }
7042         return;
7043     }
7044
7045     /*
7046      * Look for communication commands
7047      */
7048     if (!strncmp(message, "telluser ", 9)) {
7049         DisplayNote(message + 9);
7050         return;
7051     }
7052     if (!strncmp(message, "tellusererror ", 14)) {
7053         cps->userError = 1;
7054         DisplayError(message + 14, 0);
7055         return;
7056     }
7057     if (!strncmp(message, "tellopponent ", 13)) {
7058       if (appData.icsActive) {
7059         if (loggedOn) {
7060           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7061           SendToICS(buf1);
7062         }
7063       } else {
7064         DisplayNote(message + 13);
7065       }
7066       return;
7067     }
7068     if (!strncmp(message, "tellothers ", 11)) {
7069       if (appData.icsActive) {
7070         if (loggedOn) {
7071           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7072           SendToICS(buf1);
7073         }
7074       }
7075       return;
7076     }
7077     if (!strncmp(message, "tellall ", 8)) {
7078       if (appData.icsActive) {
7079         if (loggedOn) {
7080           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7081           SendToICS(buf1);
7082         }
7083       } else {
7084         DisplayNote(message + 8);
7085       }
7086       return;
7087     }
7088     if (strncmp(message, "warning", 7) == 0) {
7089         /* Undocumented feature, use tellusererror in new code */
7090         DisplayError(message, 0);
7091         return;
7092     }
7093     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7094         strcpy(realname, cps->tidy);
7095         strcat(realname, " query");
7096         AskQuestion(realname, buf2, buf1, cps->pr);
7097         return;
7098     }
7099     /* Commands from the engine directly to ICS.  We don't allow these to be 
7100      *  sent until we are logged on. Crafty kibitzes have been known to 
7101      *  interfere with the login process.
7102      */
7103     if (loggedOn) {
7104         if (!strncmp(message, "tellics ", 8)) {
7105             SendToICS(message + 8);
7106             SendToICS("\n");
7107             return;
7108         }
7109         if (!strncmp(message, "tellicsnoalias ", 15)) {
7110             SendToICS(ics_prefix);
7111             SendToICS(message + 15);
7112             SendToICS("\n");
7113             return;
7114         }
7115         /* The following are for backward compatibility only */
7116         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7117             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7118             SendToICS(ics_prefix);
7119             SendToICS(message);
7120             SendToICS("\n");
7121             return;
7122         }
7123     }
7124     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7125         return;
7126     }
7127     /*
7128      * If the move is illegal, cancel it and redraw the board.
7129      * Also deal with other error cases.  Matching is rather loose
7130      * here to accommodate engines written before the spec.
7131      */
7132     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7133         strncmp(message, "Error", 5) == 0) {
7134         if (StrStr(message, "name") || 
7135             StrStr(message, "rating") || StrStr(message, "?") ||
7136             StrStr(message, "result") || StrStr(message, "board") ||
7137             StrStr(message, "bk") || StrStr(message, "computer") ||
7138             StrStr(message, "variant") || StrStr(message, "hint") ||
7139             StrStr(message, "random") || StrStr(message, "depth") ||
7140             StrStr(message, "accepted")) {
7141             return;
7142         }
7143         if (StrStr(message, "protover")) {
7144           /* Program is responding to input, so it's apparently done
7145              initializing, and this error message indicates it is
7146              protocol version 1.  So we don't need to wait any longer
7147              for it to initialize and send feature commands. */
7148           FeatureDone(cps, 1);
7149           cps->protocolVersion = 1;
7150           return;
7151         }
7152         cps->maybeThinking = FALSE;
7153
7154         if (StrStr(message, "draw")) {
7155             /* Program doesn't have "draw" command */
7156             cps->sendDrawOffers = 0;
7157             return;
7158         }
7159         if (cps->sendTime != 1 &&
7160             (StrStr(message, "time") || StrStr(message, "otim"))) {
7161           /* Program apparently doesn't have "time" or "otim" command */
7162           cps->sendTime = 0;
7163           return;
7164         }
7165         if (StrStr(message, "analyze")) {
7166             cps->analysisSupport = FALSE;
7167             cps->analyzing = FALSE;
7168             Reset(FALSE, TRUE);
7169             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7170             DisplayError(buf2, 0);
7171             return;
7172         }
7173         if (StrStr(message, "(no matching move)st")) {
7174           /* Special kludge for GNU Chess 4 only */
7175           cps->stKludge = TRUE;
7176           SendTimeControl(cps, movesPerSession, timeControl,
7177                           timeIncrement, appData.searchDepth,
7178                           searchTime);
7179           return;
7180         }
7181         if (StrStr(message, "(no matching move)sd")) {
7182           /* Special kludge for GNU Chess 4 only */
7183           cps->sdKludge = TRUE;
7184           SendTimeControl(cps, movesPerSession, timeControl,
7185                           timeIncrement, appData.searchDepth,
7186                           searchTime);
7187           return;
7188         }
7189         if (!StrStr(message, "llegal")) {
7190             return;
7191         }
7192         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7193             gameMode == IcsIdle) return;
7194         if (forwardMostMove <= backwardMostMove) return;
7195         if (pausing) PauseEvent();
7196       if(appData.forceIllegal) {
7197             // [HGM] illegal: machine refused move; force position after move into it
7198           SendToProgram("force\n", cps);
7199           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7200                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7201                 // when black is to move, while there might be nothing on a2 or black
7202                 // might already have the move. So send the board as if white has the move.
7203                 // But first we must change the stm of the engine, as it refused the last move
7204                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7205                 if(WhiteOnMove(forwardMostMove)) {
7206                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7207                     SendBoard(cps, forwardMostMove); // kludgeless board
7208                 } else {
7209                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7210                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7211                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7212                 }
7213           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7214             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7215                  gameMode == TwoMachinesPlay)
7216               SendToProgram("go\n", cps);
7217             return;
7218       } else
7219         if (gameMode == PlayFromGameFile) {
7220             /* Stop reading this game file */
7221             gameMode = EditGame;
7222             ModeHighlight();
7223         }
7224         currentMove = --forwardMostMove;
7225         DisplayMove(currentMove-1); /* before DisplayMoveError */
7226         SwitchClocks();
7227         DisplayBothClocks();
7228         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7229                 parseList[currentMove], cps->which);
7230         DisplayMoveError(buf1);
7231         DrawPosition(FALSE, boards[currentMove]);
7232
7233         /* [HGM] illegal-move claim should forfeit game when Xboard */
7234         /* only passes fully legal moves                            */
7235         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7236             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7237                                 "False illegal-move claim", GE_XBOARD );
7238         }
7239         return;
7240     }
7241     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7242         /* Program has a broken "time" command that
7243            outputs a string not ending in newline.
7244            Don't use it. */
7245         cps->sendTime = 0;
7246     }
7247     
7248     /*
7249      * If chess program startup fails, exit with an error message.
7250      * Attempts to recover here are futile.
7251      */
7252     if ((StrStr(message, "unknown host") != NULL)
7253         || (StrStr(message, "No remote directory") != NULL)
7254         || (StrStr(message, "not found") != NULL)
7255         || (StrStr(message, "No such file") != NULL)
7256         || (StrStr(message, "can't alloc") != NULL)
7257         || (StrStr(message, "Permission denied") != NULL)) {
7258
7259         cps->maybeThinking = FALSE;
7260         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7261                 cps->which, cps->program, cps->host, message);
7262         RemoveInputSource(cps->isr);
7263         DisplayFatalError(buf1, 0, 1);
7264         return;
7265     }
7266     
7267     /* 
7268      * Look for hint output
7269      */
7270     if (sscanf(message, "Hint: %s", buf1) == 1) {
7271         if (cps == &first && hintRequested) {
7272             hintRequested = FALSE;
7273             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7274                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7275                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7276                                     PosFlags(forwardMostMove),
7277                                     fromY, fromX, toY, toX, promoChar, buf1);
7278                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7279                 DisplayInformation(buf2);
7280             } else {
7281                 /* Hint move could not be parsed!? */
7282               snprintf(buf2, sizeof(buf2),
7283                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7284                         buf1, cps->which);
7285                 DisplayError(buf2, 0);
7286             }
7287         } else {
7288             strcpy(lastHint, buf1);
7289         }
7290         return;
7291     }
7292
7293     /*
7294      * Ignore other messages if game is not in progress
7295      */
7296     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7297         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7298
7299     /*
7300      * look for win, lose, draw, or draw offer
7301      */
7302     if (strncmp(message, "1-0", 3) == 0) {
7303         char *p, *q, *r = "";
7304         p = strchr(message, '{');
7305         if (p) {
7306             q = strchr(p, '}');
7307             if (q) {
7308                 *q = NULLCHAR;
7309                 r = p + 1;
7310             }
7311         }
7312         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7313         return;
7314     } else if (strncmp(message, "0-1", 3) == 0) {
7315         char *p, *q, *r = "";
7316         p = strchr(message, '{');
7317         if (p) {
7318             q = strchr(p, '}');
7319             if (q) {
7320                 *q = NULLCHAR;
7321                 r = p + 1;
7322             }
7323         }
7324         /* Kludge for Arasan 4.1 bug */
7325         if (strcmp(r, "Black resigns") == 0) {
7326             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7327             return;
7328         }
7329         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7330         return;
7331     } else if (strncmp(message, "1/2", 3) == 0) {
7332         char *p, *q, *r = "";
7333         p = strchr(message, '{');
7334         if (p) {
7335             q = strchr(p, '}');
7336             if (q) {
7337                 *q = NULLCHAR;
7338                 r = p + 1;
7339             }
7340         }
7341             
7342         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7343         return;
7344
7345     } else if (strncmp(message, "White resign", 12) == 0) {
7346         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7347         return;
7348     } else if (strncmp(message, "Black resign", 12) == 0) {
7349         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7350         return;
7351     } else if (strncmp(message, "White matches", 13) == 0 ||
7352                strncmp(message, "Black matches", 13) == 0   ) {
7353         /* [HGM] ignore GNUShogi noises */
7354         return;
7355     } else if (strncmp(message, "White", 5) == 0 &&
7356                message[5] != '(' &&
7357                StrStr(message, "Black") == NULL) {
7358         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7359         return;
7360     } else if (strncmp(message, "Black", 5) == 0 &&
7361                message[5] != '(') {
7362         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7363         return;
7364     } else if (strcmp(message, "resign") == 0 ||
7365                strcmp(message, "computer resigns") == 0) {
7366         switch (gameMode) {
7367           case MachinePlaysBlack:
7368           case IcsPlayingBlack:
7369             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7370             break;
7371           case MachinePlaysWhite:
7372           case IcsPlayingWhite:
7373             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7374             break;
7375           case TwoMachinesPlay:
7376             if (cps->twoMachinesColor[0] == 'w')
7377               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7378             else
7379               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7380             break;
7381           default:
7382             /* can't happen */
7383             break;
7384         }
7385         return;
7386     } else if (strncmp(message, "opponent mates", 14) == 0) {
7387         switch (gameMode) {
7388           case MachinePlaysBlack:
7389           case IcsPlayingBlack:
7390             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7391             break;
7392           case MachinePlaysWhite:
7393           case IcsPlayingWhite:
7394             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7395             break;
7396           case TwoMachinesPlay:
7397             if (cps->twoMachinesColor[0] == 'w')
7398               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7399             else
7400               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7401             break;
7402           default:
7403             /* can't happen */
7404             break;
7405         }
7406         return;
7407     } else if (strncmp(message, "computer mates", 14) == 0) {
7408         switch (gameMode) {
7409           case MachinePlaysBlack:
7410           case IcsPlayingBlack:
7411             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7412             break;
7413           case MachinePlaysWhite:
7414           case IcsPlayingWhite:
7415             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7416             break;
7417           case TwoMachinesPlay:
7418             if (cps->twoMachinesColor[0] == 'w')
7419               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7420             else
7421               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7422             break;
7423           default:
7424             /* can't happen */
7425             break;
7426         }
7427         return;
7428     } else if (strncmp(message, "checkmate", 9) == 0) {
7429         if (WhiteOnMove(forwardMostMove)) {
7430             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7431         } else {
7432             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7433         }
7434         return;
7435     } else if (strstr(message, "Draw") != NULL ||
7436                strstr(message, "game is a draw") != NULL) {
7437         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7438         return;
7439     } else if (strstr(message, "offer") != NULL &&
7440                strstr(message, "draw") != NULL) {
7441 #if ZIPPY
7442         if (appData.zippyPlay && first.initDone) {
7443             /* Relay offer to ICS */
7444             SendToICS(ics_prefix);
7445             SendToICS("draw\n");
7446         }
7447 #endif
7448         cps->offeredDraw = 2; /* valid until this engine moves twice */
7449         if (gameMode == TwoMachinesPlay) {
7450             if (cps->other->offeredDraw) {
7451                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7452             /* [HGM] in two-machine mode we delay relaying draw offer      */
7453             /* until after we also have move, to see if it is really claim */
7454             }
7455         } else if (gameMode == MachinePlaysWhite ||
7456                    gameMode == MachinePlaysBlack) {
7457           if (userOfferedDraw) {
7458             DisplayInformation(_("Machine accepts your draw offer"));
7459             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7460           } else {
7461             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7462           }
7463         }
7464     }
7465
7466     
7467     /*
7468      * Look for thinking output
7469      */
7470     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7471           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7472                                 ) {
7473         int plylev, mvleft, mvtot, curscore, time;
7474         char mvname[MOVE_LEN];
7475         u64 nodes; // [DM]
7476         char plyext;
7477         int ignore = FALSE;
7478         int prefixHint = FALSE;
7479         mvname[0] = NULLCHAR;
7480
7481         switch (gameMode) {
7482           case MachinePlaysBlack:
7483           case IcsPlayingBlack:
7484             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7485             break;
7486           case MachinePlaysWhite:
7487           case IcsPlayingWhite:
7488             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7489             break;
7490           case AnalyzeMode:
7491           case AnalyzeFile:
7492             break;
7493           case IcsObserving: /* [DM] icsEngineAnalyze */
7494             if (!appData.icsEngineAnalyze) ignore = TRUE;
7495             break;
7496           case TwoMachinesPlay:
7497             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7498                 ignore = TRUE;
7499             }
7500             break;
7501           default:
7502             ignore = TRUE;
7503             break;
7504         }
7505
7506         if (!ignore) {
7507             buf1[0] = NULLCHAR;
7508             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7509                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7510
7511                 if (plyext != ' ' && plyext != '\t') {
7512                     time *= 100;
7513                 }
7514
7515                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7516                 if( cps->scoreIsAbsolute && 
7517                     ( gameMode == MachinePlaysBlack ||
7518                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7519                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7520                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7521                      !WhiteOnMove(currentMove)
7522                     ) )
7523                 {
7524                     curscore = -curscore;
7525                 }
7526
7527
7528                 programStats.depth = plylev;
7529                 programStats.nodes = nodes;
7530                 programStats.time = time;
7531                 programStats.score = curscore;
7532                 programStats.got_only_move = 0;
7533
7534                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7535                         int ticklen;
7536
7537                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7538                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7539                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7540                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7541                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7542                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7543                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7544                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7545                 }
7546
7547                 /* Buffer overflow protection */
7548                 if (buf1[0] != NULLCHAR) {
7549                     if (strlen(buf1) >= sizeof(programStats.movelist)
7550                         && appData.debugMode) {
7551                         fprintf(debugFP,
7552                                 "PV is too long; using the first %u bytes.\n",
7553                                 (unsigned) sizeof(programStats.movelist) - 1);
7554                     }
7555
7556                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7557                 } else {
7558                     sprintf(programStats.movelist, " no PV\n");
7559                 }
7560
7561                 if (programStats.seen_stat) {
7562                     programStats.ok_to_send = 1;
7563                 }
7564
7565                 if (strchr(programStats.movelist, '(') != NULL) {
7566                     programStats.line_is_book = 1;
7567                     programStats.nr_moves = 0;
7568                     programStats.moves_left = 0;
7569                 } else {
7570                     programStats.line_is_book = 0;
7571                 }
7572
7573                 SendProgramStatsToFrontend( cps, &programStats );
7574
7575                 /* 
7576                     [AS] Protect the thinkOutput buffer from overflow... this
7577                     is only useful if buf1 hasn't overflowed first!
7578                 */
7579                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7580                         plylev, 
7581                         (gameMode == TwoMachinesPlay ?
7582                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7583                         ((double) curscore) / 100.0,
7584                         prefixHint ? lastHint : "",
7585                         prefixHint ? " " : "" );
7586
7587                 if( buf1[0] != NULLCHAR ) {
7588                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7589
7590                     if( strlen(buf1) > max_len ) {
7591                         if( appData.debugMode) {
7592                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7593                         }
7594                         buf1[max_len+1] = '\0';
7595                     }
7596
7597                     strcat( thinkOutput, buf1 );
7598                 }
7599
7600                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7601                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7602                     DisplayMove(currentMove - 1);
7603                 }
7604                 return;
7605
7606             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7607                 /* crafty (9.25+) says "(only move) <move>"
7608                  * if there is only 1 legal move
7609                  */
7610                 sscanf(p, "(only move) %s", buf1);
7611                 sprintf(thinkOutput, "%s (only move)", buf1);
7612                 sprintf(programStats.movelist, "%s (only move)", buf1);
7613                 programStats.depth = 1;
7614                 programStats.nr_moves = 1;
7615                 programStats.moves_left = 1;
7616                 programStats.nodes = 1;
7617                 programStats.time = 1;
7618                 programStats.got_only_move = 1;
7619
7620                 /* Not really, but we also use this member to
7621                    mean "line isn't going to change" (Crafty
7622                    isn't searching, so stats won't change) */
7623                 programStats.line_is_book = 1;
7624
7625                 SendProgramStatsToFrontend( cps, &programStats );
7626                 
7627                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7628                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7629                     DisplayMove(currentMove - 1);
7630                 }
7631                 return;
7632             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7633                               &time, &nodes, &plylev, &mvleft,
7634                               &mvtot, mvname) >= 5) {
7635                 /* The stat01: line is from Crafty (9.29+) in response
7636                    to the "." command */
7637                 programStats.seen_stat = 1;
7638                 cps->maybeThinking = TRUE;
7639
7640                 if (programStats.got_only_move || !appData.periodicUpdates)
7641                   return;
7642
7643                 programStats.depth = plylev;
7644                 programStats.time = time;
7645                 programStats.nodes = nodes;
7646                 programStats.moves_left = mvleft;
7647                 programStats.nr_moves = mvtot;
7648                 strcpy(programStats.move_name, mvname);
7649                 programStats.ok_to_send = 1;
7650                 programStats.movelist[0] = '\0';
7651
7652                 SendProgramStatsToFrontend( cps, &programStats );
7653
7654                 return;
7655
7656             } else if (strncmp(message,"++",2) == 0) {
7657                 /* Crafty 9.29+ outputs this */
7658                 programStats.got_fail = 2;
7659                 return;
7660
7661             } else if (strncmp(message,"--",2) == 0) {
7662                 /* Crafty 9.29+ outputs this */
7663                 programStats.got_fail = 1;
7664                 return;
7665
7666             } else if (thinkOutput[0] != NULLCHAR &&
7667                        strncmp(message, "    ", 4) == 0) {
7668                 unsigned message_len;
7669
7670                 p = message;
7671                 while (*p && *p == ' ') p++;
7672
7673                 message_len = strlen( p );
7674
7675                 /* [AS] Avoid buffer overflow */
7676                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7677                     strcat(thinkOutput, " ");
7678                     strcat(thinkOutput, p);
7679                 }
7680
7681                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7682                     strcat(programStats.movelist, " ");
7683                     strcat(programStats.movelist, p);
7684                 }
7685
7686                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7687                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7688                     DisplayMove(currentMove - 1);
7689                 }
7690                 return;
7691             }
7692         }
7693         else {
7694             buf1[0] = NULLCHAR;
7695
7696             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7697                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7698             {
7699                 ChessProgramStats cpstats;
7700
7701                 if (plyext != ' ' && plyext != '\t') {
7702                     time *= 100;
7703                 }
7704
7705                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7706                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7707                     curscore = -curscore;
7708                 }
7709
7710                 cpstats.depth = plylev;
7711                 cpstats.nodes = nodes;
7712                 cpstats.time = time;
7713                 cpstats.score = curscore;
7714                 cpstats.got_only_move = 0;
7715                 cpstats.movelist[0] = '\0';
7716
7717                 if (buf1[0] != NULLCHAR) {
7718                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7719                 }
7720
7721                 cpstats.ok_to_send = 0;
7722                 cpstats.line_is_book = 0;
7723                 cpstats.nr_moves = 0;
7724                 cpstats.moves_left = 0;
7725
7726                 SendProgramStatsToFrontend( cps, &cpstats );
7727             }
7728         }
7729     }
7730 }
7731
7732
7733 /* Parse a game score from the character string "game", and
7734    record it as the history of the current game.  The game
7735    score is NOT assumed to start from the standard position. 
7736    The display is not updated in any way.
7737    */
7738 void
7739 ParseGameHistory(game)
7740      char *game;
7741 {
7742     ChessMove moveType;
7743     int fromX, fromY, toX, toY, boardIndex;
7744     char promoChar;
7745     char *p, *q;
7746     char buf[MSG_SIZ];
7747
7748     if (appData.debugMode)
7749       fprintf(debugFP, "Parsing game history: %s\n", game);
7750
7751     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7752     gameInfo.site = StrSave(appData.icsHost);
7753     gameInfo.date = PGNDate();
7754     gameInfo.round = StrSave("-");
7755
7756     /* Parse out names of players */
7757     while (*game == ' ') game++;
7758     p = buf;
7759     while (*game != ' ') *p++ = *game++;
7760     *p = NULLCHAR;
7761     gameInfo.white = StrSave(buf);
7762     while (*game == ' ') game++;
7763     p = buf;
7764     while (*game != ' ' && *game != '\n') *p++ = *game++;
7765     *p = NULLCHAR;
7766     gameInfo.black = StrSave(buf);
7767
7768     /* Parse moves */
7769     boardIndex = blackPlaysFirst ? 1 : 0;
7770     yynewstr(game);
7771     for (;;) {
7772         yyboardindex = boardIndex;
7773         moveType = (ChessMove) yylex();
7774         switch (moveType) {
7775           case IllegalMove:             /* maybe suicide chess, etc. */
7776   if (appData.debugMode) {
7777     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7778     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7779     setbuf(debugFP, NULL);
7780   }
7781           case WhitePromotionChancellor:
7782           case BlackPromotionChancellor:
7783           case WhitePromotionArchbishop:
7784           case BlackPromotionArchbishop:
7785           case WhitePromotionQueen:
7786           case BlackPromotionQueen:
7787           case WhitePromotionRook:
7788           case BlackPromotionRook:
7789           case WhitePromotionBishop:
7790           case BlackPromotionBishop:
7791           case WhitePromotionKnight:
7792           case BlackPromotionKnight:
7793           case WhitePromotionKing:
7794           case BlackPromotionKing:
7795           case NormalMove:
7796           case WhiteCapturesEnPassant:
7797           case BlackCapturesEnPassant:
7798           case WhiteKingSideCastle:
7799           case WhiteQueenSideCastle:
7800           case BlackKingSideCastle:
7801           case BlackQueenSideCastle:
7802           case WhiteKingSideCastleWild:
7803           case WhiteQueenSideCastleWild:
7804           case BlackKingSideCastleWild:
7805           case BlackQueenSideCastleWild:
7806           /* PUSH Fabien */
7807           case WhiteHSideCastleFR:
7808           case WhiteASideCastleFR:
7809           case BlackHSideCastleFR:
7810           case BlackASideCastleFR:
7811           /* POP Fabien */
7812             fromX = currentMoveString[0] - AAA;
7813             fromY = currentMoveString[1] - ONE;
7814             toX = currentMoveString[2] - AAA;
7815             toY = currentMoveString[3] - ONE;
7816             promoChar = currentMoveString[4];
7817             break;
7818           case WhiteDrop:
7819           case BlackDrop:
7820             fromX = moveType == WhiteDrop ?
7821               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7822             (int) CharToPiece(ToLower(currentMoveString[0]));
7823             fromY = DROP_RANK;
7824             toX = currentMoveString[2] - AAA;
7825             toY = currentMoveString[3] - ONE;
7826             promoChar = NULLCHAR;
7827             break;
7828           case AmbiguousMove:
7829             /* bug? */
7830             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7831   if (appData.debugMode) {
7832     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7833     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7834     setbuf(debugFP, NULL);
7835   }
7836             DisplayError(buf, 0);
7837             return;
7838           case ImpossibleMove:
7839             /* bug? */
7840             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7841   if (appData.debugMode) {
7842     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7843     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7844     setbuf(debugFP, NULL);
7845   }
7846             DisplayError(buf, 0);
7847             return;
7848           case (ChessMove) 0:   /* end of file */
7849             if (boardIndex < backwardMostMove) {
7850                 /* Oops, gap.  How did that happen? */
7851                 DisplayError(_("Gap in move list"), 0);
7852                 return;
7853             }
7854             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7855             if (boardIndex > forwardMostMove) {
7856                 forwardMostMove = boardIndex;
7857             }
7858             return;
7859           case ElapsedTime:
7860             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7861                 strcat(parseList[boardIndex-1], " ");
7862                 strcat(parseList[boardIndex-1], yy_text);
7863             }
7864             continue;
7865           case Comment:
7866           case PGNTag:
7867           case NAG:
7868           default:
7869             /* ignore */
7870             continue;
7871           case WhiteWins:
7872           case BlackWins:
7873           case GameIsDrawn:
7874           case GameUnfinished:
7875             if (gameMode == IcsExamining) {
7876                 if (boardIndex < backwardMostMove) {
7877                     /* Oops, gap.  How did that happen? */
7878                     return;
7879                 }
7880                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7881                 return;
7882             }
7883             gameInfo.result = moveType;
7884             p = strchr(yy_text, '{');
7885             if (p == NULL) p = strchr(yy_text, '(');
7886             if (p == NULL) {
7887                 p = yy_text;
7888                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7889             } else {
7890                 q = strchr(p, *p == '{' ? '}' : ')');
7891                 if (q != NULL) *q = NULLCHAR;
7892                 p++;
7893             }
7894             gameInfo.resultDetails = StrSave(p);
7895             continue;
7896         }
7897         if (boardIndex >= forwardMostMove &&
7898             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7899             backwardMostMove = blackPlaysFirst ? 1 : 0;
7900             return;
7901         }
7902         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7903                                  fromY, fromX, toY, toX, promoChar,
7904                                  parseList[boardIndex]);
7905         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7906         /* currentMoveString is set as a side-effect of yylex */
7907         strcpy(moveList[boardIndex], currentMoveString);
7908         strcat(moveList[boardIndex], "\n");
7909         boardIndex++;
7910         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7911         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7912           case MT_NONE:
7913           case MT_STALEMATE:
7914           default:
7915             break;
7916           case MT_CHECK:
7917             if(gameInfo.variant != VariantShogi)
7918                 strcat(parseList[boardIndex - 1], "+");
7919             break;
7920           case MT_CHECKMATE:
7921           case MT_STAINMATE:
7922             strcat(parseList[boardIndex - 1], "#");
7923             break;
7924         }
7925     }
7926 }
7927
7928
7929 /* Apply a move to the given board  */
7930 void
7931 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7932      int fromX, fromY, toX, toY;
7933      int promoChar;
7934      Board board;
7935 {
7936   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7937   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7938
7939     /* [HGM] compute & store e.p. status and castling rights for new position */
7940     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7941     { int i;
7942
7943       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7944       oldEP = (signed char)board[EP_STATUS];
7945       board[EP_STATUS] = EP_NONE;
7946
7947       if( board[toY][toX] != EmptySquare ) 
7948            board[EP_STATUS] = EP_CAPTURE;  
7949
7950       if( board[fromY][fromX] == WhitePawn ) {
7951            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7952                board[EP_STATUS] = EP_PAWN_MOVE;
7953            if( toY-fromY==2) {
7954                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7955                         gameInfo.variant != VariantBerolina || toX < fromX)
7956                       board[EP_STATUS] = toX | berolina;
7957                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7958                         gameInfo.variant != VariantBerolina || toX > fromX) 
7959                       board[EP_STATUS] = toX;
7960            }
7961       } else 
7962       if( board[fromY][fromX] == BlackPawn ) {
7963            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7964                board[EP_STATUS] = EP_PAWN_MOVE; 
7965            if( toY-fromY== -2) {
7966                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7967                         gameInfo.variant != VariantBerolina || toX < fromX)
7968                       board[EP_STATUS] = toX | berolina;
7969                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7970                         gameInfo.variant != VariantBerolina || toX > fromX) 
7971                       board[EP_STATUS] = toX;
7972            }
7973        }
7974
7975        for(i=0; i<nrCastlingRights; i++) {
7976            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7977               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7978              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7979        }
7980
7981     }
7982
7983   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7984   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7985        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7986          
7987   if (fromX == toX && fromY == toY) return;
7988
7989   if (fromY == DROP_RANK) {
7990         /* must be first */
7991         piece = board[toY][toX] = (ChessSquare) fromX;
7992   } else {
7993      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7994      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7995      if(gameInfo.variant == VariantKnightmate)
7996          king += (int) WhiteUnicorn - (int) WhiteKing;
7997
7998     /* Code added by Tord: */
7999     /* FRC castling assumed when king captures friendly rook. */
8000     if (board[fromY][fromX] == WhiteKing &&
8001              board[toY][toX] == WhiteRook) {
8002       board[fromY][fromX] = EmptySquare;
8003       board[toY][toX] = EmptySquare;
8004       if(toX > fromX) {
8005         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8006       } else {
8007         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8008       }
8009     } else if (board[fromY][fromX] == BlackKing &&
8010                board[toY][toX] == BlackRook) {
8011       board[fromY][fromX] = EmptySquare;
8012       board[toY][toX] = EmptySquare;
8013       if(toX > fromX) {
8014         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8015       } else {
8016         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8017       }
8018     /* End of code added by Tord */
8019
8020     } else if (board[fromY][fromX] == king
8021         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8022         && toY == fromY && toX > fromX+1) {
8023         board[fromY][fromX] = EmptySquare;
8024         board[toY][toX] = king;
8025         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8026         board[fromY][BOARD_RGHT-1] = EmptySquare;
8027     } else if (board[fromY][fromX] == king
8028         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8029                && toY == fromY && toX < fromX-1) {
8030         board[fromY][fromX] = EmptySquare;
8031         board[toY][toX] = king;
8032         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8033         board[fromY][BOARD_LEFT] = EmptySquare;
8034     } else if (board[fromY][fromX] == WhitePawn
8035                && toY >= BOARD_HEIGHT-promoRank
8036                && gameInfo.variant != VariantXiangqi
8037                ) {
8038         /* white pawn promotion */
8039         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8040         if (board[toY][toX] == EmptySquare) {
8041             board[toY][toX] = WhiteQueen;
8042         }
8043         if(gameInfo.variant==VariantBughouse ||
8044            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8045             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8046         board[fromY][fromX] = EmptySquare;
8047     } else if ((fromY == BOARD_HEIGHT-4)
8048                && (toX != fromX)
8049                && gameInfo.variant != VariantXiangqi
8050                && gameInfo.variant != VariantBerolina
8051                && (board[fromY][fromX] == WhitePawn)
8052                && (board[toY][toX] == EmptySquare)) {
8053         board[fromY][fromX] = EmptySquare;
8054         board[toY][toX] = WhitePawn;
8055         captured = board[toY - 1][toX];
8056         board[toY - 1][toX] = EmptySquare;
8057     } else if ((fromY == BOARD_HEIGHT-4)
8058                && (toX == fromX)
8059                && gameInfo.variant == VariantBerolina
8060                && (board[fromY][fromX] == WhitePawn)
8061                && (board[toY][toX] == EmptySquare)) {
8062         board[fromY][fromX] = EmptySquare;
8063         board[toY][toX] = WhitePawn;
8064         if(oldEP & EP_BEROLIN_A) {
8065                 captured = board[fromY][fromX-1];
8066                 board[fromY][fromX-1] = EmptySquare;
8067         }else{  captured = board[fromY][fromX+1];
8068                 board[fromY][fromX+1] = EmptySquare;
8069         }
8070     } else if (board[fromY][fromX] == king
8071         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8072                && toY == fromY && toX > fromX+1) {
8073         board[fromY][fromX] = EmptySquare;
8074         board[toY][toX] = king;
8075         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8076         board[fromY][BOARD_RGHT-1] = EmptySquare;
8077     } else if (board[fromY][fromX] == king
8078         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8079                && toY == fromY && toX < fromX-1) {
8080         board[fromY][fromX] = EmptySquare;
8081         board[toY][toX] = king;
8082         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8083         board[fromY][BOARD_LEFT] = EmptySquare;
8084     } else if (fromY == 7 && fromX == 3
8085                && board[fromY][fromX] == BlackKing
8086                && toY == 7 && toX == 5) {
8087         board[fromY][fromX] = EmptySquare;
8088         board[toY][toX] = BlackKing;
8089         board[fromY][7] = EmptySquare;
8090         board[toY][4] = BlackRook;
8091     } else if (fromY == 7 && fromX == 3
8092                && board[fromY][fromX] == BlackKing
8093                && toY == 7 && toX == 1) {
8094         board[fromY][fromX] = EmptySquare;
8095         board[toY][toX] = BlackKing;
8096         board[fromY][0] = EmptySquare;
8097         board[toY][2] = BlackRook;
8098     } else if (board[fromY][fromX] == BlackPawn
8099                && toY < promoRank
8100                && gameInfo.variant != VariantXiangqi
8101                ) {
8102         /* black pawn promotion */
8103         board[toY][toX] = CharToPiece(ToLower(promoChar));
8104         if (board[toY][toX] == EmptySquare) {
8105             board[toY][toX] = BlackQueen;
8106         }
8107         if(gameInfo.variant==VariantBughouse ||
8108            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8109             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8110         board[fromY][fromX] = EmptySquare;
8111     } else if ((fromY == 3)
8112                && (toX != fromX)
8113                && gameInfo.variant != VariantXiangqi
8114                && gameInfo.variant != VariantBerolina
8115                && (board[fromY][fromX] == BlackPawn)
8116                && (board[toY][toX] == EmptySquare)) {
8117         board[fromY][fromX] = EmptySquare;
8118         board[toY][toX] = BlackPawn;
8119         captured = board[toY + 1][toX];
8120         board[toY + 1][toX] = EmptySquare;
8121     } else if ((fromY == 3)
8122                && (toX == fromX)
8123                && gameInfo.variant == VariantBerolina
8124                && (board[fromY][fromX] == BlackPawn)
8125                && (board[toY][toX] == EmptySquare)) {
8126         board[fromY][fromX] = EmptySquare;
8127         board[toY][toX] = BlackPawn;
8128         if(oldEP & EP_BEROLIN_A) {
8129                 captured = board[fromY][fromX-1];
8130                 board[fromY][fromX-1] = EmptySquare;
8131         }else{  captured = board[fromY][fromX+1];
8132                 board[fromY][fromX+1] = EmptySquare;
8133         }
8134     } else {
8135         board[toY][toX] = board[fromY][fromX];
8136         board[fromY][fromX] = EmptySquare;
8137     }
8138
8139     /* [HGM] now we promote for Shogi, if needed */
8140     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8141         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8142   }
8143
8144     if (gameInfo.holdingsWidth != 0) {
8145
8146       /* !!A lot more code needs to be written to support holdings  */
8147       /* [HGM] OK, so I have written it. Holdings are stored in the */
8148       /* penultimate board files, so they are automaticlly stored   */
8149       /* in the game history.                                       */
8150       if (fromY == DROP_RANK) {
8151         /* Delete from holdings, by decreasing count */
8152         /* and erasing image if necessary            */
8153         p = (int) fromX;
8154         if(p < (int) BlackPawn) { /* white drop */
8155              p -= (int)WhitePawn;
8156                  p = PieceToNumber((ChessSquare)p);
8157              if(p >= gameInfo.holdingsSize) p = 0;
8158              if(--board[p][BOARD_WIDTH-2] <= 0)
8159                   board[p][BOARD_WIDTH-1] = EmptySquare;
8160              if((int)board[p][BOARD_WIDTH-2] < 0)
8161                         board[p][BOARD_WIDTH-2] = 0;
8162         } else {                  /* black drop */
8163              p -= (int)BlackPawn;
8164                  p = PieceToNumber((ChessSquare)p);
8165              if(p >= gameInfo.holdingsSize) p = 0;
8166              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8167                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8168              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8169                         board[BOARD_HEIGHT-1-p][1] = 0;
8170         }
8171       }
8172       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8173           && gameInfo.variant != VariantBughouse        ) {
8174         /* [HGM] holdings: Add to holdings, if holdings exist */
8175         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8176                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8177                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8178         }
8179         p = (int) captured;
8180         if (p >= (int) BlackPawn) {
8181           p -= (int)BlackPawn;
8182           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8183                   /* in Shogi restore piece to its original  first */
8184                   captured = (ChessSquare) (DEMOTED captured);
8185                   p = DEMOTED p;
8186           }
8187           p = PieceToNumber((ChessSquare)p);
8188           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8189           board[p][BOARD_WIDTH-2]++;
8190           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8191         } else {
8192           p -= (int)WhitePawn;
8193           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8194                   captured = (ChessSquare) (DEMOTED captured);
8195                   p = DEMOTED p;
8196           }
8197           p = PieceToNumber((ChessSquare)p);
8198           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8199           board[BOARD_HEIGHT-1-p][1]++;
8200           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8201         }
8202       }
8203     } else if (gameInfo.variant == VariantAtomic) {
8204       if (captured != EmptySquare) {
8205         int y, x;
8206         for (y = toY-1; y <= toY+1; y++) {
8207           for (x = toX-1; x <= toX+1; x++) {
8208             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8209                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8210               board[y][x] = EmptySquare;
8211             }
8212           }
8213         }
8214         board[toY][toX] = EmptySquare;
8215       }
8216     }
8217     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8218         /* [HGM] Shogi promotions */
8219         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8220     }
8221
8222     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8223                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8224         // [HGM] superchess: take promotion piece out of holdings
8225         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8226         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8227             if(!--board[k][BOARD_WIDTH-2])
8228                 board[k][BOARD_WIDTH-1] = EmptySquare;
8229         } else {
8230             if(!--board[BOARD_HEIGHT-1-k][1])
8231                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8232         }
8233     }
8234
8235 }
8236
8237 /* Updates forwardMostMove */
8238 void
8239 MakeMove(fromX, fromY, toX, toY, promoChar)
8240      int fromX, fromY, toX, toY;
8241      int promoChar;
8242 {
8243 //    forwardMostMove++; // [HGM] bare: moved downstream
8244
8245     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8246         int timeLeft; static int lastLoadFlag=0; int king, piece;
8247         piece = boards[forwardMostMove][fromY][fromX];
8248         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8249         if(gameInfo.variant == VariantKnightmate)
8250             king += (int) WhiteUnicorn - (int) WhiteKing;
8251         if(forwardMostMove == 0) {
8252             if(blackPlaysFirst) 
8253                 fprintf(serverMoves, "%s;", second.tidy);
8254             fprintf(serverMoves, "%s;", first.tidy);
8255             if(!blackPlaysFirst) 
8256                 fprintf(serverMoves, "%s;", second.tidy);
8257         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8258         lastLoadFlag = loadFlag;
8259         // print base move
8260         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8261         // print castling suffix
8262         if( toY == fromY && piece == king ) {
8263             if(toX-fromX > 1)
8264                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8265             if(fromX-toX >1)
8266                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8267         }
8268         // e.p. suffix
8269         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8270              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8271              boards[forwardMostMove][toY][toX] == EmptySquare
8272              && fromX != toX )
8273                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8274         // promotion suffix
8275         if(promoChar != NULLCHAR)
8276                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8277         if(!loadFlag) {
8278             fprintf(serverMoves, "/%d/%d",
8279                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8280             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8281             else                      timeLeft = blackTimeRemaining/1000;
8282             fprintf(serverMoves, "/%d", timeLeft);
8283         }
8284         fflush(serverMoves);
8285     }
8286
8287     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8288       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8289                         0, 1);
8290       return;
8291     }
8292     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8293     if (commentList[forwardMostMove+1] != NULL) {
8294         free(commentList[forwardMostMove+1]);
8295         commentList[forwardMostMove+1] = NULL;
8296     }
8297     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8298     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8299     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8300     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8301     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8302     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8303     gameInfo.result = GameUnfinished;
8304     if (gameInfo.resultDetails != NULL) {
8305         free(gameInfo.resultDetails);
8306         gameInfo.resultDetails = NULL;
8307     }
8308     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8309                               moveList[forwardMostMove - 1]);
8310     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8311                              PosFlags(forwardMostMove - 1),
8312                              fromY, fromX, toY, toX, promoChar,
8313                              parseList[forwardMostMove - 1]);
8314     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8315       case MT_NONE:
8316       case MT_STALEMATE:
8317       default:
8318         break;
8319       case MT_CHECK:
8320         if(gameInfo.variant != VariantShogi)
8321             strcat(parseList[forwardMostMove - 1], "+");
8322         break;
8323       case MT_CHECKMATE:
8324       case MT_STAINMATE:
8325         strcat(parseList[forwardMostMove - 1], "#");
8326         break;
8327     }
8328     if (appData.debugMode) {
8329         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8330     }
8331
8332 }
8333
8334 /* Updates currentMove if not pausing */
8335 void
8336 ShowMove(fromX, fromY, toX, toY)
8337 {
8338     int instant = (gameMode == PlayFromGameFile) ?
8339         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8340     if(appData.noGUI) return;
8341     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8342         if (!instant) {
8343             if (forwardMostMove == currentMove + 1) {
8344                 AnimateMove(boards[forwardMostMove - 1],
8345                             fromX, fromY, toX, toY);
8346             }
8347             if (appData.highlightLastMove) {
8348                 SetHighlights(fromX, fromY, toX, toY);
8349             }
8350         }
8351         currentMove = forwardMostMove;
8352     }
8353
8354     if (instant) return;
8355
8356     DisplayMove(currentMove - 1);
8357     DrawPosition(FALSE, boards[currentMove]);
8358     DisplayBothClocks();
8359     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8360 }
8361
8362 void SendEgtPath(ChessProgramState *cps)
8363 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8364         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8365
8366         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8367
8368         while(*p) {
8369             char c, *q = name+1, *r, *s;
8370
8371             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8372             while(*p && *p != ',') *q++ = *p++;
8373             *q++ = ':'; *q = 0;
8374             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8375                 strcmp(name, ",nalimov:") == 0 ) {
8376                 // take nalimov path from the menu-changeable option first, if it is defined
8377                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8378                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8379             } else
8380             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8381                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8382                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8383                 s = r = StrStr(s, ":") + 1; // beginning of path info
8384                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8385                 c = *r; *r = 0;             // temporarily null-terminate path info
8386                     *--q = 0;               // strip of trailig ':' from name
8387                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8388                 *r = c;
8389                 SendToProgram(buf,cps);     // send egtbpath command for this format
8390             }
8391             if(*p == ',') p++; // read away comma to position for next format name
8392         }
8393 }
8394
8395 void
8396 InitChessProgram(cps, setup)
8397      ChessProgramState *cps;
8398      int setup; /* [HGM] needed to setup FRC opening position */
8399 {
8400     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8401     if (appData.noChessProgram) return;
8402     hintRequested = FALSE;
8403     bookRequested = FALSE;
8404
8405     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8406     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8407     if(cps->memSize) { /* [HGM] memory */
8408         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8409         SendToProgram(buf, cps);
8410     }
8411     SendEgtPath(cps); /* [HGM] EGT */
8412     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8413         sprintf(buf, "cores %d\n", appData.smpCores);
8414         SendToProgram(buf, cps);
8415     }
8416
8417     SendToProgram(cps->initString, cps);
8418     if (gameInfo.variant != VariantNormal &&
8419         gameInfo.variant != VariantLoadable
8420         /* [HGM] also send variant if board size non-standard */
8421         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8422                                             ) {
8423       char *v = VariantName(gameInfo.variant);
8424       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8425         /* [HGM] in protocol 1 we have to assume all variants valid */
8426         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8427         DisplayFatalError(buf, 0, 1);
8428         return;
8429       }
8430
8431       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8432       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8433       if( gameInfo.variant == VariantXiangqi )
8434            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8435       if( gameInfo.variant == VariantShogi )
8436            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8437       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8438            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8439       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8440                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8441            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8442       if( gameInfo.variant == VariantCourier )
8443            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8444       if( gameInfo.variant == VariantSuper )
8445            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8446       if( gameInfo.variant == VariantGreat )
8447            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8448
8449       if(overruled) {
8450            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8451                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8452            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8453            if(StrStr(cps->variants, b) == NULL) { 
8454                // specific sized variant not known, check if general sizing allowed
8455                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8456                    if(StrStr(cps->variants, "boardsize") == NULL) {
8457                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8458                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8459                        DisplayFatalError(buf, 0, 1);
8460                        return;
8461                    }
8462                    /* [HGM] here we really should compare with the maximum supported board size */
8463                }
8464            }
8465       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8466       sprintf(buf, "variant %s\n", b);
8467       SendToProgram(buf, cps);
8468     }
8469     currentlyInitializedVariant = gameInfo.variant;
8470
8471     /* [HGM] send opening position in FRC to first engine */
8472     if(setup) {
8473           SendToProgram("force\n", cps);
8474           SendBoard(cps, 0);
8475           /* engine is now in force mode! Set flag to wake it up after first move. */
8476           setboardSpoiledMachineBlack = 1;
8477     }
8478
8479     if (cps->sendICS) {
8480       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8481       SendToProgram(buf, cps);
8482     }
8483     cps->maybeThinking = FALSE;
8484     cps->offeredDraw = 0;
8485     if (!appData.icsActive) {
8486         SendTimeControl(cps, movesPerSession, timeControl,
8487                         timeIncrement, appData.searchDepth,
8488                         searchTime);
8489     }
8490     if (appData.showThinking 
8491         // [HGM] thinking: four options require thinking output to be sent
8492         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8493                                 ) {
8494         SendToProgram("post\n", cps);
8495     }
8496     SendToProgram("hard\n", cps);
8497     if (!appData.ponderNextMove) {
8498         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8499            it without being sure what state we are in first.  "hard"
8500            is not a toggle, so that one is OK.
8501          */
8502         SendToProgram("easy\n", cps);
8503     }
8504     if (cps->usePing) {
8505       sprintf(buf, "ping %d\n", ++cps->lastPing);
8506       SendToProgram(buf, cps);
8507     }
8508     cps->initDone = TRUE;
8509 }   
8510
8511
8512 void
8513 StartChessProgram(cps)
8514      ChessProgramState *cps;
8515 {
8516     char buf[MSG_SIZ];
8517     int err;
8518
8519     if (appData.noChessProgram) return;
8520     cps->initDone = FALSE;
8521
8522     if (strcmp(cps->host, "localhost") == 0) {
8523         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8524     } else if (*appData.remoteShell == NULLCHAR) {
8525         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8526     } else {
8527         if (*appData.remoteUser == NULLCHAR) {
8528           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8529                     cps->program);
8530         } else {
8531           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8532                     cps->host, appData.remoteUser, cps->program);
8533         }
8534         err = StartChildProcess(buf, "", &cps->pr);
8535     }
8536     
8537     if (err != 0) {
8538         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8539         DisplayFatalError(buf, err, 1);
8540         cps->pr = NoProc;
8541         cps->isr = NULL;
8542         return;
8543     }
8544     
8545     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8546     if (cps->protocolVersion > 1) {
8547       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8548       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8549       cps->comboCnt = 0;  //                and values of combo boxes
8550       SendToProgram(buf, cps);
8551     } else {
8552       SendToProgram("xboard\n", cps);
8553     }
8554 }
8555
8556
8557 void
8558 TwoMachinesEventIfReady P((void))
8559 {
8560   if (first.lastPing != first.lastPong) {
8561     DisplayMessage("", _("Waiting for first chess program"));
8562     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8563     return;
8564   }
8565   if (second.lastPing != second.lastPong) {
8566     DisplayMessage("", _("Waiting for second chess program"));
8567     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8568     return;
8569   }
8570   ThawUI();
8571   TwoMachinesEvent();
8572 }
8573
8574 void
8575 NextMatchGame P((void))
8576 {
8577     int index; /* [HGM] autoinc: step load index during match */
8578     Reset(FALSE, TRUE);
8579     if (*appData.loadGameFile != NULLCHAR) {
8580         index = appData.loadGameIndex;
8581         if(index < 0) { // [HGM] autoinc
8582             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8583             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8584         } 
8585         LoadGameFromFile(appData.loadGameFile,
8586                          index,
8587                          appData.loadGameFile, FALSE);
8588     } else if (*appData.loadPositionFile != NULLCHAR) {
8589         index = appData.loadPositionIndex;
8590         if(index < 0) { // [HGM] autoinc
8591             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8592             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8593         } 
8594         LoadPositionFromFile(appData.loadPositionFile,
8595                              index,
8596                              appData.loadPositionFile);
8597     }
8598     TwoMachinesEventIfReady();
8599 }
8600
8601 void UserAdjudicationEvent( int result )
8602 {
8603     ChessMove gameResult = GameIsDrawn;
8604
8605     if( result > 0 ) {
8606         gameResult = WhiteWins;
8607     }
8608     else if( result < 0 ) {
8609         gameResult = BlackWins;
8610     }
8611
8612     if( gameMode == TwoMachinesPlay ) {
8613         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8614     }
8615 }
8616
8617
8618 // [HGM] save: calculate checksum of game to make games easily identifiable
8619 int StringCheckSum(char *s)
8620 {
8621         int i = 0;
8622         if(s==NULL) return 0;
8623         while(*s) i = i*259 + *s++;
8624         return i;
8625 }
8626
8627 int GameCheckSum()
8628 {
8629         int i, sum=0;
8630         for(i=backwardMostMove; i<forwardMostMove; i++) {
8631                 sum += pvInfoList[i].depth;
8632                 sum += StringCheckSum(parseList[i]);
8633                 sum += StringCheckSum(commentList[i]);
8634                 sum *= 261;
8635         }
8636         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8637         return sum + StringCheckSum(commentList[i]);
8638 } // end of save patch
8639
8640 void
8641 GameEnds(result, resultDetails, whosays)
8642      ChessMove result;
8643      char *resultDetails;
8644      int whosays;
8645 {
8646     GameMode nextGameMode;
8647     int isIcsGame;
8648     char buf[MSG_SIZ];
8649
8650     if(endingGame) return; /* [HGM] crash: forbid recursion */
8651     endingGame = 1;
8652
8653     if (appData.debugMode) {
8654       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8655               result, resultDetails ? resultDetails : "(null)", whosays);
8656     }
8657
8658     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8659         /* If we are playing on ICS, the server decides when the
8660            game is over, but the engine can offer to draw, claim 
8661            a draw, or resign. 
8662          */
8663 #if ZIPPY
8664         if (appData.zippyPlay && first.initDone) {
8665             if (result == GameIsDrawn) {
8666                 /* In case draw still needs to be claimed */
8667                 SendToICS(ics_prefix);
8668                 SendToICS("draw\n");
8669             } else if (StrCaseStr(resultDetails, "resign")) {
8670                 SendToICS(ics_prefix);
8671                 SendToICS("resign\n");
8672             }
8673         }
8674 #endif
8675         endingGame = 0; /* [HGM] crash */
8676         return;
8677     }
8678
8679     /* If we're loading the game from a file, stop */
8680     if (whosays == GE_FILE) {
8681       (void) StopLoadGameTimer();
8682       gameFileFP = NULL;
8683     }
8684
8685     /* Cancel draw offers */
8686     first.offeredDraw = second.offeredDraw = 0;
8687
8688     /* If this is an ICS game, only ICS can really say it's done;
8689        if not, anyone can. */
8690     isIcsGame = (gameMode == IcsPlayingWhite || 
8691                  gameMode == IcsPlayingBlack || 
8692                  gameMode == IcsObserving    || 
8693                  gameMode == IcsExamining);
8694
8695     if (!isIcsGame || whosays == GE_ICS) {
8696         /* OK -- not an ICS game, or ICS said it was done */
8697         StopClocks();
8698         if (!isIcsGame && !appData.noChessProgram) 
8699           SetUserThinkingEnables();
8700     
8701         /* [HGM] if a machine claims the game end we verify this claim */
8702         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8703             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8704                 char claimer;
8705                 ChessMove trueResult = (ChessMove) -1;
8706
8707                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8708                                             first.twoMachinesColor[0] :
8709                                             second.twoMachinesColor[0] ;
8710
8711                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8712                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8713                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8714                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8715                 } else
8716                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8717                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8718                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8719                 } else
8720                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8721                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8722                 }
8723
8724                 // now verify win claims, but not in drop games, as we don't understand those yet
8725                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8726                                                  || gameInfo.variant == VariantGreat) &&
8727                     (result == WhiteWins && claimer == 'w' ||
8728                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8729                       if (appData.debugMode) {
8730                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8731                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8732                       }
8733                       if(result != trueResult) {
8734                               sprintf(buf, "False win claim: '%s'", resultDetails);
8735                               result = claimer == 'w' ? BlackWins : WhiteWins;
8736                               resultDetails = buf;
8737                       }
8738                 } else
8739                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8740                     && (forwardMostMove <= backwardMostMove ||
8741                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8742                         (claimer=='b')==(forwardMostMove&1))
8743                                                                                   ) {
8744                       /* [HGM] verify: draws that were not flagged are false claims */
8745                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8746                       result = claimer == 'w' ? BlackWins : WhiteWins;
8747                       resultDetails = buf;
8748                 }
8749                 /* (Claiming a loss is accepted no questions asked!) */
8750             }
8751             /* [HGM] bare: don't allow bare King to win */
8752             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8753                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8754                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8755                && result != GameIsDrawn)
8756             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8757                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8758                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8759                         if(p >= 0 && p <= (int)WhiteKing) k++;
8760                 }
8761                 if (appData.debugMode) {
8762                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8763                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8764                 }
8765                 if(k <= 1) {
8766                         result = GameIsDrawn;
8767                         sprintf(buf, "%s but bare king", resultDetails);
8768                         resultDetails = buf;
8769                 }
8770             }
8771         }
8772
8773
8774         if(serverMoves != NULL && !loadFlag) { char c = '=';
8775             if(result==WhiteWins) c = '+';
8776             if(result==BlackWins) c = '-';
8777             if(resultDetails != NULL)
8778                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8779         }
8780         if (resultDetails != NULL) {
8781             gameInfo.result = result;
8782             gameInfo.resultDetails = StrSave(resultDetails);
8783
8784             /* display last move only if game was not loaded from file */
8785             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8786                 DisplayMove(currentMove - 1);
8787     
8788             if (forwardMostMove != 0) {
8789                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8790                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8791                                                                 ) {
8792                     if (*appData.saveGameFile != NULLCHAR) {
8793                         SaveGameToFile(appData.saveGameFile, TRUE);
8794                     } else if (appData.autoSaveGames) {
8795                         AutoSaveGame();
8796                     }
8797                     if (*appData.savePositionFile != NULLCHAR) {
8798                         SavePositionToFile(appData.savePositionFile);
8799                     }
8800                 }
8801             }
8802
8803             /* Tell program how game ended in case it is learning */
8804             /* [HGM] Moved this to after saving the PGN, just in case */
8805             /* engine died and we got here through time loss. In that */
8806             /* case we will get a fatal error writing the pipe, which */
8807             /* would otherwise lose us the PGN.                       */
8808             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8809             /* output during GameEnds should never be fatal anymore   */
8810             if (gameMode == MachinePlaysWhite ||
8811                 gameMode == MachinePlaysBlack ||
8812                 gameMode == TwoMachinesPlay ||
8813                 gameMode == IcsPlayingWhite ||
8814                 gameMode == IcsPlayingBlack ||
8815                 gameMode == BeginningOfGame) {
8816                 char buf[MSG_SIZ];
8817                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8818                         resultDetails);
8819                 if (first.pr != NoProc) {
8820                     SendToProgram(buf, &first);
8821                 }
8822                 if (second.pr != NoProc &&
8823                     gameMode == TwoMachinesPlay) {
8824                     SendToProgram(buf, &second);
8825                 }
8826             }
8827         }
8828
8829         if (appData.icsActive) {
8830             if (appData.quietPlay &&
8831                 (gameMode == IcsPlayingWhite ||
8832                  gameMode == IcsPlayingBlack)) {
8833                 SendToICS(ics_prefix);
8834                 SendToICS("set shout 1\n");
8835             }
8836             nextGameMode = IcsIdle;
8837             ics_user_moved = FALSE;
8838             /* clean up premove.  It's ugly when the game has ended and the
8839              * premove highlights are still on the board.
8840              */
8841             if (gotPremove) {
8842               gotPremove = FALSE;
8843               ClearPremoveHighlights();
8844               DrawPosition(FALSE, boards[currentMove]);
8845             }
8846             if (whosays == GE_ICS) {
8847                 switch (result) {
8848                 case WhiteWins:
8849                     if (gameMode == IcsPlayingWhite)
8850                         PlayIcsWinSound();
8851                     else if(gameMode == IcsPlayingBlack)
8852                         PlayIcsLossSound();
8853                     break;
8854                 case BlackWins:
8855                     if (gameMode == IcsPlayingBlack)
8856                         PlayIcsWinSound();
8857                     else if(gameMode == IcsPlayingWhite)
8858                         PlayIcsLossSound();
8859                     break;
8860                 case GameIsDrawn:
8861                     PlayIcsDrawSound();
8862                     break;
8863                 default:
8864                     PlayIcsUnfinishedSound();
8865                 }
8866             }
8867         } else if (gameMode == EditGame ||
8868                    gameMode == PlayFromGameFile || 
8869                    gameMode == AnalyzeMode || 
8870                    gameMode == AnalyzeFile) {
8871             nextGameMode = gameMode;
8872         } else {
8873             nextGameMode = EndOfGame;
8874         }
8875         pausing = FALSE;
8876         ModeHighlight();
8877     } else {
8878         nextGameMode = gameMode;
8879     }
8880
8881     if (appData.noChessProgram) {
8882         gameMode = nextGameMode;
8883         ModeHighlight();
8884         endingGame = 0; /* [HGM] crash */
8885         return;
8886     }
8887
8888     if (first.reuse) {
8889         /* Put first chess program into idle state */
8890         if (first.pr != NoProc &&
8891             (gameMode == MachinePlaysWhite ||
8892              gameMode == MachinePlaysBlack ||
8893              gameMode == TwoMachinesPlay ||
8894              gameMode == IcsPlayingWhite ||
8895              gameMode == IcsPlayingBlack ||
8896              gameMode == BeginningOfGame)) {
8897             SendToProgram("force\n", &first);
8898             if (first.usePing) {
8899               char buf[MSG_SIZ];
8900               sprintf(buf, "ping %d\n", ++first.lastPing);
8901               SendToProgram(buf, &first);
8902             }
8903         }
8904     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8905         /* Kill off first chess program */
8906         if (first.isr != NULL)
8907           RemoveInputSource(first.isr);
8908         first.isr = NULL;
8909     
8910         if (first.pr != NoProc) {
8911             ExitAnalyzeMode();
8912             DoSleep( appData.delayBeforeQuit );
8913             SendToProgram("quit\n", &first);
8914             DoSleep( appData.delayAfterQuit );
8915             DestroyChildProcess(first.pr, first.useSigterm);
8916         }
8917         first.pr = NoProc;
8918     }
8919     if (second.reuse) {
8920         /* Put second chess program into idle state */
8921         if (second.pr != NoProc &&
8922             gameMode == TwoMachinesPlay) {
8923             SendToProgram("force\n", &second);
8924             if (second.usePing) {
8925               char buf[MSG_SIZ];
8926               sprintf(buf, "ping %d\n", ++second.lastPing);
8927               SendToProgram(buf, &second);
8928             }
8929         }
8930     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8931         /* Kill off second chess program */
8932         if (second.isr != NULL)
8933           RemoveInputSource(second.isr);
8934         second.isr = NULL;
8935     
8936         if (second.pr != NoProc) {
8937             DoSleep( appData.delayBeforeQuit );
8938             SendToProgram("quit\n", &second);
8939             DoSleep( appData.delayAfterQuit );
8940             DestroyChildProcess(second.pr, second.useSigterm);
8941         }
8942         second.pr = NoProc;
8943     }
8944
8945     if (matchMode && gameMode == TwoMachinesPlay) {
8946         switch (result) {
8947         case WhiteWins:
8948           if (first.twoMachinesColor[0] == 'w') {
8949             first.matchWins++;
8950           } else {
8951             second.matchWins++;
8952           }
8953           break;
8954         case BlackWins:
8955           if (first.twoMachinesColor[0] == 'b') {
8956             first.matchWins++;
8957           } else {
8958             second.matchWins++;
8959           }
8960           break;
8961         default:
8962           break;
8963         }
8964         if (matchGame < appData.matchGames) {
8965             char *tmp;
8966             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8967                 tmp = first.twoMachinesColor;
8968                 first.twoMachinesColor = second.twoMachinesColor;
8969                 second.twoMachinesColor = tmp;
8970             }
8971             gameMode = nextGameMode;
8972             matchGame++;
8973             if(appData.matchPause>10000 || appData.matchPause<10)
8974                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8975             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8976             endingGame = 0; /* [HGM] crash */
8977             return;
8978         } else {
8979             char buf[MSG_SIZ];
8980             gameMode = nextGameMode;
8981             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8982                     first.tidy, second.tidy,
8983                     first.matchWins, second.matchWins,
8984                     appData.matchGames - (first.matchWins + second.matchWins));
8985             DisplayFatalError(buf, 0, 0);
8986         }
8987     }
8988     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8989         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8990       ExitAnalyzeMode();
8991     gameMode = nextGameMode;
8992     ModeHighlight();
8993     endingGame = 0;  /* [HGM] crash */
8994 }
8995
8996 /* Assumes program was just initialized (initString sent).
8997    Leaves program in force mode. */
8998 void
8999 FeedMovesToProgram(cps, upto) 
9000      ChessProgramState *cps;
9001      int upto;
9002 {
9003     int i;
9004     
9005     if (appData.debugMode)
9006       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9007               startedFromSetupPosition ? "position and " : "",
9008               backwardMostMove, upto, cps->which);
9009     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9010         // [HGM] variantswitch: make engine aware of new variant
9011         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9012                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9013         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9014         SendToProgram(buf, cps);
9015         currentlyInitializedVariant = gameInfo.variant;
9016     }
9017     SendToProgram("force\n", cps);
9018     if (startedFromSetupPosition) {
9019         SendBoard(cps, backwardMostMove);
9020     if (appData.debugMode) {
9021         fprintf(debugFP, "feedMoves\n");
9022     }
9023     }
9024     for (i = backwardMostMove; i < upto; i++) {
9025         SendMoveToProgram(i, cps);
9026     }
9027 }
9028
9029
9030 void
9031 ResurrectChessProgram()
9032 {
9033      /* The chess program may have exited.
9034         If so, restart it and feed it all the moves made so far. */
9035
9036     if (appData.noChessProgram || first.pr != NoProc) return;
9037     
9038     StartChessProgram(&first);
9039     InitChessProgram(&first, FALSE);
9040     FeedMovesToProgram(&first, currentMove);
9041
9042     if (!first.sendTime) {
9043         /* can't tell gnuchess what its clock should read,
9044            so we bow to its notion. */
9045         ResetClocks();
9046         timeRemaining[0][currentMove] = whiteTimeRemaining;
9047         timeRemaining[1][currentMove] = blackTimeRemaining;
9048     }
9049
9050     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9051                 appData.icsEngineAnalyze) && first.analysisSupport) {
9052       SendToProgram("analyze\n", &first);
9053       first.analyzing = TRUE;
9054     }
9055 }
9056
9057 /*
9058  * Button procedures
9059  */
9060 void
9061 Reset(redraw, init)
9062      int redraw, init;
9063 {
9064     int i;
9065
9066     if (appData.debugMode) {
9067         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9068                 redraw, init, gameMode);
9069     }
9070     CleanupTail(); // [HGM] vari: delete any stored variations
9071     pausing = pauseExamInvalid = FALSE;
9072     startedFromSetupPosition = blackPlaysFirst = FALSE;
9073     firstMove = TRUE;
9074     whiteFlag = blackFlag = FALSE;
9075     userOfferedDraw = FALSE;
9076     hintRequested = bookRequested = FALSE;
9077     first.maybeThinking = FALSE;
9078     second.maybeThinking = FALSE;
9079     first.bookSuspend = FALSE; // [HGM] book
9080     second.bookSuspend = FALSE;
9081     thinkOutput[0] = NULLCHAR;
9082     lastHint[0] = NULLCHAR;
9083     ClearGameInfo(&gameInfo);
9084     gameInfo.variant = StringToVariant(appData.variant);
9085     ics_user_moved = ics_clock_paused = FALSE;
9086     ics_getting_history = H_FALSE;
9087     ics_gamenum = -1;
9088     white_holding[0] = black_holding[0] = NULLCHAR;
9089     ClearProgramStats();
9090     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9091     
9092     ResetFrontEnd();
9093     ClearHighlights();
9094     flipView = appData.flipView;
9095     ClearPremoveHighlights();
9096     gotPremove = FALSE;
9097     alarmSounded = FALSE;
9098
9099     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9100     if(appData.serverMovesName != NULL) {
9101         /* [HGM] prepare to make moves file for broadcasting */
9102         clock_t t = clock();
9103         if(serverMoves != NULL) fclose(serverMoves);
9104         serverMoves = fopen(appData.serverMovesName, "r");
9105         if(serverMoves != NULL) {
9106             fclose(serverMoves);
9107             /* delay 15 sec before overwriting, so all clients can see end */
9108             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9109         }
9110         serverMoves = fopen(appData.serverMovesName, "w");
9111     }
9112
9113     ExitAnalyzeMode();
9114     gameMode = BeginningOfGame;
9115     ModeHighlight();
9116     if(appData.icsActive) gameInfo.variant = VariantNormal;
9117     currentMove = forwardMostMove = backwardMostMove = 0;
9118     InitPosition(redraw);
9119     for (i = 0; i < MAX_MOVES; i++) {
9120         if (commentList[i] != NULL) {
9121             free(commentList[i]);
9122             commentList[i] = NULL;
9123         }
9124     }
9125     ResetClocks();
9126     timeRemaining[0][0] = whiteTimeRemaining;
9127     timeRemaining[1][0] = blackTimeRemaining;
9128     if (first.pr == NULL) {
9129         StartChessProgram(&first);
9130     }
9131     if (init) {
9132             InitChessProgram(&first, startedFromSetupPosition);
9133     }
9134     DisplayTitle("");
9135     DisplayMessage("", "");
9136     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9137     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9138 }
9139
9140 void
9141 AutoPlayGameLoop()
9142 {
9143     for (;;) {
9144         if (!AutoPlayOneMove())
9145           return;
9146         if (matchMode || appData.timeDelay == 0)
9147           continue;
9148         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9149           return;
9150         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9151         break;
9152     }
9153 }
9154
9155
9156 int
9157 AutoPlayOneMove()
9158 {
9159     int fromX, fromY, toX, toY;
9160
9161     if (appData.debugMode) {
9162       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9163     }
9164
9165     if (gameMode != PlayFromGameFile)
9166       return FALSE;
9167
9168     if (currentMove >= forwardMostMove) {
9169       gameMode = EditGame;
9170       ModeHighlight();
9171
9172       /* [AS] Clear current move marker at the end of a game */
9173       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9174
9175       return FALSE;
9176     }
9177     
9178     toX = moveList[currentMove][2] - AAA;
9179     toY = moveList[currentMove][3] - ONE;
9180
9181     if (moveList[currentMove][1] == '@') {
9182         if (appData.highlightLastMove) {
9183             SetHighlights(-1, -1, toX, toY);
9184         }
9185     } else {
9186         fromX = moveList[currentMove][0] - AAA;
9187         fromY = moveList[currentMove][1] - ONE;
9188
9189         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9190
9191         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9192
9193         if (appData.highlightLastMove) {
9194             SetHighlights(fromX, fromY, toX, toY);
9195         }
9196     }
9197     DisplayMove(currentMove);
9198     SendMoveToProgram(currentMove++, &first);
9199     DisplayBothClocks();
9200     DrawPosition(FALSE, boards[currentMove]);
9201     // [HGM] PV info: always display, routine tests if empty
9202     DisplayComment(currentMove - 1, commentList[currentMove]);
9203     return TRUE;
9204 }
9205
9206
9207 int
9208 LoadGameOneMove(readAhead)
9209      ChessMove readAhead;
9210 {
9211     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9212     char promoChar = NULLCHAR;
9213     ChessMove moveType;
9214     char move[MSG_SIZ];
9215     char *p, *q;
9216     
9217     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9218         gameMode != AnalyzeMode && gameMode != Training) {
9219         gameFileFP = NULL;
9220         return FALSE;
9221     }
9222     
9223     yyboardindex = forwardMostMove;
9224     if (readAhead != (ChessMove)0) {
9225       moveType = readAhead;
9226     } else {
9227       if (gameFileFP == NULL)
9228           return FALSE;
9229       moveType = (ChessMove) yylex();
9230     }
9231     
9232     done = FALSE;
9233     switch (moveType) {
9234       case Comment:
9235         if (appData.debugMode) 
9236           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9237         p = yy_text;
9238
9239         /* append the comment but don't display it */
9240         AppendComment(currentMove, p, FALSE);
9241         return TRUE;
9242
9243       case WhiteCapturesEnPassant:
9244       case BlackCapturesEnPassant:
9245       case WhitePromotionChancellor:
9246       case BlackPromotionChancellor:
9247       case WhitePromotionArchbishop:
9248       case BlackPromotionArchbishop:
9249       case WhitePromotionCentaur:
9250       case BlackPromotionCentaur:
9251       case WhitePromotionQueen:
9252       case BlackPromotionQueen:
9253       case WhitePromotionRook:
9254       case BlackPromotionRook:
9255       case WhitePromotionBishop:
9256       case BlackPromotionBishop:
9257       case WhitePromotionKnight:
9258       case BlackPromotionKnight:
9259       case WhitePromotionKing:
9260       case BlackPromotionKing:
9261       case NormalMove:
9262       case WhiteKingSideCastle:
9263       case WhiteQueenSideCastle:
9264       case BlackKingSideCastle:
9265       case BlackQueenSideCastle:
9266       case WhiteKingSideCastleWild:
9267       case WhiteQueenSideCastleWild:
9268       case BlackKingSideCastleWild:
9269       case BlackQueenSideCastleWild:
9270       /* PUSH Fabien */
9271       case WhiteHSideCastleFR:
9272       case WhiteASideCastleFR:
9273       case BlackHSideCastleFR:
9274       case BlackASideCastleFR:
9275       /* POP Fabien */
9276         if (appData.debugMode)
9277           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9278         fromX = currentMoveString[0] - AAA;
9279         fromY = currentMoveString[1] - ONE;
9280         toX = currentMoveString[2] - AAA;
9281         toY = currentMoveString[3] - ONE;
9282         promoChar = currentMoveString[4];
9283         break;
9284
9285       case WhiteDrop:
9286       case BlackDrop:
9287         if (appData.debugMode)
9288           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9289         fromX = moveType == WhiteDrop ?
9290           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9291         (int) CharToPiece(ToLower(currentMoveString[0]));
9292         fromY = DROP_RANK;
9293         toX = currentMoveString[2] - AAA;
9294         toY = currentMoveString[3] - ONE;
9295         break;
9296
9297       case WhiteWins:
9298       case BlackWins:
9299       case GameIsDrawn:
9300       case GameUnfinished:
9301         if (appData.debugMode)
9302           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9303         p = strchr(yy_text, '{');
9304         if (p == NULL) p = strchr(yy_text, '(');
9305         if (p == NULL) {
9306             p = yy_text;
9307             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9308         } else {
9309             q = strchr(p, *p == '{' ? '}' : ')');
9310             if (q != NULL) *q = NULLCHAR;
9311             p++;
9312         }
9313         GameEnds(moveType, p, GE_FILE);
9314         done = TRUE;
9315         if (cmailMsgLoaded) {
9316             ClearHighlights();
9317             flipView = WhiteOnMove(currentMove);
9318             if (moveType == GameUnfinished) flipView = !flipView;
9319             if (appData.debugMode)
9320               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9321         }
9322         break;
9323
9324       case (ChessMove) 0:       /* end of file */
9325         if (appData.debugMode)
9326           fprintf(debugFP, "Parser hit end of file\n");
9327         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9328           case MT_NONE:
9329           case MT_CHECK:
9330             break;
9331           case MT_CHECKMATE:
9332           case MT_STAINMATE:
9333             if (WhiteOnMove(currentMove)) {
9334                 GameEnds(BlackWins, "Black mates", GE_FILE);
9335             } else {
9336                 GameEnds(WhiteWins, "White mates", GE_FILE);
9337             }
9338             break;
9339           case MT_STALEMATE:
9340             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9341             break;
9342         }
9343         done = TRUE;
9344         break;
9345
9346       case MoveNumberOne:
9347         if (lastLoadGameStart == GNUChessGame) {
9348             /* GNUChessGames have numbers, but they aren't move numbers */
9349             if (appData.debugMode)
9350               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9351                       yy_text, (int) moveType);
9352             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9353         }
9354         /* else fall thru */
9355
9356       case XBoardGame:
9357       case GNUChessGame:
9358       case PGNTag:
9359         /* Reached start of next game in file */
9360         if (appData.debugMode)
9361           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9362         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9363           case MT_NONE:
9364           case MT_CHECK:
9365             break;
9366           case MT_CHECKMATE:
9367           case MT_STAINMATE:
9368             if (WhiteOnMove(currentMove)) {
9369                 GameEnds(BlackWins, "Black mates", GE_FILE);
9370             } else {
9371                 GameEnds(WhiteWins, "White mates", GE_FILE);
9372             }
9373             break;
9374           case MT_STALEMATE:
9375             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9376             break;
9377         }
9378         done = TRUE;
9379         break;
9380
9381       case PositionDiagram:     /* should not happen; ignore */
9382       case ElapsedTime:         /* ignore */
9383       case NAG:                 /* ignore */
9384         if (appData.debugMode)
9385           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9386                   yy_text, (int) moveType);
9387         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9388
9389       case IllegalMove:
9390         if (appData.testLegality) {
9391             if (appData.debugMode)
9392               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9393             sprintf(move, _("Illegal move: %d.%s%s"),
9394                     (forwardMostMove / 2) + 1,
9395                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9396             DisplayError(move, 0);
9397             done = TRUE;
9398         } else {
9399             if (appData.debugMode)
9400               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9401                       yy_text, currentMoveString);
9402             fromX = currentMoveString[0] - AAA;
9403             fromY = currentMoveString[1] - ONE;
9404             toX = currentMoveString[2] - AAA;
9405             toY = currentMoveString[3] - ONE;
9406             promoChar = currentMoveString[4];
9407         }
9408         break;
9409
9410       case AmbiguousMove:
9411         if (appData.debugMode)
9412           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9413         sprintf(move, _("Ambiguous move: %d.%s%s"),
9414                 (forwardMostMove / 2) + 1,
9415                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9416         DisplayError(move, 0);
9417         done = TRUE;
9418         break;
9419
9420       default:
9421       case ImpossibleMove:
9422         if (appData.debugMode)
9423           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9424         sprintf(move, _("Illegal move: %d.%s%s"),
9425                 (forwardMostMove / 2) + 1,
9426                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9427         DisplayError(move, 0);
9428         done = TRUE;
9429         break;
9430     }
9431
9432     if (done) {
9433         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9434             DrawPosition(FALSE, boards[currentMove]);
9435             DisplayBothClocks();
9436             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9437               DisplayComment(currentMove - 1, commentList[currentMove]);
9438         }
9439         (void) StopLoadGameTimer();
9440         gameFileFP = NULL;
9441         cmailOldMove = forwardMostMove;
9442         return FALSE;
9443     } else {
9444         /* currentMoveString is set as a side-effect of yylex */
9445         strcat(currentMoveString, "\n");
9446         strcpy(moveList[forwardMostMove], currentMoveString);
9447         
9448         thinkOutput[0] = NULLCHAR;
9449         MakeMove(fromX, fromY, toX, toY, promoChar);
9450         currentMove = forwardMostMove;
9451         return TRUE;
9452     }
9453 }
9454
9455 /* Load the nth game from the given file */
9456 int
9457 LoadGameFromFile(filename, n, title, useList)
9458      char *filename;
9459      int n;
9460      char *title;
9461      /*Boolean*/ int useList;
9462 {
9463     FILE *f;
9464     char buf[MSG_SIZ];
9465
9466     if (strcmp(filename, "-") == 0) {
9467         f = stdin;
9468         title = "stdin";
9469     } else {
9470         f = fopen(filename, "rb");
9471         if (f == NULL) {
9472           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9473             DisplayError(buf, errno);
9474             return FALSE;
9475         }
9476     }
9477     if (fseek(f, 0, 0) == -1) {
9478         /* f is not seekable; probably a pipe */
9479         useList = FALSE;
9480     }
9481     if (useList && n == 0) {
9482         int error = GameListBuild(f);
9483         if (error) {
9484             DisplayError(_("Cannot build game list"), error);
9485         } else if (!ListEmpty(&gameList) &&
9486                    ((ListGame *) gameList.tailPred)->number > 1) {
9487             GameListPopUp(f, title);
9488             return TRUE;
9489         }
9490         GameListDestroy();
9491         n = 1;
9492     }
9493     if (n == 0) n = 1;
9494     return LoadGame(f, n, title, FALSE);
9495 }
9496
9497
9498 void
9499 MakeRegisteredMove()
9500 {
9501     int fromX, fromY, toX, toY;
9502     char promoChar;
9503     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9504         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9505           case CMAIL_MOVE:
9506           case CMAIL_DRAW:
9507             if (appData.debugMode)
9508               fprintf(debugFP, "Restoring %s for game %d\n",
9509                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9510     
9511             thinkOutput[0] = NULLCHAR;
9512             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9513             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9514             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9515             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9516             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9517             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9518             MakeMove(fromX, fromY, toX, toY, promoChar);
9519             ShowMove(fromX, fromY, toX, toY);
9520               
9521             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9522               case MT_NONE:
9523               case MT_CHECK:
9524                 break;
9525                 
9526               case MT_CHECKMATE:
9527               case MT_STAINMATE:
9528                 if (WhiteOnMove(currentMove)) {
9529                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9530                 } else {
9531                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9532                 }
9533                 break;
9534                 
9535               case MT_STALEMATE:
9536                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9537                 break;
9538             }
9539
9540             break;
9541             
9542           case CMAIL_RESIGN:
9543             if (WhiteOnMove(currentMove)) {
9544                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9545             } else {
9546                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9547             }
9548             break;
9549             
9550           case CMAIL_ACCEPT:
9551             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9552             break;
9553               
9554           default:
9555             break;
9556         }
9557     }
9558
9559     return;
9560 }
9561
9562 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9563 int
9564 CmailLoadGame(f, gameNumber, title, useList)
9565      FILE *f;
9566      int gameNumber;
9567      char *title;
9568      int useList;
9569 {
9570     int retVal;
9571
9572     if (gameNumber > nCmailGames) {
9573         DisplayError(_("No more games in this message"), 0);
9574         return FALSE;
9575     }
9576     if (f == lastLoadGameFP) {
9577         int offset = gameNumber - lastLoadGameNumber;
9578         if (offset == 0) {
9579             cmailMsg[0] = NULLCHAR;
9580             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9581                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9582                 nCmailMovesRegistered--;
9583             }
9584             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9585             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9586                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9587             }
9588         } else {
9589             if (! RegisterMove()) return FALSE;
9590         }
9591     }
9592
9593     retVal = LoadGame(f, gameNumber, title, useList);
9594
9595     /* Make move registered during previous look at this game, if any */
9596     MakeRegisteredMove();
9597
9598     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9599         commentList[currentMove]
9600           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9601         DisplayComment(currentMove - 1, commentList[currentMove]);
9602     }
9603
9604     return retVal;
9605 }
9606
9607 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9608 int
9609 ReloadGame(offset)
9610      int offset;
9611 {
9612     int gameNumber = lastLoadGameNumber + offset;
9613     if (lastLoadGameFP == NULL) {
9614         DisplayError(_("No game has been loaded yet"), 0);
9615         return FALSE;
9616     }
9617     if (gameNumber <= 0) {
9618         DisplayError(_("Can't back up any further"), 0);
9619         return FALSE;
9620     }
9621     if (cmailMsgLoaded) {
9622         return CmailLoadGame(lastLoadGameFP, gameNumber,
9623                              lastLoadGameTitle, lastLoadGameUseList);
9624     } else {
9625         return LoadGame(lastLoadGameFP, gameNumber,
9626                         lastLoadGameTitle, lastLoadGameUseList);
9627     }
9628 }
9629
9630
9631
9632 /* Load the nth game from open file f */
9633 int
9634 LoadGame(f, gameNumber, title, useList)
9635      FILE *f;
9636      int gameNumber;
9637      char *title;
9638      int useList;
9639 {
9640     ChessMove cm;
9641     char buf[MSG_SIZ];
9642     int gn = gameNumber;
9643     ListGame *lg = NULL;
9644     int numPGNTags = 0;
9645     int err;
9646     GameMode oldGameMode;
9647     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9648
9649     if (appData.debugMode) 
9650         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9651
9652     if (gameMode == Training )
9653         SetTrainingModeOff();
9654
9655     oldGameMode = gameMode;
9656     if (gameMode != BeginningOfGame) {
9657       Reset(FALSE, TRUE);
9658     }
9659
9660     gameFileFP = f;
9661     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9662         fclose(lastLoadGameFP);
9663     }
9664
9665     if (useList) {
9666         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9667         
9668         if (lg) {
9669             fseek(f, lg->offset, 0);
9670             GameListHighlight(gameNumber);
9671             gn = 1;
9672         }
9673         else {
9674             DisplayError(_("Game number out of range"), 0);
9675             return FALSE;
9676         }
9677     } else {
9678         GameListDestroy();
9679         if (fseek(f, 0, 0) == -1) {
9680             if (f == lastLoadGameFP ?
9681                 gameNumber == lastLoadGameNumber + 1 :
9682                 gameNumber == 1) {
9683                 gn = 1;
9684             } else {
9685                 DisplayError(_("Can't seek on game file"), 0);
9686                 return FALSE;
9687             }
9688         }
9689     }
9690     lastLoadGameFP = f;
9691     lastLoadGameNumber = gameNumber;
9692     strcpy(lastLoadGameTitle, title);
9693     lastLoadGameUseList = useList;
9694
9695     yynewfile(f);
9696
9697     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9698       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9699                 lg->gameInfo.black);
9700             DisplayTitle(buf);
9701     } else if (*title != NULLCHAR) {
9702         if (gameNumber > 1) {
9703             sprintf(buf, "%s %d", title, gameNumber);
9704             DisplayTitle(buf);
9705         } else {
9706             DisplayTitle(title);
9707         }
9708     }
9709
9710     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9711         gameMode = PlayFromGameFile;
9712         ModeHighlight();
9713     }
9714
9715     currentMove = forwardMostMove = backwardMostMove = 0;
9716     CopyBoard(boards[0], initialPosition);
9717     StopClocks();
9718
9719     /*
9720      * Skip the first gn-1 games in the file.
9721      * Also skip over anything that precedes an identifiable 
9722      * start of game marker, to avoid being confused by 
9723      * garbage at the start of the file.  Currently 
9724      * recognized start of game markers are the move number "1",
9725      * the pattern "gnuchess .* game", the pattern
9726      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9727      * A game that starts with one of the latter two patterns
9728      * will also have a move number 1, possibly
9729      * following a position diagram.
9730      * 5-4-02: Let's try being more lenient and allowing a game to
9731      * start with an unnumbered move.  Does that break anything?
9732      */
9733     cm = lastLoadGameStart = (ChessMove) 0;
9734     while (gn > 0) {
9735         yyboardindex = forwardMostMove;
9736         cm = (ChessMove) yylex();
9737         switch (cm) {
9738           case (ChessMove) 0:
9739             if (cmailMsgLoaded) {
9740                 nCmailGames = CMAIL_MAX_GAMES - gn;
9741             } else {
9742                 Reset(TRUE, TRUE);
9743                 DisplayError(_("Game not found in file"), 0);
9744             }
9745             return FALSE;
9746
9747           case GNUChessGame:
9748           case XBoardGame:
9749             gn--;
9750             lastLoadGameStart = cm;
9751             break;
9752             
9753           case MoveNumberOne:
9754             switch (lastLoadGameStart) {
9755               case GNUChessGame:
9756               case XBoardGame:
9757               case PGNTag:
9758                 break;
9759               case MoveNumberOne:
9760               case (ChessMove) 0:
9761                 gn--;           /* count this game */
9762                 lastLoadGameStart = cm;
9763                 break;
9764               default:
9765                 /* impossible */
9766                 break;
9767             }
9768             break;
9769
9770           case PGNTag:
9771             switch (lastLoadGameStart) {
9772               case GNUChessGame:
9773               case PGNTag:
9774               case MoveNumberOne:
9775               case (ChessMove) 0:
9776                 gn--;           /* count this game */
9777                 lastLoadGameStart = cm;
9778                 break;
9779               case XBoardGame:
9780                 lastLoadGameStart = cm; /* game counted already */
9781                 break;
9782               default:
9783                 /* impossible */
9784                 break;
9785             }
9786             if (gn > 0) {
9787                 do {
9788                     yyboardindex = forwardMostMove;
9789                     cm = (ChessMove) yylex();
9790                 } while (cm == PGNTag || cm == Comment);
9791             }
9792             break;
9793
9794           case WhiteWins:
9795           case BlackWins:
9796           case GameIsDrawn:
9797             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9798                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9799                     != CMAIL_OLD_RESULT) {
9800                     nCmailResults ++ ;
9801                     cmailResult[  CMAIL_MAX_GAMES
9802                                 - gn - 1] = CMAIL_OLD_RESULT;
9803                 }
9804             }
9805             break;
9806
9807           case NormalMove:
9808             /* Only a NormalMove can be at the start of a game
9809              * without a position diagram. */
9810             if (lastLoadGameStart == (ChessMove) 0) {
9811               gn--;
9812               lastLoadGameStart = MoveNumberOne;
9813             }
9814             break;
9815
9816           default:
9817             break;
9818         }
9819     }
9820     
9821     if (appData.debugMode)
9822       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9823
9824     if (cm == XBoardGame) {
9825         /* Skip any header junk before position diagram and/or move 1 */
9826         for (;;) {
9827             yyboardindex = forwardMostMove;
9828             cm = (ChessMove) yylex();
9829
9830             if (cm == (ChessMove) 0 ||
9831                 cm == GNUChessGame || cm == XBoardGame) {
9832                 /* Empty game; pretend end-of-file and handle later */
9833                 cm = (ChessMove) 0;
9834                 break;
9835             }
9836
9837             if (cm == MoveNumberOne || cm == PositionDiagram ||
9838                 cm == PGNTag || cm == Comment)
9839               break;
9840         }
9841     } else if (cm == GNUChessGame) {
9842         if (gameInfo.event != NULL) {
9843             free(gameInfo.event);
9844         }
9845         gameInfo.event = StrSave(yy_text);
9846     }   
9847
9848     startedFromSetupPosition = FALSE;
9849     while (cm == PGNTag) {
9850         if (appData.debugMode) 
9851           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9852         err = ParsePGNTag(yy_text, &gameInfo);
9853         if (!err) numPGNTags++;
9854
9855         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9856         if(gameInfo.variant != oldVariant) {
9857             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9858             InitPosition(TRUE);
9859             oldVariant = gameInfo.variant;
9860             if (appData.debugMode) 
9861               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9862         }
9863
9864
9865         if (gameInfo.fen != NULL) {
9866           Board initial_position;
9867           startedFromSetupPosition = TRUE;
9868           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9869             Reset(TRUE, TRUE);
9870             DisplayError(_("Bad FEN position in file"), 0);
9871             return FALSE;
9872           }
9873           CopyBoard(boards[0], initial_position);
9874           if (blackPlaysFirst) {
9875             currentMove = forwardMostMove = backwardMostMove = 1;
9876             CopyBoard(boards[1], initial_position);
9877             strcpy(moveList[0], "");
9878             strcpy(parseList[0], "");
9879             timeRemaining[0][1] = whiteTimeRemaining;
9880             timeRemaining[1][1] = blackTimeRemaining;
9881             if (commentList[0] != NULL) {
9882               commentList[1] = commentList[0];
9883               commentList[0] = NULL;
9884             }
9885           } else {
9886             currentMove = forwardMostMove = backwardMostMove = 0;
9887           }
9888           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9889           {   int i;
9890               initialRulePlies = FENrulePlies;
9891               for( i=0; i< nrCastlingRights; i++ )
9892                   initialRights[i] = initial_position[CASTLING][i];
9893           }
9894           yyboardindex = forwardMostMove;
9895           free(gameInfo.fen);
9896           gameInfo.fen = NULL;
9897         }
9898
9899         yyboardindex = forwardMostMove;
9900         cm = (ChessMove) yylex();
9901
9902         /* Handle comments interspersed among the tags */
9903         while (cm == Comment) {
9904             char *p;
9905             if (appData.debugMode) 
9906               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9907             p = yy_text;
9908             AppendComment(currentMove, p, FALSE);
9909             yyboardindex = forwardMostMove;
9910             cm = (ChessMove) yylex();
9911         }
9912     }
9913
9914     /* don't rely on existence of Event tag since if game was
9915      * pasted from clipboard the Event tag may not exist
9916      */
9917     if (numPGNTags > 0){
9918         char *tags;
9919         if (gameInfo.variant == VariantNormal) {
9920           gameInfo.variant = StringToVariant(gameInfo.event);
9921         }
9922         if (!matchMode) {
9923           if( appData.autoDisplayTags ) {
9924             tags = PGNTags(&gameInfo);
9925             TagsPopUp(tags, CmailMsg());
9926             free(tags);
9927           }
9928         }
9929     } else {
9930         /* Make something up, but don't display it now */
9931         SetGameInfo();
9932         TagsPopDown();
9933     }
9934
9935     if (cm == PositionDiagram) {
9936         int i, j;
9937         char *p;
9938         Board initial_position;
9939
9940         if (appData.debugMode)
9941           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9942
9943         if (!startedFromSetupPosition) {
9944             p = yy_text;
9945             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9946               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9947                 switch (*p) {
9948                   case '[':
9949                   case '-':
9950                   case ' ':
9951                   case '\t':
9952                   case '\n':
9953                   case '\r':
9954                     break;
9955                   default:
9956                     initial_position[i][j++] = CharToPiece(*p);
9957                     break;
9958                 }
9959             while (*p == ' ' || *p == '\t' ||
9960                    *p == '\n' || *p == '\r') p++;
9961         
9962             if (strncmp(p, "black", strlen("black"))==0)
9963               blackPlaysFirst = TRUE;
9964             else
9965               blackPlaysFirst = FALSE;
9966             startedFromSetupPosition = TRUE;
9967         
9968             CopyBoard(boards[0], initial_position);
9969             if (blackPlaysFirst) {
9970                 currentMove = forwardMostMove = backwardMostMove = 1;
9971                 CopyBoard(boards[1], initial_position);
9972                 strcpy(moveList[0], "");
9973                 strcpy(parseList[0], "");
9974                 timeRemaining[0][1] = whiteTimeRemaining;
9975                 timeRemaining[1][1] = blackTimeRemaining;
9976                 if (commentList[0] != NULL) {
9977                     commentList[1] = commentList[0];
9978                     commentList[0] = NULL;
9979                 }
9980             } else {
9981                 currentMove = forwardMostMove = backwardMostMove = 0;
9982             }
9983         }
9984         yyboardindex = forwardMostMove;
9985         cm = (ChessMove) yylex();
9986     }
9987
9988     if (first.pr == NoProc) {
9989         StartChessProgram(&first);
9990     }
9991     InitChessProgram(&first, FALSE);
9992     SendToProgram("force\n", &first);
9993     if (startedFromSetupPosition) {
9994         SendBoard(&first, forwardMostMove);
9995     if (appData.debugMode) {
9996         fprintf(debugFP, "Load Game\n");
9997     }
9998         DisplayBothClocks();
9999     }      
10000
10001     /* [HGM] server: flag to write setup moves in broadcast file as one */
10002     loadFlag = appData.suppressLoadMoves;
10003
10004     while (cm == Comment) {
10005         char *p;
10006         if (appData.debugMode) 
10007           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10008         p = yy_text;
10009         AppendComment(currentMove, p, FALSE);
10010         yyboardindex = forwardMostMove;
10011         cm = (ChessMove) yylex();
10012     }
10013
10014     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10015         cm == WhiteWins || cm == BlackWins ||
10016         cm == GameIsDrawn || cm == GameUnfinished) {
10017         DisplayMessage("", _("No moves in game"));
10018         if (cmailMsgLoaded) {
10019             if (appData.debugMode)
10020               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10021             ClearHighlights();
10022             flipView = FALSE;
10023         }
10024         DrawPosition(FALSE, boards[currentMove]);
10025         DisplayBothClocks();
10026         gameMode = EditGame;
10027         ModeHighlight();
10028         gameFileFP = NULL;
10029         cmailOldMove = 0;
10030         return TRUE;
10031     }
10032
10033     // [HGM] PV info: routine tests if comment empty
10034     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10035         DisplayComment(currentMove - 1, commentList[currentMove]);
10036     }
10037     if (!matchMode && appData.timeDelay != 0) 
10038       DrawPosition(FALSE, boards[currentMove]);
10039
10040     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10041       programStats.ok_to_send = 1;
10042     }
10043
10044     /* if the first token after the PGN tags is a move
10045      * and not move number 1, retrieve it from the parser 
10046      */
10047     if (cm != MoveNumberOne)
10048         LoadGameOneMove(cm);
10049
10050     /* load the remaining moves from the file */
10051     while (LoadGameOneMove((ChessMove)0)) {
10052       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10053       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10054     }
10055
10056     /* rewind to the start of the game */
10057     currentMove = backwardMostMove;
10058
10059     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10060
10061     if (oldGameMode == AnalyzeFile ||
10062         oldGameMode == AnalyzeMode) {
10063       AnalyzeFileEvent();
10064     }
10065
10066     if (matchMode || appData.timeDelay == 0) {
10067       ToEndEvent();
10068       gameMode = EditGame;
10069       ModeHighlight();
10070     } else if (appData.timeDelay > 0) {
10071       AutoPlayGameLoop();
10072     }
10073
10074     if (appData.debugMode) 
10075         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10076
10077     loadFlag = 0; /* [HGM] true game starts */
10078     return TRUE;
10079 }
10080
10081 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10082 int
10083 ReloadPosition(offset)
10084      int offset;
10085 {
10086     int positionNumber = lastLoadPositionNumber + offset;
10087     if (lastLoadPositionFP == NULL) {
10088         DisplayError(_("No position has been loaded yet"), 0);
10089         return FALSE;
10090     }
10091     if (positionNumber <= 0) {
10092         DisplayError(_("Can't back up any further"), 0);
10093         return FALSE;
10094     }
10095     return LoadPosition(lastLoadPositionFP, positionNumber,
10096                         lastLoadPositionTitle);
10097 }
10098
10099 /* Load the nth position from the given file */
10100 int
10101 LoadPositionFromFile(filename, n, title)
10102      char *filename;
10103      int n;
10104      char *title;
10105 {
10106     FILE *f;
10107     char buf[MSG_SIZ];
10108
10109     if (strcmp(filename, "-") == 0) {
10110         return LoadPosition(stdin, n, "stdin");
10111     } else {
10112         f = fopen(filename, "rb");
10113         if (f == NULL) {
10114             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10115             DisplayError(buf, errno);
10116             return FALSE;
10117         } else {
10118             return LoadPosition(f, n, title);
10119         }
10120     }
10121 }
10122
10123 /* Load the nth position from the given open file, and close it */
10124 int
10125 LoadPosition(f, positionNumber, title)
10126      FILE *f;
10127      int positionNumber;
10128      char *title;
10129 {
10130     char *p, line[MSG_SIZ];
10131     Board initial_position;
10132     int i, j, fenMode, pn;
10133     
10134     if (gameMode == Training )
10135         SetTrainingModeOff();
10136
10137     if (gameMode != BeginningOfGame) {
10138         Reset(FALSE, TRUE);
10139     }
10140     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10141         fclose(lastLoadPositionFP);
10142     }
10143     if (positionNumber == 0) positionNumber = 1;
10144     lastLoadPositionFP = f;
10145     lastLoadPositionNumber = positionNumber;
10146     strcpy(lastLoadPositionTitle, title);
10147     if (first.pr == NoProc) {
10148       StartChessProgram(&first);
10149       InitChessProgram(&first, FALSE);
10150     }    
10151     pn = positionNumber;
10152     if (positionNumber < 0) {
10153         /* Negative position number means to seek to that byte offset */
10154         if (fseek(f, -positionNumber, 0) == -1) {
10155             DisplayError(_("Can't seek on position file"), 0);
10156             return FALSE;
10157         };
10158         pn = 1;
10159     } else {
10160         if (fseek(f, 0, 0) == -1) {
10161             if (f == lastLoadPositionFP ?
10162                 positionNumber == lastLoadPositionNumber + 1 :
10163                 positionNumber == 1) {
10164                 pn = 1;
10165             } else {
10166                 DisplayError(_("Can't seek on position file"), 0);
10167                 return FALSE;
10168             }
10169         }
10170     }
10171     /* See if this file is FEN or old-style xboard */
10172     if (fgets(line, MSG_SIZ, f) == NULL) {
10173         DisplayError(_("Position not found in file"), 0);
10174         return FALSE;
10175     }
10176     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10177     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10178
10179     if (pn >= 2) {
10180         if (fenMode || line[0] == '#') pn--;
10181         while (pn > 0) {
10182             /* skip positions before number pn */
10183             if (fgets(line, MSG_SIZ, f) == NULL) {
10184                 Reset(TRUE, TRUE);
10185                 DisplayError(_("Position not found in file"), 0);
10186                 return FALSE;
10187             }
10188             if (fenMode || line[0] == '#') pn--;
10189         }
10190     }
10191
10192     if (fenMode) {
10193         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10194             DisplayError(_("Bad FEN position in file"), 0);
10195             return FALSE;
10196         }
10197     } else {
10198         (void) fgets(line, MSG_SIZ, f);
10199         (void) fgets(line, MSG_SIZ, f);
10200     
10201         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10202             (void) fgets(line, MSG_SIZ, f);
10203             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10204                 if (*p == ' ')
10205                   continue;
10206                 initial_position[i][j++] = CharToPiece(*p);
10207             }
10208         }
10209     
10210         blackPlaysFirst = FALSE;
10211         if (!feof(f)) {
10212             (void) fgets(line, MSG_SIZ, f);
10213             if (strncmp(line, "black", strlen("black"))==0)
10214               blackPlaysFirst = TRUE;
10215         }
10216     }
10217     startedFromSetupPosition = TRUE;
10218     
10219     SendToProgram("force\n", &first);
10220     CopyBoard(boards[0], initial_position);
10221     if (blackPlaysFirst) {
10222         currentMove = forwardMostMove = backwardMostMove = 1;
10223         strcpy(moveList[0], "");
10224         strcpy(parseList[0], "");
10225         CopyBoard(boards[1], initial_position);
10226         DisplayMessage("", _("Black to play"));
10227     } else {
10228         currentMove = forwardMostMove = backwardMostMove = 0;
10229         DisplayMessage("", _("White to play"));
10230     }
10231     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10232     SendBoard(&first, forwardMostMove);
10233     if (appData.debugMode) {
10234 int i, j;
10235   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10236   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10237         fprintf(debugFP, "Load Position\n");
10238     }
10239
10240     if (positionNumber > 1) {
10241         sprintf(line, "%s %d", title, positionNumber);
10242         DisplayTitle(line);
10243     } else {
10244         DisplayTitle(title);
10245     }
10246     gameMode = EditGame;
10247     ModeHighlight();
10248     ResetClocks();
10249     timeRemaining[0][1] = whiteTimeRemaining;
10250     timeRemaining[1][1] = blackTimeRemaining;
10251     DrawPosition(FALSE, boards[currentMove]);
10252    
10253     return TRUE;
10254 }
10255
10256
10257 void
10258 CopyPlayerNameIntoFileName(dest, src)
10259      char **dest, *src;
10260 {
10261     while (*src != NULLCHAR && *src != ',') {
10262         if (*src == ' ') {
10263             *(*dest)++ = '_';
10264             src++;
10265         } else {
10266             *(*dest)++ = *src++;
10267         }
10268     }
10269 }
10270
10271 char *DefaultFileName(ext)
10272      char *ext;
10273 {
10274     static char def[MSG_SIZ];
10275     char *p;
10276
10277     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10278         p = def;
10279         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10280         *p++ = '-';
10281         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10282         *p++ = '.';
10283         strcpy(p, ext);
10284     } else {
10285         def[0] = NULLCHAR;
10286     }
10287     return def;
10288 }
10289
10290 /* Save the current game to the given file */
10291 int
10292 SaveGameToFile(filename, append)
10293      char *filename;
10294      int append;
10295 {
10296     FILE *f;
10297     char buf[MSG_SIZ];
10298
10299     if (strcmp(filename, "-") == 0) {
10300         return SaveGame(stdout, 0, NULL);
10301     } else {
10302         f = fopen(filename, append ? "a" : "w");
10303         if (f == NULL) {
10304             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10305             DisplayError(buf, errno);
10306             return FALSE;
10307         } else {
10308             return SaveGame(f, 0, NULL);
10309         }
10310     }
10311 }
10312
10313 char *
10314 SavePart(str)
10315      char *str;
10316 {
10317     static char buf[MSG_SIZ];
10318     char *p;
10319     
10320     p = strchr(str, ' ');
10321     if (p == NULL) return str;
10322     strncpy(buf, str, p - str);
10323     buf[p - str] = NULLCHAR;
10324     return buf;
10325 }
10326
10327 #define PGN_MAX_LINE 75
10328
10329 #define PGN_SIDE_WHITE  0
10330 #define PGN_SIDE_BLACK  1
10331
10332 /* [AS] */
10333 static int FindFirstMoveOutOfBook( int side )
10334 {
10335     int result = -1;
10336
10337     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10338         int index = backwardMostMove;
10339         int has_book_hit = 0;
10340
10341         if( (index % 2) != side ) {
10342             index++;
10343         }
10344
10345         while( index < forwardMostMove ) {
10346             /* Check to see if engine is in book */
10347             int depth = pvInfoList[index].depth;
10348             int score = pvInfoList[index].score;
10349             int in_book = 0;
10350
10351             if( depth <= 2 ) {
10352                 in_book = 1;
10353             }
10354             else if( score == 0 && depth == 63 ) {
10355                 in_book = 1; /* Zappa */
10356             }
10357             else if( score == 2 && depth == 99 ) {
10358                 in_book = 1; /* Abrok */
10359             }
10360
10361             has_book_hit += in_book;
10362
10363             if( ! in_book ) {
10364                 result = index;
10365
10366                 break;
10367             }
10368
10369             index += 2;
10370         }
10371     }
10372
10373     return result;
10374 }
10375
10376 /* [AS] */
10377 void GetOutOfBookInfo( char * buf )
10378 {
10379     int oob[2];
10380     int i;
10381     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10382
10383     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10384     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10385
10386     *buf = '\0';
10387
10388     if( oob[0] >= 0 || oob[1] >= 0 ) {
10389         for( i=0; i<2; i++ ) {
10390             int idx = oob[i];
10391
10392             if( idx >= 0 ) {
10393                 if( i > 0 && oob[0] >= 0 ) {
10394                     strcat( buf, "   " );
10395                 }
10396
10397                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10398                 sprintf( buf+strlen(buf), "%s%.2f", 
10399                     pvInfoList[idx].score >= 0 ? "+" : "",
10400                     pvInfoList[idx].score / 100.0 );
10401             }
10402         }
10403     }
10404 }
10405
10406 /* Save game in PGN style and close the file */
10407 int
10408 SaveGamePGN(f)
10409      FILE *f;
10410 {
10411     int i, offset, linelen, newblock;
10412     time_t tm;
10413 //    char *movetext;
10414     char numtext[32];
10415     int movelen, numlen, blank;
10416     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10417
10418     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10419     
10420     tm = time((time_t *) NULL);
10421     
10422     PrintPGNTags(f, &gameInfo);
10423     
10424     if (backwardMostMove > 0 || startedFromSetupPosition) {
10425         char *fen = PositionToFEN(backwardMostMove, NULL);
10426         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10427         fprintf(f, "\n{--------------\n");
10428         PrintPosition(f, backwardMostMove);
10429         fprintf(f, "--------------}\n");
10430         free(fen);
10431     }
10432     else {
10433         /* [AS] Out of book annotation */
10434         if( appData.saveOutOfBookInfo ) {
10435             char buf[64];
10436
10437             GetOutOfBookInfo( buf );
10438
10439             if( buf[0] != '\0' ) {
10440                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10441             }
10442         }
10443
10444         fprintf(f, "\n");
10445     }
10446
10447     i = backwardMostMove;
10448     linelen = 0;
10449     newblock = TRUE;
10450
10451     while (i < forwardMostMove) {
10452         /* Print comments preceding this move */
10453         if (commentList[i] != NULL) {
10454             if (linelen > 0) fprintf(f, "\n");
10455             fprintf(f, "%s", commentList[i]);
10456             linelen = 0;
10457             newblock = TRUE;
10458         }
10459
10460         /* Format move number */
10461         if ((i % 2) == 0) {
10462             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10463         } else {
10464             if (newblock) {
10465                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10466             } else {
10467                 numtext[0] = NULLCHAR;
10468             }
10469         }
10470         numlen = strlen(numtext);
10471         newblock = FALSE;
10472
10473         /* Print move number */
10474         blank = linelen > 0 && numlen > 0;
10475         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10476             fprintf(f, "\n");
10477             linelen = 0;
10478             blank = 0;
10479         }
10480         if (blank) {
10481             fprintf(f, " ");
10482             linelen++;
10483         }
10484         fprintf(f, "%s", numtext);
10485         linelen += numlen;
10486
10487         /* Get move */
10488         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10489         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10490
10491         /* Print move */
10492         blank = linelen > 0 && movelen > 0;
10493         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10494             fprintf(f, "\n");
10495             linelen = 0;
10496             blank = 0;
10497         }
10498         if (blank) {
10499             fprintf(f, " ");
10500             linelen++;
10501         }
10502         fprintf(f, "%s", move_buffer);
10503         linelen += movelen;
10504
10505         /* [AS] Add PV info if present */
10506         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10507             /* [HGM] add time */
10508             char buf[MSG_SIZ]; int seconds;
10509
10510             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10511
10512             if( seconds <= 0) buf[0] = 0; else
10513             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10514                 seconds = (seconds + 4)/10; // round to full seconds
10515                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10516                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10517             }
10518
10519             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10520                 pvInfoList[i].score >= 0 ? "+" : "",
10521                 pvInfoList[i].score / 100.0,
10522                 pvInfoList[i].depth,
10523                 buf );
10524
10525             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10526
10527             /* Print score/depth */
10528             blank = linelen > 0 && movelen > 0;
10529             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10530                 fprintf(f, "\n");
10531                 linelen = 0;
10532                 blank = 0;
10533             }
10534             if (blank) {
10535                 fprintf(f, " ");
10536                 linelen++;
10537             }
10538             fprintf(f, "%s", move_buffer);
10539             linelen += movelen;
10540         }
10541
10542         i++;
10543     }
10544     
10545     /* Start a new line */
10546     if (linelen > 0) fprintf(f, "\n");
10547
10548     /* Print comments after last move */
10549     if (commentList[i] != NULL) {
10550         fprintf(f, "%s\n", commentList[i]);
10551     }
10552
10553     /* Print result */
10554     if (gameInfo.resultDetails != NULL &&
10555         gameInfo.resultDetails[0] != NULLCHAR) {
10556         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10557                 PGNResult(gameInfo.result));
10558     } else {
10559         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10560     }
10561
10562     fclose(f);
10563     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10564     return TRUE;
10565 }
10566
10567 /* Save game in old style and close the file */
10568 int
10569 SaveGameOldStyle(f)
10570      FILE *f;
10571 {
10572     int i, offset;
10573     time_t tm;
10574     
10575     tm = time((time_t *) NULL);
10576     
10577     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10578     PrintOpponents(f);
10579     
10580     if (backwardMostMove > 0 || startedFromSetupPosition) {
10581         fprintf(f, "\n[--------------\n");
10582         PrintPosition(f, backwardMostMove);
10583         fprintf(f, "--------------]\n");
10584     } else {
10585         fprintf(f, "\n");
10586     }
10587
10588     i = backwardMostMove;
10589     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10590
10591     while (i < forwardMostMove) {
10592         if (commentList[i] != NULL) {
10593             fprintf(f, "[%s]\n", commentList[i]);
10594         }
10595
10596         if ((i % 2) == 1) {
10597             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10598             i++;
10599         } else {
10600             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10601             i++;
10602             if (commentList[i] != NULL) {
10603                 fprintf(f, "\n");
10604                 continue;
10605             }
10606             if (i >= forwardMostMove) {
10607                 fprintf(f, "\n");
10608                 break;
10609             }
10610             fprintf(f, "%s\n", parseList[i]);
10611             i++;
10612         }
10613     }
10614     
10615     if (commentList[i] != NULL) {
10616         fprintf(f, "[%s]\n", commentList[i]);
10617     }
10618
10619     /* This isn't really the old style, but it's close enough */
10620     if (gameInfo.resultDetails != NULL &&
10621         gameInfo.resultDetails[0] != NULLCHAR) {
10622         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10623                 gameInfo.resultDetails);
10624     } else {
10625         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10626     }
10627
10628     fclose(f);
10629     return TRUE;
10630 }
10631
10632 /* Save the current game to open file f and close the file */
10633 int
10634 SaveGame(f, dummy, dummy2)
10635      FILE *f;
10636      int dummy;
10637      char *dummy2;
10638 {
10639     if (gameMode == EditPosition) EditPositionDone(TRUE);
10640     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10641     if (appData.oldSaveStyle)
10642       return SaveGameOldStyle(f);
10643     else
10644       return SaveGamePGN(f);
10645 }
10646
10647 /* Save the current position to the given file */
10648 int
10649 SavePositionToFile(filename)
10650      char *filename;
10651 {
10652     FILE *f;
10653     char buf[MSG_SIZ];
10654
10655     if (strcmp(filename, "-") == 0) {
10656         return SavePosition(stdout, 0, NULL);
10657     } else {
10658         f = fopen(filename, "a");
10659         if (f == NULL) {
10660             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10661             DisplayError(buf, errno);
10662             return FALSE;
10663         } else {
10664             SavePosition(f, 0, NULL);
10665             return TRUE;
10666         }
10667     }
10668 }
10669
10670 /* Save the current position to the given open file and close the file */
10671 int
10672 SavePosition(f, dummy, dummy2)
10673      FILE *f;
10674      int dummy;
10675      char *dummy2;
10676 {
10677     time_t tm;
10678     char *fen;
10679     
10680     if (gameMode == EditPosition) EditPositionDone(TRUE);
10681     if (appData.oldSaveStyle) {
10682         tm = time((time_t *) NULL);
10683     
10684         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10685         PrintOpponents(f);
10686         fprintf(f, "[--------------\n");
10687         PrintPosition(f, currentMove);
10688         fprintf(f, "--------------]\n");
10689     } else {
10690         fen = PositionToFEN(currentMove, NULL);
10691         fprintf(f, "%s\n", fen);
10692         free(fen);
10693     }
10694     fclose(f);
10695     return TRUE;
10696 }
10697
10698 void
10699 ReloadCmailMsgEvent(unregister)
10700      int unregister;
10701 {
10702 #if !WIN32
10703     static char *inFilename = NULL;
10704     static char *outFilename;
10705     int i;
10706     struct stat inbuf, outbuf;
10707     int status;
10708     
10709     /* Any registered moves are unregistered if unregister is set, */
10710     /* i.e. invoked by the signal handler */
10711     if (unregister) {
10712         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10713             cmailMoveRegistered[i] = FALSE;
10714             if (cmailCommentList[i] != NULL) {
10715                 free(cmailCommentList[i]);
10716                 cmailCommentList[i] = NULL;
10717             }
10718         }
10719         nCmailMovesRegistered = 0;
10720     }
10721
10722     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10723         cmailResult[i] = CMAIL_NOT_RESULT;
10724     }
10725     nCmailResults = 0;
10726
10727     if (inFilename == NULL) {
10728         /* Because the filenames are static they only get malloced once  */
10729         /* and they never get freed                                      */
10730         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10731         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10732
10733         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10734         sprintf(outFilename, "%s.out", appData.cmailGameName);
10735     }
10736     
10737     status = stat(outFilename, &outbuf);
10738     if (status < 0) {
10739         cmailMailedMove = FALSE;
10740     } else {
10741         status = stat(inFilename, &inbuf);
10742         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10743     }
10744     
10745     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10746        counts the games, notes how each one terminated, etc.
10747        
10748        It would be nice to remove this kludge and instead gather all
10749        the information while building the game list.  (And to keep it
10750        in the game list nodes instead of having a bunch of fixed-size
10751        parallel arrays.)  Note this will require getting each game's
10752        termination from the PGN tags, as the game list builder does
10753        not process the game moves.  --mann
10754        */
10755     cmailMsgLoaded = TRUE;
10756     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10757     
10758     /* Load first game in the file or popup game menu */
10759     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10760
10761 #endif /* !WIN32 */
10762     return;
10763 }
10764
10765 int
10766 RegisterMove()
10767 {
10768     FILE *f;
10769     char string[MSG_SIZ];
10770
10771     if (   cmailMailedMove
10772         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10773         return TRUE;            /* Allow free viewing  */
10774     }
10775
10776     /* Unregister move to ensure that we don't leave RegisterMove        */
10777     /* with the move registered when the conditions for registering no   */
10778     /* longer hold                                                       */
10779     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10780         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10781         nCmailMovesRegistered --;
10782
10783         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10784           {
10785               free(cmailCommentList[lastLoadGameNumber - 1]);
10786               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10787           }
10788     }
10789
10790     if (cmailOldMove == -1) {
10791         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10792         return FALSE;
10793     }
10794
10795     if (currentMove > cmailOldMove + 1) {
10796         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10797         return FALSE;
10798     }
10799
10800     if (currentMove < cmailOldMove) {
10801         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10802         return FALSE;
10803     }
10804
10805     if (forwardMostMove > currentMove) {
10806         /* Silently truncate extra moves */
10807         TruncateGame();
10808     }
10809
10810     if (   (currentMove == cmailOldMove + 1)
10811         || (   (currentMove == cmailOldMove)
10812             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10813                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10814         if (gameInfo.result != GameUnfinished) {
10815             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10816         }
10817
10818         if (commentList[currentMove] != NULL) {
10819             cmailCommentList[lastLoadGameNumber - 1]
10820               = StrSave(commentList[currentMove]);
10821         }
10822         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10823
10824         if (appData.debugMode)
10825           fprintf(debugFP, "Saving %s for game %d\n",
10826                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10827
10828         sprintf(string,
10829                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10830         
10831         f = fopen(string, "w");
10832         if (appData.oldSaveStyle) {
10833             SaveGameOldStyle(f); /* also closes the file */
10834             
10835             sprintf(string, "%s.pos.out", appData.cmailGameName);
10836             f = fopen(string, "w");
10837             SavePosition(f, 0, NULL); /* also closes the file */
10838         } else {
10839             fprintf(f, "{--------------\n");
10840             PrintPosition(f, currentMove);
10841             fprintf(f, "--------------}\n\n");
10842             
10843             SaveGame(f, 0, NULL); /* also closes the file*/
10844         }
10845         
10846         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10847         nCmailMovesRegistered ++;
10848     } else if (nCmailGames == 1) {
10849         DisplayError(_("You have not made a move yet"), 0);
10850         return FALSE;
10851     }
10852
10853     return TRUE;
10854 }
10855
10856 void
10857 MailMoveEvent()
10858 {
10859 #if !WIN32
10860     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10861     FILE *commandOutput;
10862     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10863     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10864     int nBuffers;
10865     int i;
10866     int archived;
10867     char *arcDir;
10868
10869     if (! cmailMsgLoaded) {
10870         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10871         return;
10872     }
10873
10874     if (nCmailGames == nCmailResults) {
10875         DisplayError(_("No unfinished games"), 0);
10876         return;
10877     }
10878
10879 #if CMAIL_PROHIBIT_REMAIL
10880     if (cmailMailedMove) {
10881         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);
10882         DisplayError(msg, 0);
10883         return;
10884     }
10885 #endif
10886
10887     if (! (cmailMailedMove || RegisterMove())) return;
10888     
10889     if (   cmailMailedMove
10890         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10891         sprintf(string, partCommandString,
10892                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10893         commandOutput = popen(string, "r");
10894
10895         if (commandOutput == NULL) {
10896             DisplayError(_("Failed to invoke cmail"), 0);
10897         } else {
10898             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10899                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10900             }
10901             if (nBuffers > 1) {
10902                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10903                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10904                 nBytes = MSG_SIZ - 1;
10905             } else {
10906                 (void) memcpy(msg, buffer, nBytes);
10907             }
10908             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10909
10910             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10911                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10912
10913                 archived = TRUE;
10914                 for (i = 0; i < nCmailGames; i ++) {
10915                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10916                         archived = FALSE;
10917                     }
10918                 }
10919                 if (   archived
10920                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10921                         != NULL)) {
10922                     sprintf(buffer, "%s/%s.%s.archive",
10923                             arcDir,
10924                             appData.cmailGameName,
10925                             gameInfo.date);
10926                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10927                     cmailMsgLoaded = FALSE;
10928                 }
10929             }
10930
10931             DisplayInformation(msg);
10932             pclose(commandOutput);
10933         }
10934     } else {
10935         if ((*cmailMsg) != '\0') {
10936             DisplayInformation(cmailMsg);
10937         }
10938     }
10939
10940     return;
10941 #endif /* !WIN32 */
10942 }
10943
10944 char *
10945 CmailMsg()
10946 {
10947 #if WIN32
10948     return NULL;
10949 #else
10950     int  prependComma = 0;
10951     char number[5];
10952     char string[MSG_SIZ];       /* Space for game-list */
10953     int  i;
10954     
10955     if (!cmailMsgLoaded) return "";
10956
10957     if (cmailMailedMove) {
10958         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10959     } else {
10960         /* Create a list of games left */
10961         sprintf(string, "[");
10962         for (i = 0; i < nCmailGames; i ++) {
10963             if (! (   cmailMoveRegistered[i]
10964                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10965                 if (prependComma) {
10966                     sprintf(number, ",%d", i + 1);
10967                 } else {
10968                     sprintf(number, "%d", i + 1);
10969                     prependComma = 1;
10970                 }
10971                 
10972                 strcat(string, number);
10973             }
10974         }
10975         strcat(string, "]");
10976
10977         if (nCmailMovesRegistered + nCmailResults == 0) {
10978             switch (nCmailGames) {
10979               case 1:
10980                 sprintf(cmailMsg,
10981                         _("Still need to make move for game\n"));
10982                 break;
10983                 
10984               case 2:
10985                 sprintf(cmailMsg,
10986                         _("Still need to make moves for both games\n"));
10987                 break;
10988                 
10989               default:
10990                 sprintf(cmailMsg,
10991                         _("Still need to make moves for all %d games\n"),
10992                         nCmailGames);
10993                 break;
10994             }
10995         } else {
10996             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10997               case 1:
10998                 sprintf(cmailMsg,
10999                         _("Still need to make a move for game %s\n"),
11000                         string);
11001                 break;
11002                 
11003               case 0:
11004                 if (nCmailResults == nCmailGames) {
11005                     sprintf(cmailMsg, _("No unfinished games\n"));
11006                 } else {
11007                     sprintf(cmailMsg, _("Ready to send mail\n"));
11008                 }
11009                 break;
11010                 
11011               default:
11012                 sprintf(cmailMsg,
11013                         _("Still need to make moves for games %s\n"),
11014                         string);
11015             }
11016         }
11017     }
11018     return cmailMsg;
11019 #endif /* WIN32 */
11020 }
11021
11022 void
11023 ResetGameEvent()
11024 {
11025     if (gameMode == Training)
11026       SetTrainingModeOff();
11027
11028     Reset(TRUE, TRUE);
11029     cmailMsgLoaded = FALSE;
11030     if (appData.icsActive) {
11031       SendToICS(ics_prefix);
11032       SendToICS("refresh\n");
11033     }
11034 }
11035
11036 void
11037 ExitEvent(status)
11038      int status;
11039 {
11040     exiting++;
11041     if (exiting > 2) {
11042       /* Give up on clean exit */
11043       exit(status);
11044     }
11045     if (exiting > 1) {
11046       /* Keep trying for clean exit */
11047       return;
11048     }
11049
11050     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11051
11052     if (telnetISR != NULL) {
11053       RemoveInputSource(telnetISR);
11054     }
11055     if (icsPR != NoProc) {
11056       DestroyChildProcess(icsPR, TRUE);
11057     }
11058
11059     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11060     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11061
11062     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11063     /* make sure this other one finishes before killing it!                  */
11064     if(endingGame) { int count = 0;
11065         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11066         while(endingGame && count++ < 10) DoSleep(1);
11067         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11068     }
11069
11070     /* Kill off chess programs */
11071     if (first.pr != NoProc) {
11072         ExitAnalyzeMode();
11073         
11074         DoSleep( appData.delayBeforeQuit );
11075         SendToProgram("quit\n", &first);
11076         DoSleep( appData.delayAfterQuit );
11077         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11078     }
11079     if (second.pr != NoProc) {
11080         DoSleep( appData.delayBeforeQuit );
11081         SendToProgram("quit\n", &second);
11082         DoSleep( appData.delayAfterQuit );
11083         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11084     }
11085     if (first.isr != NULL) {
11086         RemoveInputSource(first.isr);
11087     }
11088     if (second.isr != NULL) {
11089         RemoveInputSource(second.isr);
11090     }
11091
11092     ShutDownFrontEnd();
11093     exit(status);
11094 }
11095
11096 void
11097 PauseEvent()
11098 {
11099     if (appData.debugMode)
11100         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11101     if (pausing) {
11102         pausing = FALSE;
11103         ModeHighlight();
11104         if (gameMode == MachinePlaysWhite ||
11105             gameMode == MachinePlaysBlack) {
11106             StartClocks();
11107         } else {
11108             DisplayBothClocks();
11109         }
11110         if (gameMode == PlayFromGameFile) {
11111             if (appData.timeDelay >= 0) 
11112                 AutoPlayGameLoop();
11113         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11114             Reset(FALSE, TRUE);
11115             SendToICS(ics_prefix);
11116             SendToICS("refresh\n");
11117         } else if (currentMove < forwardMostMove) {
11118             ForwardInner(forwardMostMove);
11119         }
11120         pauseExamInvalid = FALSE;
11121     } else {
11122         switch (gameMode) {
11123           default:
11124             return;
11125           case IcsExamining:
11126             pauseExamForwardMostMove = forwardMostMove;
11127             pauseExamInvalid = FALSE;
11128             /* fall through */
11129           case IcsObserving:
11130           case IcsPlayingWhite:
11131           case IcsPlayingBlack:
11132             pausing = TRUE;
11133             ModeHighlight();
11134             return;
11135           case PlayFromGameFile:
11136             (void) StopLoadGameTimer();
11137             pausing = TRUE;
11138             ModeHighlight();
11139             break;
11140           case BeginningOfGame:
11141             if (appData.icsActive) return;
11142             /* else fall through */
11143           case MachinePlaysWhite:
11144           case MachinePlaysBlack:
11145           case TwoMachinesPlay:
11146             if (forwardMostMove == 0)
11147               return;           /* don't pause if no one has moved */
11148             if ((gameMode == MachinePlaysWhite &&
11149                  !WhiteOnMove(forwardMostMove)) ||
11150                 (gameMode == MachinePlaysBlack &&
11151                  WhiteOnMove(forwardMostMove))) {
11152                 StopClocks();
11153             }
11154             pausing = TRUE;
11155             ModeHighlight();
11156             break;
11157         }
11158     }
11159 }
11160
11161 void
11162 EditCommentEvent()
11163 {
11164     char title[MSG_SIZ];
11165
11166     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11167         strcpy(title, _("Edit comment"));
11168     } else {
11169         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11170                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11171                 parseList[currentMove - 1]);
11172     }
11173
11174     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11175 }
11176
11177
11178 void
11179 EditTagsEvent()
11180 {
11181     char *tags = PGNTags(&gameInfo);
11182     EditTagsPopUp(tags);
11183     free(tags);
11184 }
11185
11186 void
11187 AnalyzeModeEvent()
11188 {
11189     if (appData.noChessProgram || gameMode == AnalyzeMode)
11190       return;
11191
11192     if (gameMode != AnalyzeFile) {
11193         if (!appData.icsEngineAnalyze) {
11194                EditGameEvent();
11195                if (gameMode != EditGame) return;
11196         }
11197         ResurrectChessProgram();
11198         SendToProgram("analyze\n", &first);
11199         first.analyzing = TRUE;
11200         /*first.maybeThinking = TRUE;*/
11201         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11202         EngineOutputPopUp();
11203     }
11204     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11205     pausing = FALSE;
11206     ModeHighlight();
11207     SetGameInfo();
11208
11209     StartAnalysisClock();
11210     GetTimeMark(&lastNodeCountTime);
11211     lastNodeCount = 0;
11212 }
11213
11214 void
11215 AnalyzeFileEvent()
11216 {
11217     if (appData.noChessProgram || gameMode == AnalyzeFile)
11218       return;
11219
11220     if (gameMode != AnalyzeMode) {
11221         EditGameEvent();
11222         if (gameMode != EditGame) return;
11223         ResurrectChessProgram();
11224         SendToProgram("analyze\n", &first);
11225         first.analyzing = TRUE;
11226         /*first.maybeThinking = TRUE;*/
11227         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11228         EngineOutputPopUp();
11229     }
11230     gameMode = AnalyzeFile;
11231     pausing = FALSE;
11232     ModeHighlight();
11233     SetGameInfo();
11234
11235     StartAnalysisClock();
11236     GetTimeMark(&lastNodeCountTime);
11237     lastNodeCount = 0;
11238 }
11239
11240 void
11241 MachineWhiteEvent()
11242 {
11243     char buf[MSG_SIZ];
11244     char *bookHit = NULL;
11245
11246     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11247       return;
11248
11249
11250     if (gameMode == PlayFromGameFile || 
11251         gameMode == TwoMachinesPlay  || 
11252         gameMode == Training         || 
11253         gameMode == AnalyzeMode      || 
11254         gameMode == EndOfGame)
11255         EditGameEvent();
11256
11257     if (gameMode == EditPosition) 
11258         EditPositionDone(TRUE);
11259
11260     if (!WhiteOnMove(currentMove)) {
11261         DisplayError(_("It is not White's turn"), 0);
11262         return;
11263     }
11264   
11265     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11266       ExitAnalyzeMode();
11267
11268     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11269         gameMode == AnalyzeFile)
11270         TruncateGame();
11271
11272     ResurrectChessProgram();    /* in case it isn't running */
11273     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11274         gameMode = MachinePlaysWhite;
11275         ResetClocks();
11276     } else
11277     gameMode = MachinePlaysWhite;
11278     pausing = FALSE;
11279     ModeHighlight();
11280     SetGameInfo();
11281     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11282     DisplayTitle(buf);
11283     if (first.sendName) {
11284       sprintf(buf, "name %s\n", gameInfo.black);
11285       SendToProgram(buf, &first);
11286     }
11287     if (first.sendTime) {
11288       if (first.useColors) {
11289         SendToProgram("black\n", &first); /*gnu kludge*/
11290       }
11291       SendTimeRemaining(&first, TRUE);
11292     }
11293     if (first.useColors) {
11294       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11295     }
11296     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11297     SetMachineThinkingEnables();
11298     first.maybeThinking = TRUE;
11299     StartClocks();
11300     firstMove = FALSE;
11301
11302     if (appData.autoFlipView && !flipView) {
11303       flipView = !flipView;
11304       DrawPosition(FALSE, NULL);
11305       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11306     }
11307
11308     if(bookHit) { // [HGM] book: simulate book reply
11309         static char bookMove[MSG_SIZ]; // a bit generous?
11310
11311         programStats.nodes = programStats.depth = programStats.time = 
11312         programStats.score = programStats.got_only_move = 0;
11313         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11314
11315         strcpy(bookMove, "move ");
11316         strcat(bookMove, bookHit);
11317         HandleMachineMove(bookMove, &first);
11318     }
11319 }
11320
11321 void
11322 MachineBlackEvent()
11323 {
11324     char buf[MSG_SIZ];
11325    char *bookHit = NULL;
11326
11327     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11328         return;
11329
11330
11331     if (gameMode == PlayFromGameFile || 
11332         gameMode == TwoMachinesPlay  || 
11333         gameMode == Training         || 
11334         gameMode == AnalyzeMode      || 
11335         gameMode == EndOfGame)
11336         EditGameEvent();
11337
11338     if (gameMode == EditPosition) 
11339         EditPositionDone(TRUE);
11340
11341     if (WhiteOnMove(currentMove)) {
11342         DisplayError(_("It is not Black's turn"), 0);
11343         return;
11344     }
11345     
11346     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11347       ExitAnalyzeMode();
11348
11349     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11350         gameMode == AnalyzeFile)
11351         TruncateGame();
11352
11353     ResurrectChessProgram();    /* in case it isn't running */
11354     gameMode = MachinePlaysBlack;
11355     pausing = FALSE;
11356     ModeHighlight();
11357     SetGameInfo();
11358     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11359     DisplayTitle(buf);
11360     if (first.sendName) {
11361       sprintf(buf, "name %s\n", gameInfo.white);
11362       SendToProgram(buf, &first);
11363     }
11364     if (first.sendTime) {
11365       if (first.useColors) {
11366         SendToProgram("white\n", &first); /*gnu kludge*/
11367       }
11368       SendTimeRemaining(&first, FALSE);
11369     }
11370     if (first.useColors) {
11371       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11372     }
11373     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11374     SetMachineThinkingEnables();
11375     first.maybeThinking = TRUE;
11376     StartClocks();
11377
11378     if (appData.autoFlipView && flipView) {
11379       flipView = !flipView;
11380       DrawPosition(FALSE, NULL);
11381       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11382     }
11383     if(bookHit) { // [HGM] book: simulate book reply
11384         static char bookMove[MSG_SIZ]; // a bit generous?
11385
11386         programStats.nodes = programStats.depth = programStats.time = 
11387         programStats.score = programStats.got_only_move = 0;
11388         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11389
11390         strcpy(bookMove, "move ");
11391         strcat(bookMove, bookHit);
11392         HandleMachineMove(bookMove, &first);
11393     }
11394 }
11395
11396
11397 void
11398 DisplayTwoMachinesTitle()
11399 {
11400     char buf[MSG_SIZ];
11401     if (appData.matchGames > 0) {
11402         if (first.twoMachinesColor[0] == 'w') {
11403             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11404                     gameInfo.white, gameInfo.black,
11405                     first.matchWins, second.matchWins,
11406                     matchGame - 1 - (first.matchWins + second.matchWins));
11407         } else {
11408             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11409                     gameInfo.white, gameInfo.black,
11410                     second.matchWins, first.matchWins,
11411                     matchGame - 1 - (first.matchWins + second.matchWins));
11412         }
11413     } else {
11414         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11415     }
11416     DisplayTitle(buf);
11417 }
11418
11419 void
11420 TwoMachinesEvent P((void))
11421 {
11422     int i;
11423     char buf[MSG_SIZ];
11424     ChessProgramState *onmove;
11425     char *bookHit = NULL;
11426     
11427     if (appData.noChessProgram) return;
11428
11429     switch (gameMode) {
11430       case TwoMachinesPlay:
11431         return;
11432       case MachinePlaysWhite:
11433       case MachinePlaysBlack:
11434         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11435             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11436             return;
11437         }
11438         /* fall through */
11439       case BeginningOfGame:
11440       case PlayFromGameFile:
11441       case EndOfGame:
11442         EditGameEvent();
11443         if (gameMode != EditGame) return;
11444         break;
11445       case EditPosition:
11446         EditPositionDone(TRUE);
11447         break;
11448       case AnalyzeMode:
11449       case AnalyzeFile:
11450         ExitAnalyzeMode();
11451         break;
11452       case EditGame:
11453       default:
11454         break;
11455     }
11456
11457 //    forwardMostMove = currentMove;
11458     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11459     ResurrectChessProgram();    /* in case first program isn't running */
11460
11461     if (second.pr == NULL) {
11462         StartChessProgram(&second);
11463         if (second.protocolVersion == 1) {
11464           TwoMachinesEventIfReady();
11465         } else {
11466           /* kludge: allow timeout for initial "feature" command */
11467           FreezeUI();
11468           DisplayMessage("", _("Starting second chess program"));
11469           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11470         }
11471         return;
11472     }
11473     DisplayMessage("", "");
11474     InitChessProgram(&second, FALSE);
11475     SendToProgram("force\n", &second);
11476     if (startedFromSetupPosition) {
11477         SendBoard(&second, backwardMostMove);
11478     if (appData.debugMode) {
11479         fprintf(debugFP, "Two Machines\n");
11480     }
11481     }
11482     for (i = backwardMostMove; i < forwardMostMove; i++) {
11483         SendMoveToProgram(i, &second);
11484     }
11485
11486     gameMode = TwoMachinesPlay;
11487     pausing = FALSE;
11488     ModeHighlight();
11489     SetGameInfo();
11490     DisplayTwoMachinesTitle();
11491     firstMove = TRUE;
11492     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11493         onmove = &first;
11494     } else {
11495         onmove = &second;
11496     }
11497
11498     SendToProgram(first.computerString, &first);
11499     if (first.sendName) {
11500       sprintf(buf, "name %s\n", second.tidy);
11501       SendToProgram(buf, &first);
11502     }
11503     SendToProgram(second.computerString, &second);
11504     if (second.sendName) {
11505       sprintf(buf, "name %s\n", first.tidy);
11506       SendToProgram(buf, &second);
11507     }
11508
11509     ResetClocks();
11510     if (!first.sendTime || !second.sendTime) {
11511         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11512         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11513     }
11514     if (onmove->sendTime) {
11515       if (onmove->useColors) {
11516         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11517       }
11518       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11519     }
11520     if (onmove->useColors) {
11521       SendToProgram(onmove->twoMachinesColor, onmove);
11522     }
11523     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11524 //    SendToProgram("go\n", onmove);
11525     onmove->maybeThinking = TRUE;
11526     SetMachineThinkingEnables();
11527
11528     StartClocks();
11529
11530     if(bookHit) { // [HGM] book: simulate book reply
11531         static char bookMove[MSG_SIZ]; // a bit generous?
11532
11533         programStats.nodes = programStats.depth = programStats.time = 
11534         programStats.score = programStats.got_only_move = 0;
11535         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11536
11537         strcpy(bookMove, "move ");
11538         strcat(bookMove, bookHit);
11539         savedMessage = bookMove; // args for deferred call
11540         savedState = onmove;
11541         ScheduleDelayedEvent(DeferredBookMove, 1);
11542     }
11543 }
11544
11545 void
11546 TrainingEvent()
11547 {
11548     if (gameMode == Training) {
11549       SetTrainingModeOff();
11550       gameMode = PlayFromGameFile;
11551       DisplayMessage("", _("Training mode off"));
11552     } else {
11553       gameMode = Training;
11554       animateTraining = appData.animate;
11555
11556       /* make sure we are not already at the end of the game */
11557       if (currentMove < forwardMostMove) {
11558         SetTrainingModeOn();
11559         DisplayMessage("", _("Training mode on"));
11560       } else {
11561         gameMode = PlayFromGameFile;
11562         DisplayError(_("Already at end of game"), 0);
11563       }
11564     }
11565     ModeHighlight();
11566 }
11567
11568 void
11569 IcsClientEvent()
11570 {
11571     if (!appData.icsActive) return;
11572     switch (gameMode) {
11573       case IcsPlayingWhite:
11574       case IcsPlayingBlack:
11575       case IcsObserving:
11576       case IcsIdle:
11577       case BeginningOfGame:
11578       case IcsExamining:
11579         return;
11580
11581       case EditGame:
11582         break;
11583
11584       case EditPosition:
11585         EditPositionDone(TRUE);
11586         break;
11587
11588       case AnalyzeMode:
11589       case AnalyzeFile:
11590         ExitAnalyzeMode();
11591         break;
11592         
11593       default:
11594         EditGameEvent();
11595         break;
11596     }
11597
11598     gameMode = IcsIdle;
11599     ModeHighlight();
11600     return;
11601 }
11602
11603
11604 void
11605 EditGameEvent()
11606 {
11607     int i;
11608
11609     switch (gameMode) {
11610       case Training:
11611         SetTrainingModeOff();
11612         break;
11613       case MachinePlaysWhite:
11614       case MachinePlaysBlack:
11615       case BeginningOfGame:
11616         SendToProgram("force\n", &first);
11617         SetUserThinkingEnables();
11618         break;
11619       case PlayFromGameFile:
11620         (void) StopLoadGameTimer();
11621         if (gameFileFP != NULL) {
11622             gameFileFP = NULL;
11623         }
11624         break;
11625       case EditPosition:
11626         EditPositionDone(TRUE);
11627         break;
11628       case AnalyzeMode:
11629       case AnalyzeFile:
11630         ExitAnalyzeMode();
11631         SendToProgram("force\n", &first);
11632         break;
11633       case TwoMachinesPlay:
11634         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11635         ResurrectChessProgram();
11636         SetUserThinkingEnables();
11637         break;
11638       case EndOfGame:
11639         ResurrectChessProgram();
11640         break;
11641       case IcsPlayingBlack:
11642       case IcsPlayingWhite:
11643         DisplayError(_("Warning: You are still playing a game"), 0);
11644         break;
11645       case IcsObserving:
11646         DisplayError(_("Warning: You are still observing a game"), 0);
11647         break;
11648       case IcsExamining:
11649         DisplayError(_("Warning: You are still examining a game"), 0);
11650         break;
11651       case IcsIdle:
11652         break;
11653       case EditGame:
11654       default:
11655         return;
11656     }
11657     
11658     pausing = FALSE;
11659     StopClocks();
11660     first.offeredDraw = second.offeredDraw = 0;
11661
11662     if (gameMode == PlayFromGameFile) {
11663         whiteTimeRemaining = timeRemaining[0][currentMove];
11664         blackTimeRemaining = timeRemaining[1][currentMove];
11665         DisplayTitle("");
11666     }
11667
11668     if (gameMode == MachinePlaysWhite ||
11669         gameMode == MachinePlaysBlack ||
11670         gameMode == TwoMachinesPlay ||
11671         gameMode == EndOfGame) {
11672         i = forwardMostMove;
11673         while (i > currentMove) {
11674             SendToProgram("undo\n", &first);
11675             i--;
11676         }
11677         whiteTimeRemaining = timeRemaining[0][currentMove];
11678         blackTimeRemaining = timeRemaining[1][currentMove];
11679         DisplayBothClocks();
11680         if (whiteFlag || blackFlag) {
11681             whiteFlag = blackFlag = 0;
11682         }
11683         DisplayTitle("");
11684     }           
11685     
11686     gameMode = EditGame;
11687     ModeHighlight();
11688     SetGameInfo();
11689 }
11690
11691
11692 void
11693 EditPositionEvent()
11694 {
11695     if (gameMode == EditPosition) {
11696         EditGameEvent();
11697         return;
11698     }
11699     
11700     EditGameEvent();
11701     if (gameMode != EditGame) return;
11702     
11703     gameMode = EditPosition;
11704     ModeHighlight();
11705     SetGameInfo();
11706     if (currentMove > 0)
11707       CopyBoard(boards[0], boards[currentMove]);
11708     
11709     blackPlaysFirst = !WhiteOnMove(currentMove);
11710     ResetClocks();
11711     currentMove = forwardMostMove = backwardMostMove = 0;
11712     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11713     DisplayMove(-1);
11714 }
11715
11716 void
11717 ExitAnalyzeMode()
11718 {
11719     /* [DM] icsEngineAnalyze - possible call from other functions */
11720     if (appData.icsEngineAnalyze) {
11721         appData.icsEngineAnalyze = FALSE;
11722
11723         DisplayMessage("",_("Close ICS engine analyze..."));
11724     }
11725     if (first.analysisSupport && first.analyzing) {
11726       SendToProgram("exit\n", &first);
11727       first.analyzing = FALSE;
11728     }
11729     thinkOutput[0] = NULLCHAR;
11730 }
11731
11732 void
11733 EditPositionDone(Boolean fakeRights)
11734 {
11735     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11736
11737     startedFromSetupPosition = TRUE;
11738     InitChessProgram(&first, FALSE);
11739     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11740       boards[0][EP_STATUS] = EP_NONE;
11741       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11742     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11743         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11744         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11745       } else boards[0][CASTLING][2] = NoRights;
11746     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11747         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11748         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11749       } else boards[0][CASTLING][5] = NoRights;
11750     }
11751     SendToProgram("force\n", &first);
11752     if (blackPlaysFirst) {
11753         strcpy(moveList[0], "");
11754         strcpy(parseList[0], "");
11755         currentMove = forwardMostMove = backwardMostMove = 1;
11756         CopyBoard(boards[1], boards[0]);
11757     } else {
11758         currentMove = forwardMostMove = backwardMostMove = 0;
11759     }
11760     SendBoard(&first, forwardMostMove);
11761     if (appData.debugMode) {
11762         fprintf(debugFP, "EditPosDone\n");
11763     }
11764     DisplayTitle("");
11765     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11766     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11767     gameMode = EditGame;
11768     ModeHighlight();
11769     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11770     ClearHighlights(); /* [AS] */
11771 }
11772
11773 /* Pause for `ms' milliseconds */
11774 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11775 void
11776 TimeDelay(ms)
11777      long ms;
11778 {
11779     TimeMark m1, m2;
11780
11781     GetTimeMark(&m1);
11782     do {
11783         GetTimeMark(&m2);
11784     } while (SubtractTimeMarks(&m2, &m1) < ms);
11785 }
11786
11787 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11788 void
11789 SendMultiLineToICS(buf)
11790      char *buf;
11791 {
11792     char temp[MSG_SIZ+1], *p;
11793     int len;
11794
11795     len = strlen(buf);
11796     if (len > MSG_SIZ)
11797       len = MSG_SIZ;
11798   
11799     strncpy(temp, buf, len);
11800     temp[len] = 0;
11801
11802     p = temp;
11803     while (*p) {
11804         if (*p == '\n' || *p == '\r')
11805           *p = ' ';
11806         ++p;
11807     }
11808
11809     strcat(temp, "\n");
11810     SendToICS(temp);
11811     SendToPlayer(temp, strlen(temp));
11812 }
11813
11814 void
11815 SetWhiteToPlayEvent()
11816 {
11817     if (gameMode == EditPosition) {
11818         blackPlaysFirst = FALSE;
11819         DisplayBothClocks();    /* works because currentMove is 0 */
11820     } else if (gameMode == IcsExamining) {
11821         SendToICS(ics_prefix);
11822         SendToICS("tomove white\n");
11823     }
11824 }
11825
11826 void
11827 SetBlackToPlayEvent()
11828 {
11829     if (gameMode == EditPosition) {
11830         blackPlaysFirst = TRUE;
11831         currentMove = 1;        /* kludge */
11832         DisplayBothClocks();
11833         currentMove = 0;
11834     } else if (gameMode == IcsExamining) {
11835         SendToICS(ics_prefix);
11836         SendToICS("tomove black\n");
11837     }
11838 }
11839
11840 void
11841 EditPositionMenuEvent(selection, x, y)
11842      ChessSquare selection;
11843      int x, y;
11844 {
11845     char buf[MSG_SIZ];
11846     ChessSquare piece = boards[0][y][x];
11847
11848     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11849
11850     switch (selection) {
11851       case ClearBoard:
11852         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11853             SendToICS(ics_prefix);
11854             SendToICS("bsetup clear\n");
11855         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11856             SendToICS(ics_prefix);
11857             SendToICS("clearboard\n");
11858         } else {
11859             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11860                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11861                 for (y = 0; y < BOARD_HEIGHT; y++) {
11862                     if (gameMode == IcsExamining) {
11863                         if (boards[currentMove][y][x] != EmptySquare) {
11864                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11865                                     AAA + x, ONE + y);
11866                             SendToICS(buf);
11867                         }
11868                     } else {
11869                         boards[0][y][x] = p;
11870                     }
11871                 }
11872             }
11873         }
11874         if (gameMode == EditPosition) {
11875             DrawPosition(FALSE, boards[0]);
11876         }
11877         break;
11878
11879       case WhitePlay:
11880         SetWhiteToPlayEvent();
11881         break;
11882
11883       case BlackPlay:
11884         SetBlackToPlayEvent();
11885         break;
11886
11887       case EmptySquare:
11888         if (gameMode == IcsExamining) {
11889             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11890             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11891             SendToICS(buf);
11892         } else {
11893             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11894                 if(x == BOARD_LEFT-2) {
11895                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11896                     boards[0][y][1] = 0;
11897                 } else
11898                 if(x == BOARD_RGHT+1) {
11899                     if(y >= gameInfo.holdingsSize) break;
11900                     boards[0][y][BOARD_WIDTH-2] = 0;
11901                 } else break;
11902             }
11903             boards[0][y][x] = EmptySquare;
11904             DrawPosition(FALSE, boards[0]);
11905         }
11906         break;
11907
11908       case PromotePiece:
11909         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11910            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11911             selection = (ChessSquare) (PROMOTED piece);
11912         } else if(piece == EmptySquare) selection = WhiteSilver;
11913         else selection = (ChessSquare)((int)piece - 1);
11914         goto defaultlabel;
11915
11916       case DemotePiece:
11917         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11918            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11919             selection = (ChessSquare) (DEMOTED piece);
11920         } else if(piece == EmptySquare) selection = BlackSilver;
11921         else selection = (ChessSquare)((int)piece + 1);       
11922         goto defaultlabel;
11923
11924       case WhiteQueen:
11925       case BlackQueen:
11926         if(gameInfo.variant == VariantShatranj ||
11927            gameInfo.variant == VariantXiangqi  ||
11928            gameInfo.variant == VariantCourier  ||
11929            gameInfo.variant == VariantMakruk     )
11930             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11931         goto defaultlabel;
11932
11933       case WhiteKing:
11934       case BlackKing:
11935         if(gameInfo.variant == VariantXiangqi)
11936             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11937         if(gameInfo.variant == VariantKnightmate)
11938             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11939       default:
11940         defaultlabel:
11941         if (gameMode == IcsExamining) {
11942             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11943             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11944                     PieceToChar(selection), AAA + x, ONE + y);
11945             SendToICS(buf);
11946         } else {
11947             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11948                 int n;
11949                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11950                     n = PieceToNumber(selection - BlackPawn);
11951                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11952                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11953                     boards[0][BOARD_HEIGHT-1-n][1]++;
11954                 } else
11955                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11956                     n = PieceToNumber(selection);
11957                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11958                     boards[0][n][BOARD_WIDTH-1] = selection;
11959                     boards[0][n][BOARD_WIDTH-2]++;
11960                 }
11961             } else
11962             boards[0][y][x] = selection;
11963             DrawPosition(TRUE, boards[0]);
11964         }
11965         break;
11966     }
11967 }
11968
11969
11970 void
11971 DropMenuEvent(selection, x, y)
11972      ChessSquare selection;
11973      int x, y;
11974 {
11975     ChessMove moveType;
11976
11977     switch (gameMode) {
11978       case IcsPlayingWhite:
11979       case MachinePlaysBlack:
11980         if (!WhiteOnMove(currentMove)) {
11981             DisplayMoveError(_("It is Black's turn"));
11982             return;
11983         }
11984         moveType = WhiteDrop;
11985         break;
11986       case IcsPlayingBlack:
11987       case MachinePlaysWhite:
11988         if (WhiteOnMove(currentMove)) {
11989             DisplayMoveError(_("It is White's turn"));
11990             return;
11991         }
11992         moveType = BlackDrop;
11993         break;
11994       case EditGame:
11995         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11996         break;
11997       default:
11998         return;
11999     }
12000
12001     if (moveType == BlackDrop && selection < BlackPawn) {
12002       selection = (ChessSquare) ((int) selection
12003                                  + (int) BlackPawn - (int) WhitePawn);
12004     }
12005     if (boards[currentMove][y][x] != EmptySquare) {
12006         DisplayMoveError(_("That square is occupied"));
12007         return;
12008     }
12009
12010     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12011 }
12012
12013 void
12014 AcceptEvent()
12015 {
12016     /* Accept a pending offer of any kind from opponent */
12017     
12018     if (appData.icsActive) {
12019         SendToICS(ics_prefix);
12020         SendToICS("accept\n");
12021     } else if (cmailMsgLoaded) {
12022         if (currentMove == cmailOldMove &&
12023             commentList[cmailOldMove] != NULL &&
12024             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12025                    "Black offers a draw" : "White offers a draw")) {
12026             TruncateGame();
12027             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12028             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12029         } else {
12030             DisplayError(_("There is no pending offer on this move"), 0);
12031             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12032         }
12033     } else {
12034         /* Not used for offers from chess program */
12035     }
12036 }
12037
12038 void
12039 DeclineEvent()
12040 {
12041     /* Decline a pending offer of any kind from opponent */
12042     
12043     if (appData.icsActive) {
12044         SendToICS(ics_prefix);
12045         SendToICS("decline\n");
12046     } else if (cmailMsgLoaded) {
12047         if (currentMove == cmailOldMove &&
12048             commentList[cmailOldMove] != NULL &&
12049             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12050                    "Black offers a draw" : "White offers a draw")) {
12051 #ifdef NOTDEF
12052             AppendComment(cmailOldMove, "Draw declined", TRUE);
12053             DisplayComment(cmailOldMove - 1, "Draw declined");
12054 #endif /*NOTDEF*/
12055         } else {
12056             DisplayError(_("There is no pending offer on this move"), 0);
12057         }
12058     } else {
12059         /* Not used for offers from chess program */
12060     }
12061 }
12062
12063 void
12064 RematchEvent()
12065 {
12066     /* Issue ICS rematch command */
12067     if (appData.icsActive) {
12068         SendToICS(ics_prefix);
12069         SendToICS("rematch\n");
12070     }
12071 }
12072
12073 void
12074 CallFlagEvent()
12075 {
12076     /* Call your opponent's flag (claim a win on time) */
12077     if (appData.icsActive) {
12078         SendToICS(ics_prefix);
12079         SendToICS("flag\n");
12080     } else {
12081         switch (gameMode) {
12082           default:
12083             return;
12084           case MachinePlaysWhite:
12085             if (whiteFlag) {
12086                 if (blackFlag)
12087                   GameEnds(GameIsDrawn, "Both players ran out of time",
12088                            GE_PLAYER);
12089                 else
12090                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12091             } else {
12092                 DisplayError(_("Your opponent is not out of time"), 0);
12093             }
12094             break;
12095           case MachinePlaysBlack:
12096             if (blackFlag) {
12097                 if (whiteFlag)
12098                   GameEnds(GameIsDrawn, "Both players ran out of time",
12099                            GE_PLAYER);
12100                 else
12101                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12102             } else {
12103                 DisplayError(_("Your opponent is not out of time"), 0);
12104             }
12105             break;
12106         }
12107     }
12108 }
12109
12110 void
12111 DrawEvent()
12112 {
12113     /* Offer draw or accept pending draw offer from opponent */
12114     
12115     if (appData.icsActive) {
12116         /* Note: tournament rules require draw offers to be
12117            made after you make your move but before you punch
12118            your clock.  Currently ICS doesn't let you do that;
12119            instead, you immediately punch your clock after making
12120            a move, but you can offer a draw at any time. */
12121         
12122         SendToICS(ics_prefix);
12123         SendToICS("draw\n");
12124         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12125     } else if (cmailMsgLoaded) {
12126         if (currentMove == cmailOldMove &&
12127             commentList[cmailOldMove] != NULL &&
12128             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12129                    "Black offers a draw" : "White offers a draw")) {
12130             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12131             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12132         } else if (currentMove == cmailOldMove + 1) {
12133             char *offer = WhiteOnMove(cmailOldMove) ?
12134               "White offers a draw" : "Black offers a draw";
12135             AppendComment(currentMove, offer, TRUE);
12136             DisplayComment(currentMove - 1, offer);
12137             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12138         } else {
12139             DisplayError(_("You must make your move before offering a draw"), 0);
12140             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12141         }
12142     } else if (first.offeredDraw) {
12143         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12144     } else {
12145         if (first.sendDrawOffers) {
12146             SendToProgram("draw\n", &first);
12147             userOfferedDraw = TRUE;
12148         }
12149     }
12150 }
12151
12152 void
12153 AdjournEvent()
12154 {
12155     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12156     
12157     if (appData.icsActive) {
12158         SendToICS(ics_prefix);
12159         SendToICS("adjourn\n");
12160     } else {
12161         /* Currently GNU Chess doesn't offer or accept Adjourns */
12162     }
12163 }
12164
12165
12166 void
12167 AbortEvent()
12168 {
12169     /* Offer Abort or accept pending Abort offer from opponent */
12170     
12171     if (appData.icsActive) {
12172         SendToICS(ics_prefix);
12173         SendToICS("abort\n");
12174     } else {
12175         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12176     }
12177 }
12178
12179 void
12180 ResignEvent()
12181 {
12182     /* Resign.  You can do this even if it's not your turn. */
12183     
12184     if (appData.icsActive) {
12185         SendToICS(ics_prefix);
12186         SendToICS("resign\n");
12187     } else {
12188         switch (gameMode) {
12189           case MachinePlaysWhite:
12190             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12191             break;
12192           case MachinePlaysBlack:
12193             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12194             break;
12195           case EditGame:
12196             if (cmailMsgLoaded) {
12197                 TruncateGame();
12198                 if (WhiteOnMove(cmailOldMove)) {
12199                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12200                 } else {
12201                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12202                 }
12203                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12204             }
12205             break;
12206           default:
12207             break;
12208         }
12209     }
12210 }
12211
12212
12213 void
12214 StopObservingEvent()
12215 {
12216     /* Stop observing current games */
12217     SendToICS(ics_prefix);
12218     SendToICS("unobserve\n");
12219 }
12220
12221 void
12222 StopExaminingEvent()
12223 {
12224     /* Stop observing current game */
12225     SendToICS(ics_prefix);
12226     SendToICS("unexamine\n");
12227 }
12228
12229 void
12230 ForwardInner(target)
12231      int target;
12232 {
12233     int limit;
12234
12235     if (appData.debugMode)
12236         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12237                 target, currentMove, forwardMostMove);
12238
12239     if (gameMode == EditPosition)
12240       return;
12241
12242     if (gameMode == PlayFromGameFile && !pausing)
12243       PauseEvent();
12244     
12245     if (gameMode == IcsExamining && pausing)
12246       limit = pauseExamForwardMostMove;
12247     else
12248       limit = forwardMostMove;
12249     
12250     if (target > limit) target = limit;
12251
12252     if (target > 0 && moveList[target - 1][0]) {
12253         int fromX, fromY, toX, toY;
12254         toX = moveList[target - 1][2] - AAA;
12255         toY = moveList[target - 1][3] - ONE;
12256         if (moveList[target - 1][1] == '@') {
12257             if (appData.highlightLastMove) {
12258                 SetHighlights(-1, -1, toX, toY);
12259             }
12260         } else {
12261             fromX = moveList[target - 1][0] - AAA;
12262             fromY = moveList[target - 1][1] - ONE;
12263             if (target == currentMove + 1) {
12264                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12265             }
12266             if (appData.highlightLastMove) {
12267                 SetHighlights(fromX, fromY, toX, toY);
12268             }
12269         }
12270     }
12271     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12272         gameMode == Training || gameMode == PlayFromGameFile || 
12273         gameMode == AnalyzeFile) {
12274         while (currentMove < target) {
12275             SendMoveToProgram(currentMove++, &first);
12276         }
12277     } else {
12278         currentMove = target;
12279     }
12280     
12281     if (gameMode == EditGame || gameMode == EndOfGame) {
12282         whiteTimeRemaining = timeRemaining[0][currentMove];
12283         blackTimeRemaining = timeRemaining[1][currentMove];
12284     }
12285     DisplayBothClocks();
12286     DisplayMove(currentMove - 1);
12287     DrawPosition(FALSE, boards[currentMove]);
12288     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12289     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12290         DisplayComment(currentMove - 1, commentList[currentMove]);
12291     }
12292 }
12293
12294
12295 void
12296 ForwardEvent()
12297 {
12298     if (gameMode == IcsExamining && !pausing) {
12299         SendToICS(ics_prefix);
12300         SendToICS("forward\n");
12301     } else {
12302         ForwardInner(currentMove + 1);
12303     }
12304 }
12305
12306 void
12307 ToEndEvent()
12308 {
12309     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12310         /* to optimze, we temporarily turn off analysis mode while we feed
12311          * the remaining moves to the engine. Otherwise we get analysis output
12312          * after each move.
12313          */ 
12314         if (first.analysisSupport) {
12315           SendToProgram("exit\nforce\n", &first);
12316           first.analyzing = FALSE;
12317         }
12318     }
12319         
12320     if (gameMode == IcsExamining && !pausing) {
12321         SendToICS(ics_prefix);
12322         SendToICS("forward 999999\n");
12323     } else {
12324         ForwardInner(forwardMostMove);
12325     }
12326
12327     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12328         /* we have fed all the moves, so reactivate analysis mode */
12329         SendToProgram("analyze\n", &first);
12330         first.analyzing = TRUE;
12331         /*first.maybeThinking = TRUE;*/
12332         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12333     }
12334 }
12335
12336 void
12337 BackwardInner(target)
12338      int target;
12339 {
12340     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12341
12342     if (appData.debugMode)
12343         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12344                 target, currentMove, forwardMostMove);
12345
12346     if (gameMode == EditPosition) return;
12347     if (currentMove <= backwardMostMove) {
12348         ClearHighlights();
12349         DrawPosition(full_redraw, boards[currentMove]);
12350         return;
12351     }
12352     if (gameMode == PlayFromGameFile && !pausing)
12353       PauseEvent();
12354     
12355     if (moveList[target][0]) {
12356         int fromX, fromY, toX, toY;
12357         toX = moveList[target][2] - AAA;
12358         toY = moveList[target][3] - ONE;
12359         if (moveList[target][1] == '@') {
12360             if (appData.highlightLastMove) {
12361                 SetHighlights(-1, -1, toX, toY);
12362             }
12363         } else {
12364             fromX = moveList[target][0] - AAA;
12365             fromY = moveList[target][1] - ONE;
12366             if (target == currentMove - 1) {
12367                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12368             }
12369             if (appData.highlightLastMove) {
12370                 SetHighlights(fromX, fromY, toX, toY);
12371             }
12372         }
12373     }
12374     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12375         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12376         while (currentMove > target) {
12377             SendToProgram("undo\n", &first);
12378             currentMove--;
12379         }
12380     } else {
12381         currentMove = target;
12382     }
12383     
12384     if (gameMode == EditGame || gameMode == EndOfGame) {
12385         whiteTimeRemaining = timeRemaining[0][currentMove];
12386         blackTimeRemaining = timeRemaining[1][currentMove];
12387     }
12388     DisplayBothClocks();
12389     DisplayMove(currentMove - 1);
12390     DrawPosition(full_redraw, boards[currentMove]);
12391     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12392     // [HGM] PV info: routine tests if comment empty
12393     DisplayComment(currentMove - 1, commentList[currentMove]);
12394 }
12395
12396 void
12397 BackwardEvent()
12398 {
12399     if (gameMode == IcsExamining && !pausing) {
12400         SendToICS(ics_prefix);
12401         SendToICS("backward\n");
12402     } else {
12403         BackwardInner(currentMove - 1);
12404     }
12405 }
12406
12407 void
12408 ToStartEvent()
12409 {
12410     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12411         /* to optimize, we temporarily turn off analysis mode while we undo
12412          * all the moves. Otherwise we get analysis output after each undo.
12413          */ 
12414         if (first.analysisSupport) {
12415           SendToProgram("exit\nforce\n", &first);
12416           first.analyzing = FALSE;
12417         }
12418     }
12419
12420     if (gameMode == IcsExamining && !pausing) {
12421         SendToICS(ics_prefix);
12422         SendToICS("backward 999999\n");
12423     } else {
12424         BackwardInner(backwardMostMove);
12425     }
12426
12427     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12428         /* we have fed all the moves, so reactivate analysis mode */
12429         SendToProgram("analyze\n", &first);
12430         first.analyzing = TRUE;
12431         /*first.maybeThinking = TRUE;*/
12432         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12433     }
12434 }
12435
12436 void
12437 ToNrEvent(int to)
12438 {
12439   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12440   if (to >= forwardMostMove) to = forwardMostMove;
12441   if (to <= backwardMostMove) to = backwardMostMove;
12442   if (to < currentMove) {
12443     BackwardInner(to);
12444   } else {
12445     ForwardInner(to);
12446   }
12447 }
12448
12449 void
12450 RevertEvent()
12451 {
12452     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12453         return;
12454     }
12455     if (gameMode != IcsExamining) {
12456         DisplayError(_("You are not examining a game"), 0);
12457         return;
12458     }
12459     if (pausing) {
12460         DisplayError(_("You can't revert while pausing"), 0);
12461         return;
12462     }
12463     SendToICS(ics_prefix);
12464     SendToICS("revert\n");
12465 }
12466
12467 void
12468 RetractMoveEvent()
12469 {
12470     switch (gameMode) {
12471       case MachinePlaysWhite:
12472       case MachinePlaysBlack:
12473         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12474             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12475             return;
12476         }
12477         if (forwardMostMove < 2) return;
12478         currentMove = forwardMostMove = forwardMostMove - 2;
12479         whiteTimeRemaining = timeRemaining[0][currentMove];
12480         blackTimeRemaining = timeRemaining[1][currentMove];
12481         DisplayBothClocks();
12482         DisplayMove(currentMove - 1);
12483         ClearHighlights();/*!! could figure this out*/
12484         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12485         SendToProgram("remove\n", &first);
12486         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12487         break;
12488
12489       case BeginningOfGame:
12490       default:
12491         break;
12492
12493       case IcsPlayingWhite:
12494       case IcsPlayingBlack:
12495         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12496             SendToICS(ics_prefix);
12497             SendToICS("takeback 2\n");
12498         } else {
12499             SendToICS(ics_prefix);
12500             SendToICS("takeback 1\n");
12501         }
12502         break;
12503     }
12504 }
12505
12506 void
12507 MoveNowEvent()
12508 {
12509     ChessProgramState *cps;
12510
12511     switch (gameMode) {
12512       case MachinePlaysWhite:
12513         if (!WhiteOnMove(forwardMostMove)) {
12514             DisplayError(_("It is your turn"), 0);
12515             return;
12516         }
12517         cps = &first;
12518         break;
12519       case MachinePlaysBlack:
12520         if (WhiteOnMove(forwardMostMove)) {
12521             DisplayError(_("It is your turn"), 0);
12522             return;
12523         }
12524         cps = &first;
12525         break;
12526       case TwoMachinesPlay:
12527         if (WhiteOnMove(forwardMostMove) ==
12528             (first.twoMachinesColor[0] == 'w')) {
12529             cps = &first;
12530         } else {
12531             cps = &second;
12532         }
12533         break;
12534       case BeginningOfGame:
12535       default:
12536         return;
12537     }
12538     SendToProgram("?\n", cps);
12539 }
12540
12541 void
12542 TruncateGameEvent()
12543 {
12544     EditGameEvent();
12545     if (gameMode != EditGame) return;
12546     TruncateGame();
12547 }
12548
12549 void
12550 TruncateGame()
12551 {
12552     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12553     if (forwardMostMove > currentMove) {
12554         if (gameInfo.resultDetails != NULL) {
12555             free(gameInfo.resultDetails);
12556             gameInfo.resultDetails = NULL;
12557             gameInfo.result = GameUnfinished;
12558         }
12559         forwardMostMove = currentMove;
12560         HistorySet(parseList, backwardMostMove, forwardMostMove,
12561                    currentMove-1);
12562     }
12563 }
12564
12565 void
12566 HintEvent()
12567 {
12568     if (appData.noChessProgram) return;
12569     switch (gameMode) {
12570       case MachinePlaysWhite:
12571         if (WhiteOnMove(forwardMostMove)) {
12572             DisplayError(_("Wait until your turn"), 0);
12573             return;
12574         }
12575         break;
12576       case BeginningOfGame:
12577       case MachinePlaysBlack:
12578         if (!WhiteOnMove(forwardMostMove)) {
12579             DisplayError(_("Wait until your turn"), 0);
12580             return;
12581         }
12582         break;
12583       default:
12584         DisplayError(_("No hint available"), 0);
12585         return;
12586     }
12587     SendToProgram("hint\n", &first);
12588     hintRequested = TRUE;
12589 }
12590
12591 void
12592 BookEvent()
12593 {
12594     if (appData.noChessProgram) return;
12595     switch (gameMode) {
12596       case MachinePlaysWhite:
12597         if (WhiteOnMove(forwardMostMove)) {
12598             DisplayError(_("Wait until your turn"), 0);
12599             return;
12600         }
12601         break;
12602       case BeginningOfGame:
12603       case MachinePlaysBlack:
12604         if (!WhiteOnMove(forwardMostMove)) {
12605             DisplayError(_("Wait until your turn"), 0);
12606             return;
12607         }
12608         break;
12609       case EditPosition:
12610         EditPositionDone(TRUE);
12611         break;
12612       case TwoMachinesPlay:
12613         return;
12614       default:
12615         break;
12616     }
12617     SendToProgram("bk\n", &first);
12618     bookOutput[0] = NULLCHAR;
12619     bookRequested = TRUE;
12620 }
12621
12622 void
12623 AboutGameEvent()
12624 {
12625     char *tags = PGNTags(&gameInfo);
12626     TagsPopUp(tags, CmailMsg());
12627     free(tags);
12628 }
12629
12630 /* end button procedures */
12631
12632 void
12633 PrintPosition(fp, move)
12634      FILE *fp;
12635      int move;
12636 {
12637     int i, j;
12638     
12639     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12640         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12641             char c = PieceToChar(boards[move][i][j]);
12642             fputc(c == 'x' ? '.' : c, fp);
12643             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12644         }
12645     }
12646     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12647       fprintf(fp, "white to play\n");
12648     else
12649       fprintf(fp, "black to play\n");
12650 }
12651
12652 void
12653 PrintOpponents(fp)
12654      FILE *fp;
12655 {
12656     if (gameInfo.white != NULL) {
12657         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12658     } else {
12659         fprintf(fp, "\n");
12660     }
12661 }
12662
12663 /* Find last component of program's own name, using some heuristics */
12664 void
12665 TidyProgramName(prog, host, buf)
12666      char *prog, *host, buf[MSG_SIZ];
12667 {
12668     char *p, *q;
12669     int local = (strcmp(host, "localhost") == 0);
12670     while (!local && (p = strchr(prog, ';')) != NULL) {
12671         p++;
12672         while (*p == ' ') p++;
12673         prog = p;
12674     }
12675     if (*prog == '"' || *prog == '\'') {
12676         q = strchr(prog + 1, *prog);
12677     } else {
12678         q = strchr(prog, ' ');
12679     }
12680     if (q == NULL) q = prog + strlen(prog);
12681     p = q;
12682     while (p >= prog && *p != '/' && *p != '\\') p--;
12683     p++;
12684     if(p == prog && *p == '"') p++;
12685     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12686     memcpy(buf, p, q - p);
12687     buf[q - p] = NULLCHAR;
12688     if (!local) {
12689         strcat(buf, "@");
12690         strcat(buf, host);
12691     }
12692 }
12693
12694 char *
12695 TimeControlTagValue()
12696 {
12697     char buf[MSG_SIZ];
12698     if (!appData.clockMode) {
12699         strcpy(buf, "-");
12700     } else if (movesPerSession > 0) {
12701         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12702     } else if (timeIncrement == 0) {
12703         sprintf(buf, "%ld", timeControl/1000);
12704     } else {
12705         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12706     }
12707     return StrSave(buf);
12708 }
12709
12710 void
12711 SetGameInfo()
12712 {
12713     /* This routine is used only for certain modes */
12714     VariantClass v = gameInfo.variant;
12715     ChessMove r = GameUnfinished;
12716     char *p = NULL;
12717
12718     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12719         r = gameInfo.result; 
12720         p = gameInfo.resultDetails; 
12721         gameInfo.resultDetails = NULL;
12722     }
12723     ClearGameInfo(&gameInfo);
12724     gameInfo.variant = v;
12725
12726     switch (gameMode) {
12727       case MachinePlaysWhite:
12728         gameInfo.event = StrSave( appData.pgnEventHeader );
12729         gameInfo.site = StrSave(HostName());
12730         gameInfo.date = PGNDate();
12731         gameInfo.round = StrSave("-");
12732         gameInfo.white = StrSave(first.tidy);
12733         gameInfo.black = StrSave(UserName());
12734         gameInfo.timeControl = TimeControlTagValue();
12735         break;
12736
12737       case MachinePlaysBlack:
12738         gameInfo.event = StrSave( appData.pgnEventHeader );
12739         gameInfo.site = StrSave(HostName());
12740         gameInfo.date = PGNDate();
12741         gameInfo.round = StrSave("-");
12742         gameInfo.white = StrSave(UserName());
12743         gameInfo.black = StrSave(first.tidy);
12744         gameInfo.timeControl = TimeControlTagValue();
12745         break;
12746
12747       case TwoMachinesPlay:
12748         gameInfo.event = StrSave( appData.pgnEventHeader );
12749         gameInfo.site = StrSave(HostName());
12750         gameInfo.date = PGNDate();
12751         if (matchGame > 0) {
12752             char buf[MSG_SIZ];
12753             sprintf(buf, "%d", matchGame);
12754             gameInfo.round = StrSave(buf);
12755         } else {
12756             gameInfo.round = StrSave("-");
12757         }
12758         if (first.twoMachinesColor[0] == 'w') {
12759             gameInfo.white = StrSave(first.tidy);
12760             gameInfo.black = StrSave(second.tidy);
12761         } else {
12762             gameInfo.white = StrSave(second.tidy);
12763             gameInfo.black = StrSave(first.tidy);
12764         }
12765         gameInfo.timeControl = TimeControlTagValue();
12766         break;
12767
12768       case EditGame:
12769         gameInfo.event = StrSave("Edited game");
12770         gameInfo.site = StrSave(HostName());
12771         gameInfo.date = PGNDate();
12772         gameInfo.round = StrSave("-");
12773         gameInfo.white = StrSave("-");
12774         gameInfo.black = StrSave("-");
12775         gameInfo.result = r;
12776         gameInfo.resultDetails = p;
12777         break;
12778
12779       case EditPosition:
12780         gameInfo.event = StrSave("Edited position");
12781         gameInfo.site = StrSave(HostName());
12782         gameInfo.date = PGNDate();
12783         gameInfo.round = StrSave("-");
12784         gameInfo.white = StrSave("-");
12785         gameInfo.black = StrSave("-");
12786         break;
12787
12788       case IcsPlayingWhite:
12789       case IcsPlayingBlack:
12790       case IcsObserving:
12791       case IcsExamining:
12792         break;
12793
12794       case PlayFromGameFile:
12795         gameInfo.event = StrSave("Game from non-PGN file");
12796         gameInfo.site = StrSave(HostName());
12797         gameInfo.date = PGNDate();
12798         gameInfo.round = StrSave("-");
12799         gameInfo.white = StrSave("?");
12800         gameInfo.black = StrSave("?");
12801         break;
12802
12803       default:
12804         break;
12805     }
12806 }
12807
12808 void
12809 ReplaceComment(index, text)
12810      int index;
12811      char *text;
12812 {
12813     int len;
12814
12815     while (*text == '\n') text++;
12816     len = strlen(text);
12817     while (len > 0 && text[len - 1] == '\n') len--;
12818
12819     if (commentList[index] != NULL)
12820       free(commentList[index]);
12821
12822     if (len == 0) {
12823         commentList[index] = NULL;
12824         return;
12825     }
12826   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12827       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12828       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12829     commentList[index] = (char *) malloc(len + 2);
12830     strncpy(commentList[index], text, len);
12831     commentList[index][len] = '\n';
12832     commentList[index][len + 1] = NULLCHAR;
12833   } else { 
12834     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12835     char *p;
12836     commentList[index] = (char *) malloc(len + 6);
12837     strcpy(commentList[index], "{\n");
12838     strncpy(commentList[index]+2, text, len);
12839     commentList[index][len+2] = NULLCHAR;
12840     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12841     strcat(commentList[index], "\n}\n");
12842   }
12843 }
12844
12845 void
12846 CrushCRs(text)
12847      char *text;
12848 {
12849   char *p = text;
12850   char *q = text;
12851   char ch;
12852
12853   do {
12854     ch = *p++;
12855     if (ch == '\r') continue;
12856     *q++ = ch;
12857   } while (ch != '\0');
12858 }
12859
12860 void
12861 AppendComment(index, text, addBraces)
12862      int index;
12863      char *text;
12864      Boolean addBraces; // [HGM] braces: tells if we should add {}
12865 {
12866     int oldlen, len;
12867     char *old;
12868
12869 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12870     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12871
12872     CrushCRs(text);
12873     while (*text == '\n') text++;
12874     len = strlen(text);
12875     while (len > 0 && text[len - 1] == '\n') len--;
12876
12877     if (len == 0) return;
12878
12879     if (commentList[index] != NULL) {
12880         old = commentList[index];
12881         oldlen = strlen(old);
12882         while(commentList[index][oldlen-1] ==  '\n')
12883           commentList[index][--oldlen] = NULLCHAR;
12884         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12885         strcpy(commentList[index], old);
12886         free(old);
12887         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12888         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12889           if(addBraces) addBraces = FALSE; else { text++; len--; }
12890           while (*text == '\n') { text++; len--; }
12891           commentList[index][--oldlen] = NULLCHAR;
12892       }
12893         if(addBraces) strcat(commentList[index], "\n{\n");
12894         else          strcat(commentList[index], "\n");
12895         strcat(commentList[index], text);
12896         if(addBraces) strcat(commentList[index], "\n}\n");
12897         else          strcat(commentList[index], "\n");
12898     } else {
12899         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12900         if(addBraces)
12901              strcpy(commentList[index], "{\n");
12902         else commentList[index][0] = NULLCHAR;
12903         strcat(commentList[index], text);
12904         strcat(commentList[index], "\n");
12905         if(addBraces) strcat(commentList[index], "}\n");
12906     }
12907 }
12908
12909 static char * FindStr( char * text, char * sub_text )
12910 {
12911     char * result = strstr( text, sub_text );
12912
12913     if( result != NULL ) {
12914         result += strlen( sub_text );
12915     }
12916
12917     return result;
12918 }
12919
12920 /* [AS] Try to extract PV info from PGN comment */
12921 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12922 char *GetInfoFromComment( int index, char * text )
12923 {
12924     char * sep = text;
12925
12926     if( text != NULL && index > 0 ) {
12927         int score = 0;
12928         int depth = 0;
12929         int time = -1, sec = 0, deci;
12930         char * s_eval = FindStr( text, "[%eval " );
12931         char * s_emt = FindStr( text, "[%emt " );
12932
12933         if( s_eval != NULL || s_emt != NULL ) {
12934             /* New style */
12935             char delim;
12936
12937             if( s_eval != NULL ) {
12938                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12939                     return text;
12940                 }
12941
12942                 if( delim != ']' ) {
12943                     return text;
12944                 }
12945             }
12946
12947             if( s_emt != NULL ) {
12948             }
12949                 return text;
12950         }
12951         else {
12952             /* We expect something like: [+|-]nnn.nn/dd */
12953             int score_lo = 0;
12954
12955             if(*text != '{') return text; // [HGM] braces: must be normal comment
12956
12957             sep = strchr( text, '/' );
12958             if( sep == NULL || sep < (text+4) ) {
12959                 return text;
12960             }
12961
12962             time = -1; sec = -1; deci = -1;
12963             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12964                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12965                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12966                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12967                 return text;
12968             }
12969
12970             if( score_lo < 0 || score_lo >= 100 ) {
12971                 return text;
12972             }
12973
12974             if(sec >= 0) time = 600*time + 10*sec; else
12975             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12976
12977             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12978
12979             /* [HGM] PV time: now locate end of PV info */
12980             while( *++sep >= '0' && *sep <= '9'); // strip depth
12981             if(time >= 0)
12982             while( *++sep >= '0' && *sep <= '9'); // strip time
12983             if(sec >= 0)
12984             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12985             if(deci >= 0)
12986             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12987             while(*sep == ' ') sep++;
12988         }
12989
12990         if( depth <= 0 ) {
12991             return text;
12992         }
12993
12994         if( time < 0 ) {
12995             time = -1;
12996         }
12997
12998         pvInfoList[index-1].depth = depth;
12999         pvInfoList[index-1].score = score;
13000         pvInfoList[index-1].time  = 10*time; // centi-sec
13001         if(*sep == '}') *sep = 0; else *--sep = '{';
13002     }
13003     return sep;
13004 }
13005
13006 void
13007 SendToProgram(message, cps)
13008      char *message;
13009      ChessProgramState *cps;
13010 {
13011     int count, outCount, error;
13012     char buf[MSG_SIZ];
13013
13014     if (cps->pr == NULL) return;
13015     Attention(cps);
13016     
13017     if (appData.debugMode) {
13018         TimeMark now;
13019         GetTimeMark(&now);
13020         fprintf(debugFP, "%ld >%-6s: %s", 
13021                 SubtractTimeMarks(&now, &programStartTime),
13022                 cps->which, message);
13023     }
13024     
13025     count = strlen(message);
13026     outCount = OutputToProcess(cps->pr, message, count, &error);
13027     if (outCount < count && !exiting 
13028                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13029         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13030         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13031             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13032                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13033                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13034             } else {
13035                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13036             }
13037             gameInfo.resultDetails = StrSave(buf);
13038         }
13039         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13040     }
13041 }
13042
13043 void
13044 ReceiveFromProgram(isr, closure, message, count, error)
13045      InputSourceRef isr;
13046      VOIDSTAR closure;
13047      char *message;
13048      int count;
13049      int error;
13050 {
13051     char *end_str;
13052     char buf[MSG_SIZ];
13053     ChessProgramState *cps = (ChessProgramState *)closure;
13054
13055     if (isr != cps->isr) return; /* Killed intentionally */
13056     if (count <= 0) {
13057         if (count == 0) {
13058             sprintf(buf,
13059                     _("Error: %s chess program (%s) exited unexpectedly"),
13060                     cps->which, cps->program);
13061         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13062                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13063                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13064                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13065                 } else {
13066                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13067                 }
13068                 gameInfo.resultDetails = StrSave(buf);
13069             }
13070             RemoveInputSource(cps->isr);
13071             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13072         } else {
13073             sprintf(buf,
13074                     _("Error reading from %s chess program (%s)"),
13075                     cps->which, cps->program);
13076             RemoveInputSource(cps->isr);
13077
13078             /* [AS] Program is misbehaving badly... kill it */
13079             if( count == -2 ) {
13080                 DestroyChildProcess( cps->pr, 9 );
13081                 cps->pr = NoProc;
13082             }
13083
13084             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13085         }
13086         return;
13087     }
13088     
13089     if ((end_str = strchr(message, '\r')) != NULL)
13090       *end_str = NULLCHAR;
13091     if ((end_str = strchr(message, '\n')) != NULL)
13092       *end_str = NULLCHAR;
13093     
13094     if (appData.debugMode) {
13095         TimeMark now; int print = 1;
13096         char *quote = ""; char c; int i;
13097
13098         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13099                 char start = message[0];
13100                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13101                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13102                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13103                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13104                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13105                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13106                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13107                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13108                         { quote = "# "; print = (appData.engineComments == 2); }
13109                 message[0] = start; // restore original message
13110         }
13111         if(print) {
13112                 GetTimeMark(&now);
13113                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13114                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13115                         quote,
13116                         message);
13117         }
13118     }
13119
13120     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13121     if (appData.icsEngineAnalyze) {
13122         if (strstr(message, "whisper") != NULL ||
13123              strstr(message, "kibitz") != NULL || 
13124             strstr(message, "tellics") != NULL) return;
13125     }
13126
13127     HandleMachineMove(message, cps);
13128 }
13129
13130
13131 void
13132 SendTimeControl(cps, mps, tc, inc, sd, st)
13133      ChessProgramState *cps;
13134      int mps, inc, sd, st;
13135      long tc;
13136 {
13137     char buf[MSG_SIZ];
13138     int seconds;
13139
13140     if( timeControl_2 > 0 ) {
13141         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13142             tc = timeControl_2;
13143         }
13144     }
13145     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13146     inc /= cps->timeOdds;
13147     st  /= cps->timeOdds;
13148
13149     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13150
13151     if (st > 0) {
13152       /* Set exact time per move, normally using st command */
13153       if (cps->stKludge) {
13154         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13155         seconds = st % 60;
13156         if (seconds == 0) {
13157           sprintf(buf, "level 1 %d\n", st/60);
13158         } else {
13159           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13160         }
13161       } else {
13162         sprintf(buf, "st %d\n", st);
13163       }
13164     } else {
13165       /* Set conventional or incremental time control, using level command */
13166       if (seconds == 0) {
13167         /* Note old gnuchess bug -- minutes:seconds used to not work.
13168            Fixed in later versions, but still avoid :seconds
13169            when seconds is 0. */
13170         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13171       } else {
13172         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13173                 seconds, inc/1000);
13174       }
13175     }
13176     SendToProgram(buf, cps);
13177
13178     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13179     /* Orthogonally, limit search to given depth */
13180     if (sd > 0) {
13181       if (cps->sdKludge) {
13182         sprintf(buf, "depth\n%d\n", sd);
13183       } else {
13184         sprintf(buf, "sd %d\n", sd);
13185       }
13186       SendToProgram(buf, cps);
13187     }
13188
13189     if(cps->nps > 0) { /* [HGM] nps */
13190         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13191         else {
13192                 sprintf(buf, "nps %d\n", cps->nps);
13193               SendToProgram(buf, cps);
13194         }
13195     }
13196 }
13197
13198 ChessProgramState *WhitePlayer()
13199 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13200 {
13201     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13202        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13203         return &second;
13204     return &first;
13205 }
13206
13207 void
13208 SendTimeRemaining(cps, machineWhite)
13209      ChessProgramState *cps;
13210      int /*boolean*/ machineWhite;
13211 {
13212     char message[MSG_SIZ];
13213     long time, otime;
13214
13215     /* Note: this routine must be called when the clocks are stopped
13216        or when they have *just* been set or switched; otherwise
13217        it will be off by the time since the current tick started.
13218     */
13219     if (machineWhite) {
13220         time = whiteTimeRemaining / 10;
13221         otime = blackTimeRemaining / 10;
13222     } else {
13223         time = blackTimeRemaining / 10;
13224         otime = whiteTimeRemaining / 10;
13225     }
13226     /* [HGM] translate opponent's time by time-odds factor */
13227     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13228     if (appData.debugMode) {
13229         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13230     }
13231
13232     if (time <= 0) time = 1;
13233     if (otime <= 0) otime = 1;
13234     
13235     sprintf(message, "time %ld\n", time);
13236     SendToProgram(message, cps);
13237
13238     sprintf(message, "otim %ld\n", otime);
13239     SendToProgram(message, cps);
13240 }
13241
13242 int
13243 BoolFeature(p, name, loc, cps)
13244      char **p;
13245      char *name;
13246      int *loc;
13247      ChessProgramState *cps;
13248 {
13249   char buf[MSG_SIZ];
13250   int len = strlen(name);
13251   int val;
13252   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13253     (*p) += len + 1;
13254     sscanf(*p, "%d", &val);
13255     *loc = (val != 0);
13256     while (**p && **p != ' ') (*p)++;
13257     sprintf(buf, "accepted %s\n", name);
13258     SendToProgram(buf, cps);
13259     return TRUE;
13260   }
13261   return FALSE;
13262 }
13263
13264 int
13265 IntFeature(p, name, loc, cps)
13266      char **p;
13267      char *name;
13268      int *loc;
13269      ChessProgramState *cps;
13270 {
13271   char buf[MSG_SIZ];
13272   int len = strlen(name);
13273   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13274     (*p) += len + 1;
13275     sscanf(*p, "%d", loc);
13276     while (**p && **p != ' ') (*p)++;
13277     sprintf(buf, "accepted %s\n", name);
13278     SendToProgram(buf, cps);
13279     return TRUE;
13280   }
13281   return FALSE;
13282 }
13283
13284 int
13285 StringFeature(p, name, loc, cps)
13286      char **p;
13287      char *name;
13288      char loc[];
13289      ChessProgramState *cps;
13290 {
13291   char buf[MSG_SIZ];
13292   int len = strlen(name);
13293   if (strncmp((*p), name, len) == 0
13294       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13295     (*p) += len + 2;
13296     sscanf(*p, "%[^\"]", loc);
13297     while (**p && **p != '\"') (*p)++;
13298     if (**p == '\"') (*p)++;
13299     sprintf(buf, "accepted %s\n", name);
13300     SendToProgram(buf, cps);
13301     return TRUE;
13302   }
13303   return FALSE;
13304 }
13305
13306 int 
13307 ParseOption(Option *opt, ChessProgramState *cps)
13308 // [HGM] options: process the string that defines an engine option, and determine
13309 // name, type, default value, and allowed value range
13310 {
13311         char *p, *q, buf[MSG_SIZ];
13312         int n, min = (-1)<<31, max = 1<<31, def;
13313
13314         if(p = strstr(opt->name, " -spin ")) {
13315             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13316             if(max < min) max = min; // enforce consistency
13317             if(def < min) def = min;
13318             if(def > max) def = max;
13319             opt->value = def;
13320             opt->min = min;
13321             opt->max = max;
13322             opt->type = Spin;
13323         } else if((p = strstr(opt->name, " -slider "))) {
13324             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13325             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13326             if(max < min) max = min; // enforce consistency
13327             if(def < min) def = min;
13328             if(def > max) def = max;
13329             opt->value = def;
13330             opt->min = min;
13331             opt->max = max;
13332             opt->type = Spin; // Slider;
13333         } else if((p = strstr(opt->name, " -string "))) {
13334             opt->textValue = p+9;
13335             opt->type = TextBox;
13336         } else if((p = strstr(opt->name, " -file "))) {
13337             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13338             opt->textValue = p+7;
13339             opt->type = TextBox; // FileName;
13340         } else if((p = strstr(opt->name, " -path "))) {
13341             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13342             opt->textValue = p+7;
13343             opt->type = TextBox; // PathName;
13344         } else if(p = strstr(opt->name, " -check ")) {
13345             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13346             opt->value = (def != 0);
13347             opt->type = CheckBox;
13348         } else if(p = strstr(opt->name, " -combo ")) {
13349             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13350             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13351             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13352             opt->value = n = 0;
13353             while(q = StrStr(q, " /// ")) {
13354                 n++; *q = 0;    // count choices, and null-terminate each of them
13355                 q += 5;
13356                 if(*q == '*') { // remember default, which is marked with * prefix
13357                     q++;
13358                     opt->value = n;
13359                 }
13360                 cps->comboList[cps->comboCnt++] = q;
13361             }
13362             cps->comboList[cps->comboCnt++] = NULL;
13363             opt->max = n + 1;
13364             opt->type = ComboBox;
13365         } else if(p = strstr(opt->name, " -button")) {
13366             opt->type = Button;
13367         } else if(p = strstr(opt->name, " -save")) {
13368             opt->type = SaveButton;
13369         } else return FALSE;
13370         *p = 0; // terminate option name
13371         // now look if the command-line options define a setting for this engine option.
13372         if(cps->optionSettings && cps->optionSettings[0])
13373             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13374         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13375                 sprintf(buf, "option %s", p);
13376                 if(p = strstr(buf, ",")) *p = 0;
13377                 strcat(buf, "\n");
13378                 SendToProgram(buf, cps);
13379         }
13380         return TRUE;
13381 }
13382
13383 void
13384 FeatureDone(cps, val)
13385      ChessProgramState* cps;
13386      int val;
13387 {
13388   DelayedEventCallback cb = GetDelayedEvent();
13389   if ((cb == InitBackEnd3 && cps == &first) ||
13390       (cb == TwoMachinesEventIfReady && cps == &second)) {
13391     CancelDelayedEvent();
13392     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13393   }
13394   cps->initDone = val;
13395 }
13396
13397 /* Parse feature command from engine */
13398 void
13399 ParseFeatures(args, cps)
13400      char* args;
13401      ChessProgramState *cps;  
13402 {
13403   char *p = args;
13404   char *q;
13405   int val;
13406   char buf[MSG_SIZ];
13407
13408   for (;;) {
13409     while (*p == ' ') p++;
13410     if (*p == NULLCHAR) return;
13411
13412     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13413     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13414     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13415     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13416     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13417     if (BoolFeature(&p, "reuse", &val, cps)) {
13418       /* Engine can disable reuse, but can't enable it if user said no */
13419       if (!val) cps->reuse = FALSE;
13420       continue;
13421     }
13422     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13423     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13424       if (gameMode == TwoMachinesPlay) {
13425         DisplayTwoMachinesTitle();
13426       } else {
13427         DisplayTitle("");
13428       }
13429       continue;
13430     }
13431     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13432     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13433     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13434     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13435     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13436     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13437     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13438     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13439     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13440     if (IntFeature(&p, "done", &val, cps)) {
13441       FeatureDone(cps, val);
13442       continue;
13443     }
13444     /* Added by Tord: */
13445     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13446     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13447     /* End of additions by Tord */
13448
13449     /* [HGM] added features: */
13450     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13451     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13452     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13453     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13454     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13455     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13456     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13457         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13458             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13459             SendToProgram(buf, cps);
13460             continue;
13461         }
13462         if(cps->nrOptions >= MAX_OPTIONS) {
13463             cps->nrOptions--;
13464             sprintf(buf, "%s engine has too many options\n", cps->which);
13465             DisplayError(buf, 0);
13466         }
13467         continue;
13468     }
13469     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13470     /* End of additions by HGM */
13471
13472     /* unknown feature: complain and skip */
13473     q = p;
13474     while (*q && *q != '=') q++;
13475     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13476     SendToProgram(buf, cps);
13477     p = q;
13478     if (*p == '=') {
13479       p++;
13480       if (*p == '\"') {
13481         p++;
13482         while (*p && *p != '\"') p++;
13483         if (*p == '\"') p++;
13484       } else {
13485         while (*p && *p != ' ') p++;
13486       }
13487     }
13488   }
13489
13490 }
13491
13492 void
13493 PeriodicUpdatesEvent(newState)
13494      int newState;
13495 {
13496     if (newState == appData.periodicUpdates)
13497       return;
13498
13499     appData.periodicUpdates=newState;
13500
13501     /* Display type changes, so update it now */
13502 //    DisplayAnalysis();
13503
13504     /* Get the ball rolling again... */
13505     if (newState) {
13506         AnalysisPeriodicEvent(1);
13507         StartAnalysisClock();
13508     }
13509 }
13510
13511 void
13512 PonderNextMoveEvent(newState)
13513      int newState;
13514 {
13515     if (newState == appData.ponderNextMove) return;
13516     if (gameMode == EditPosition) EditPositionDone(TRUE);
13517     if (newState) {
13518         SendToProgram("hard\n", &first);
13519         if (gameMode == TwoMachinesPlay) {
13520             SendToProgram("hard\n", &second);
13521         }
13522     } else {
13523         SendToProgram("easy\n", &first);
13524         thinkOutput[0] = NULLCHAR;
13525         if (gameMode == TwoMachinesPlay) {
13526             SendToProgram("easy\n", &second);
13527         }
13528     }
13529     appData.ponderNextMove = newState;
13530 }
13531
13532 void
13533 NewSettingEvent(option, command, value)
13534      char *command;
13535      int option, value;
13536 {
13537     char buf[MSG_SIZ];
13538
13539     if (gameMode == EditPosition) EditPositionDone(TRUE);
13540     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13541     SendToProgram(buf, &first);
13542     if (gameMode == TwoMachinesPlay) {
13543         SendToProgram(buf, &second);
13544     }
13545 }
13546
13547 void
13548 ShowThinkingEvent()
13549 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13550 {
13551     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13552     int newState = appData.showThinking
13553         // [HGM] thinking: other features now need thinking output as well
13554         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13555     
13556     if (oldState == newState) return;
13557     oldState = newState;
13558     if (gameMode == EditPosition) EditPositionDone(TRUE);
13559     if (oldState) {
13560         SendToProgram("post\n", &first);
13561         if (gameMode == TwoMachinesPlay) {
13562             SendToProgram("post\n", &second);
13563         }
13564     } else {
13565         SendToProgram("nopost\n", &first);
13566         thinkOutput[0] = NULLCHAR;
13567         if (gameMode == TwoMachinesPlay) {
13568             SendToProgram("nopost\n", &second);
13569         }
13570     }
13571 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13572 }
13573
13574 void
13575 AskQuestionEvent(title, question, replyPrefix, which)
13576      char *title; char *question; char *replyPrefix; char *which;
13577 {
13578   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13579   if (pr == NoProc) return;
13580   AskQuestion(title, question, replyPrefix, pr);
13581 }
13582
13583 void
13584 DisplayMove(moveNumber)
13585      int moveNumber;
13586 {
13587     char message[MSG_SIZ];
13588     char res[MSG_SIZ];
13589     char cpThinkOutput[MSG_SIZ];
13590
13591     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13592     
13593     if (moveNumber == forwardMostMove - 1 || 
13594         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13595
13596         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13597
13598         if (strchr(cpThinkOutput, '\n')) {
13599             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13600         }
13601     } else {
13602         *cpThinkOutput = NULLCHAR;
13603     }
13604
13605     /* [AS] Hide thinking from human user */
13606     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13607         *cpThinkOutput = NULLCHAR;
13608         if( thinkOutput[0] != NULLCHAR ) {
13609             int i;
13610
13611             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13612                 cpThinkOutput[i] = '.';
13613             }
13614             cpThinkOutput[i] = NULLCHAR;
13615             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13616         }
13617     }
13618
13619     if (moveNumber == forwardMostMove - 1 &&
13620         gameInfo.resultDetails != NULL) {
13621         if (gameInfo.resultDetails[0] == NULLCHAR) {
13622             sprintf(res, " %s", PGNResult(gameInfo.result));
13623         } else {
13624             sprintf(res, " {%s} %s",
13625                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13626         }
13627     } else {
13628         res[0] = NULLCHAR;
13629     }
13630
13631     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13632         DisplayMessage(res, cpThinkOutput);
13633     } else {
13634         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13635                 WhiteOnMove(moveNumber) ? " " : ".. ",
13636                 parseList[moveNumber], res);
13637         DisplayMessage(message, cpThinkOutput);
13638     }
13639 }
13640
13641 void
13642 DisplayComment(moveNumber, text)
13643      int moveNumber;
13644      char *text;
13645 {
13646     char title[MSG_SIZ];
13647     char buf[8000]; // comment can be long!
13648     int score, depth;
13649     
13650     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13651       strcpy(title, "Comment");
13652     } else {
13653       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13654               WhiteOnMove(moveNumber) ? " " : ".. ",
13655               parseList[moveNumber]);
13656     }
13657     // [HGM] PV info: display PV info together with (or as) comment
13658     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13659       if(text == NULL) text = "";                                           
13660       score = pvInfoList[moveNumber].score;
13661       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13662               depth, (pvInfoList[moveNumber].time+50)/100, text);
13663       text = buf;
13664     }
13665     if (text != NULL && (appData.autoDisplayComment || commentUp))
13666         CommentPopUp(title, text);
13667 }
13668
13669 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13670  * might be busy thinking or pondering.  It can be omitted if your
13671  * gnuchess is configured to stop thinking immediately on any user
13672  * input.  However, that gnuchess feature depends on the FIONREAD
13673  * ioctl, which does not work properly on some flavors of Unix.
13674  */
13675 void
13676 Attention(cps)
13677      ChessProgramState *cps;
13678 {
13679 #if ATTENTION
13680     if (!cps->useSigint) return;
13681     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13682     switch (gameMode) {
13683       case MachinePlaysWhite:
13684       case MachinePlaysBlack:
13685       case TwoMachinesPlay:
13686       case IcsPlayingWhite:
13687       case IcsPlayingBlack:
13688       case AnalyzeMode:
13689       case AnalyzeFile:
13690         /* Skip if we know it isn't thinking */
13691         if (!cps->maybeThinking) return;
13692         if (appData.debugMode)
13693           fprintf(debugFP, "Interrupting %s\n", cps->which);
13694         InterruptChildProcess(cps->pr);
13695         cps->maybeThinking = FALSE;
13696         break;
13697       default:
13698         break;
13699     }
13700 #endif /*ATTENTION*/
13701 }
13702
13703 int
13704 CheckFlags()
13705 {
13706     if (whiteTimeRemaining <= 0) {
13707         if (!whiteFlag) {
13708             whiteFlag = TRUE;
13709             if (appData.icsActive) {
13710                 if (appData.autoCallFlag &&
13711                     gameMode == IcsPlayingBlack && !blackFlag) {
13712                   SendToICS(ics_prefix);
13713                   SendToICS("flag\n");
13714                 }
13715             } else {
13716                 if (blackFlag) {
13717                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13718                 } else {
13719                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13720                     if (appData.autoCallFlag) {
13721                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13722                         return TRUE;
13723                     }
13724                 }
13725             }
13726         }
13727     }
13728     if (blackTimeRemaining <= 0) {
13729         if (!blackFlag) {
13730             blackFlag = TRUE;
13731             if (appData.icsActive) {
13732                 if (appData.autoCallFlag &&
13733                     gameMode == IcsPlayingWhite && !whiteFlag) {
13734                   SendToICS(ics_prefix);
13735                   SendToICS("flag\n");
13736                 }
13737             } else {
13738                 if (whiteFlag) {
13739                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13740                 } else {
13741                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13742                     if (appData.autoCallFlag) {
13743                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13744                         return TRUE;
13745                     }
13746                 }
13747             }
13748         }
13749     }
13750     return FALSE;
13751 }
13752
13753 void
13754 CheckTimeControl()
13755 {
13756     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13757         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13758
13759     /*
13760      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13761      */
13762     if ( !WhiteOnMove(forwardMostMove) )
13763         /* White made time control */
13764         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13765         /* [HGM] time odds: correct new time quota for time odds! */
13766                                             / WhitePlayer()->timeOdds;
13767       else
13768         /* Black made time control */
13769         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13770                                             / WhitePlayer()->other->timeOdds;
13771 }
13772
13773 void
13774 DisplayBothClocks()
13775 {
13776     int wom = gameMode == EditPosition ?
13777       !blackPlaysFirst : WhiteOnMove(currentMove);
13778     DisplayWhiteClock(whiteTimeRemaining, wom);
13779     DisplayBlackClock(blackTimeRemaining, !wom);
13780 }
13781
13782
13783 /* Timekeeping seems to be a portability nightmare.  I think everyone
13784    has ftime(), but I'm really not sure, so I'm including some ifdefs
13785    to use other calls if you don't.  Clocks will be less accurate if
13786    you have neither ftime nor gettimeofday.
13787 */
13788
13789 /* VS 2008 requires the #include outside of the function */
13790 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13791 #include <sys/timeb.h>
13792 #endif
13793
13794 /* Get the current time as a TimeMark */
13795 void
13796 GetTimeMark(tm)
13797      TimeMark *tm;
13798 {
13799 #if HAVE_GETTIMEOFDAY
13800
13801     struct timeval timeVal;
13802     struct timezone timeZone;
13803
13804     gettimeofday(&timeVal, &timeZone);
13805     tm->sec = (long) timeVal.tv_sec; 
13806     tm->ms = (int) (timeVal.tv_usec / 1000L);
13807
13808 #else /*!HAVE_GETTIMEOFDAY*/
13809 #if HAVE_FTIME
13810
13811 // include <sys/timeb.h> / moved to just above start of function
13812     struct timeb timeB;
13813
13814     ftime(&timeB);
13815     tm->sec = (long) timeB.time;
13816     tm->ms = (int) timeB.millitm;
13817
13818 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13819     tm->sec = (long) time(NULL);
13820     tm->ms = 0;
13821 #endif
13822 #endif
13823 }
13824
13825 /* Return the difference in milliseconds between two
13826    time marks.  We assume the difference will fit in a long!
13827 */
13828 long
13829 SubtractTimeMarks(tm2, tm1)
13830      TimeMark *tm2, *tm1;
13831 {
13832     return 1000L*(tm2->sec - tm1->sec) +
13833            (long) (tm2->ms - tm1->ms);
13834 }
13835
13836
13837 /*
13838  * Code to manage the game clocks.
13839  *
13840  * In tournament play, black starts the clock and then white makes a move.
13841  * We give the human user a slight advantage if he is playing white---the
13842  * clocks don't run until he makes his first move, so it takes zero time.
13843  * Also, we don't account for network lag, so we could get out of sync
13844  * with GNU Chess's clock -- but then, referees are always right.  
13845  */
13846
13847 static TimeMark tickStartTM;
13848 static long intendedTickLength;
13849
13850 long
13851 NextTickLength(timeRemaining)
13852      long timeRemaining;
13853 {
13854     long nominalTickLength, nextTickLength;
13855
13856     if (timeRemaining > 0L && timeRemaining <= 10000L)
13857       nominalTickLength = 100L;
13858     else
13859       nominalTickLength = 1000L;
13860     nextTickLength = timeRemaining % nominalTickLength;
13861     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13862
13863     return nextTickLength;
13864 }
13865
13866 /* Adjust clock one minute up or down */
13867 void
13868 AdjustClock(Boolean which, int dir)
13869 {
13870     if(which) blackTimeRemaining += 60000*dir;
13871     else      whiteTimeRemaining += 60000*dir;
13872     DisplayBothClocks();
13873 }
13874
13875 /* Stop clocks and reset to a fresh time control */
13876 void
13877 ResetClocks() 
13878 {
13879     (void) StopClockTimer();
13880     if (appData.icsActive) {
13881         whiteTimeRemaining = blackTimeRemaining = 0;
13882     } else if (searchTime) {
13883         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13884         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13885     } else { /* [HGM] correct new time quote for time odds */
13886         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13887         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13888     }
13889     if (whiteFlag || blackFlag) {
13890         DisplayTitle("");
13891         whiteFlag = blackFlag = FALSE;
13892     }
13893     DisplayBothClocks();
13894 }
13895
13896 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13897
13898 /* Decrement running clock by amount of time that has passed */
13899 void
13900 DecrementClocks()
13901 {
13902     long timeRemaining;
13903     long lastTickLength, fudge;
13904     TimeMark now;
13905
13906     if (!appData.clockMode) return;
13907     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13908         
13909     GetTimeMark(&now);
13910
13911     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13912
13913     /* Fudge if we woke up a little too soon */
13914     fudge = intendedTickLength - lastTickLength;
13915     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13916
13917     if (WhiteOnMove(forwardMostMove)) {
13918         if(whiteNPS >= 0) lastTickLength = 0;
13919         timeRemaining = whiteTimeRemaining -= lastTickLength;
13920         DisplayWhiteClock(whiteTimeRemaining - fudge,
13921                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13922     } else {
13923         if(blackNPS >= 0) lastTickLength = 0;
13924         timeRemaining = blackTimeRemaining -= lastTickLength;
13925         DisplayBlackClock(blackTimeRemaining - fudge,
13926                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13927     }
13928
13929     if (CheckFlags()) return;
13930         
13931     tickStartTM = now;
13932     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13933     StartClockTimer(intendedTickLength);
13934
13935     /* if the time remaining has fallen below the alarm threshold, sound the
13936      * alarm. if the alarm has sounded and (due to a takeback or time control
13937      * with increment) the time remaining has increased to a level above the
13938      * threshold, reset the alarm so it can sound again. 
13939      */
13940     
13941     if (appData.icsActive && appData.icsAlarm) {
13942
13943         /* make sure we are dealing with the user's clock */
13944         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13945                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13946            )) return;
13947
13948         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13949             alarmSounded = FALSE;
13950         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13951             PlayAlarmSound();
13952             alarmSounded = TRUE;
13953         }
13954     }
13955 }
13956
13957
13958 /* A player has just moved, so stop the previously running
13959    clock and (if in clock mode) start the other one.
13960    We redisplay both clocks in case we're in ICS mode, because
13961    ICS gives us an update to both clocks after every move.
13962    Note that this routine is called *after* forwardMostMove
13963    is updated, so the last fractional tick must be subtracted
13964    from the color that is *not* on move now.
13965 */
13966 void
13967 SwitchClocks()
13968 {
13969     long lastTickLength;
13970     TimeMark now;
13971     int flagged = FALSE;
13972
13973     GetTimeMark(&now);
13974
13975     if (StopClockTimer() && appData.clockMode) {
13976         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13977         if (WhiteOnMove(forwardMostMove)) {
13978             if(blackNPS >= 0) lastTickLength = 0;
13979             blackTimeRemaining -= lastTickLength;
13980            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13981 //         if(pvInfoList[forwardMostMove-1].time == -1)
13982                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13983                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13984         } else {
13985            if(whiteNPS >= 0) lastTickLength = 0;
13986            whiteTimeRemaining -= lastTickLength;
13987            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13988 //         if(pvInfoList[forwardMostMove-1].time == -1)
13989                  pvInfoList[forwardMostMove-1].time = 
13990                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13991         }
13992         flagged = CheckFlags();
13993     }
13994     CheckTimeControl();
13995
13996     if (flagged || !appData.clockMode) return;
13997
13998     switch (gameMode) {
13999       case MachinePlaysBlack:
14000       case MachinePlaysWhite:
14001       case BeginningOfGame:
14002         if (pausing) return;
14003         break;
14004
14005       case EditGame:
14006       case PlayFromGameFile:
14007       case IcsExamining:
14008         return;
14009
14010       default:
14011         break;
14012     }
14013
14014     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14015         if(WhiteOnMove(forwardMostMove))
14016              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14017         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14018     }
14019
14020     tickStartTM = now;
14021     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14022       whiteTimeRemaining : blackTimeRemaining);
14023     StartClockTimer(intendedTickLength);
14024 }
14025         
14026
14027 /* Stop both clocks */
14028 void
14029 StopClocks()
14030 {       
14031     long lastTickLength;
14032     TimeMark now;
14033
14034     if (!StopClockTimer()) return;
14035     if (!appData.clockMode) return;
14036
14037     GetTimeMark(&now);
14038
14039     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14040     if (WhiteOnMove(forwardMostMove)) {
14041         if(whiteNPS >= 0) lastTickLength = 0;
14042         whiteTimeRemaining -= lastTickLength;
14043         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14044     } else {
14045         if(blackNPS >= 0) lastTickLength = 0;
14046         blackTimeRemaining -= lastTickLength;
14047         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14048     }
14049     CheckFlags();
14050 }
14051         
14052 /* Start clock of player on move.  Time may have been reset, so
14053    if clock is already running, stop and restart it. */
14054 void
14055 StartClocks()
14056 {
14057     (void) StopClockTimer(); /* in case it was running already */
14058     DisplayBothClocks();
14059     if (CheckFlags()) return;
14060
14061     if (!appData.clockMode) return;
14062     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14063
14064     GetTimeMark(&tickStartTM);
14065     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14066       whiteTimeRemaining : blackTimeRemaining);
14067
14068    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14069     whiteNPS = blackNPS = -1; 
14070     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14071        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14072         whiteNPS = first.nps;
14073     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14074        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14075         blackNPS = first.nps;
14076     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14077         whiteNPS = second.nps;
14078     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14079         blackNPS = second.nps;
14080     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14081
14082     StartClockTimer(intendedTickLength);
14083 }
14084
14085 char *
14086 TimeString(ms)
14087      long ms;
14088 {
14089     long second, minute, hour, day;
14090     char *sign = "";
14091     static char buf[32];
14092     
14093     if (ms > 0 && ms <= 9900) {
14094       /* convert milliseconds to tenths, rounding up */
14095       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14096
14097       sprintf(buf, " %03.1f ", tenths/10.0);
14098       return buf;
14099     }
14100
14101     /* convert milliseconds to seconds, rounding up */
14102     /* use floating point to avoid strangeness of integer division
14103        with negative dividends on many machines */
14104     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14105
14106     if (second < 0) {
14107         sign = "-";
14108         second = -second;
14109     }
14110     
14111     day = second / (60 * 60 * 24);
14112     second = second % (60 * 60 * 24);
14113     hour = second / (60 * 60);
14114     second = second % (60 * 60);
14115     minute = second / 60;
14116     second = second % 60;
14117     
14118     if (day > 0)
14119       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14120               sign, day, hour, minute, second);
14121     else if (hour > 0)
14122       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14123     else
14124       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14125     
14126     return buf;
14127 }
14128
14129
14130 /*
14131  * This is necessary because some C libraries aren't ANSI C compliant yet.
14132  */
14133 char *
14134 StrStr(string, match)
14135      char *string, *match;
14136 {
14137     int i, length;
14138     
14139     length = strlen(match);
14140     
14141     for (i = strlen(string) - length; i >= 0; i--, string++)
14142       if (!strncmp(match, string, length))
14143         return string;
14144     
14145     return NULL;
14146 }
14147
14148 char *
14149 StrCaseStr(string, match)
14150      char *string, *match;
14151 {
14152     int i, j, length;
14153     
14154     length = strlen(match);
14155     
14156     for (i = strlen(string) - length; i >= 0; i--, string++) {
14157         for (j = 0; j < length; j++) {
14158             if (ToLower(match[j]) != ToLower(string[j]))
14159               break;
14160         }
14161         if (j == length) return string;
14162     }
14163
14164     return NULL;
14165 }
14166
14167 #ifndef _amigados
14168 int
14169 StrCaseCmp(s1, s2)
14170      char *s1, *s2;
14171 {
14172     char c1, c2;
14173     
14174     for (;;) {
14175         c1 = ToLower(*s1++);
14176         c2 = ToLower(*s2++);
14177         if (c1 > c2) return 1;
14178         if (c1 < c2) return -1;
14179         if (c1 == NULLCHAR) return 0;
14180     }
14181 }
14182
14183
14184 int
14185 ToLower(c)
14186      int c;
14187 {
14188     return isupper(c) ? tolower(c) : c;
14189 }
14190
14191
14192 int
14193 ToUpper(c)
14194      int c;
14195 {
14196     return islower(c) ? toupper(c) : c;
14197 }
14198 #endif /* !_amigados    */
14199
14200 char *
14201 StrSave(s)
14202      char *s;
14203 {
14204     char *ret;
14205
14206     if ((ret = (char *) malloc(strlen(s) + 1))) {
14207         strcpy(ret, s);
14208     }
14209     return ret;
14210 }
14211
14212 char *
14213 StrSavePtr(s, savePtr)
14214      char *s, **savePtr;
14215 {
14216     if (*savePtr) {
14217         free(*savePtr);
14218     }
14219     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14220         strcpy(*savePtr, s);
14221     }
14222     return(*savePtr);
14223 }
14224
14225 char *
14226 PGNDate()
14227 {
14228     time_t clock;
14229     struct tm *tm;
14230     char buf[MSG_SIZ];
14231
14232     clock = time((time_t *)NULL);
14233     tm = localtime(&clock);
14234     sprintf(buf, "%04d.%02d.%02d",
14235             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14236     return StrSave(buf);
14237 }
14238
14239
14240 char *
14241 PositionToFEN(move, overrideCastling)
14242      int move;
14243      char *overrideCastling;
14244 {
14245     int i, j, fromX, fromY, toX, toY;
14246     int whiteToPlay;
14247     char buf[128];
14248     char *p, *q;
14249     int emptycount;
14250     ChessSquare piece;
14251
14252     whiteToPlay = (gameMode == EditPosition) ?
14253       !blackPlaysFirst : (move % 2 == 0);
14254     p = buf;
14255
14256     /* Piece placement data */
14257     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14258         emptycount = 0;
14259         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14260             if (boards[move][i][j] == EmptySquare) {
14261                 emptycount++;
14262             } else { ChessSquare piece = boards[move][i][j];
14263                 if (emptycount > 0) {
14264                     if(emptycount<10) /* [HGM] can be >= 10 */
14265                         *p++ = '0' + emptycount;
14266                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14267                     emptycount = 0;
14268                 }
14269                 if(PieceToChar(piece) == '+') {
14270                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14271                     *p++ = '+';
14272                     piece = (ChessSquare)(DEMOTED piece);
14273                 } 
14274                 *p++ = PieceToChar(piece);
14275                 if(p[-1] == '~') {
14276                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14277                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14278                     *p++ = '~';
14279                 }
14280             }
14281         }
14282         if (emptycount > 0) {
14283             if(emptycount<10) /* [HGM] can be >= 10 */
14284                 *p++ = '0' + emptycount;
14285             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14286             emptycount = 0;
14287         }
14288         *p++ = '/';
14289     }
14290     *(p - 1) = ' ';
14291
14292     /* [HGM] print Crazyhouse or Shogi holdings */
14293     if( gameInfo.holdingsWidth ) {
14294         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14295         q = p;
14296         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14297             piece = boards[move][i][BOARD_WIDTH-1];
14298             if( piece != EmptySquare )
14299               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14300                   *p++ = PieceToChar(piece);
14301         }
14302         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14303             piece = boards[move][BOARD_HEIGHT-i-1][0];
14304             if( piece != EmptySquare )
14305               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14306                   *p++ = PieceToChar(piece);
14307         }
14308
14309         if( q == p ) *p++ = '-';
14310         *p++ = ']';
14311         *p++ = ' ';
14312     }
14313
14314     /* Active color */
14315     *p++ = whiteToPlay ? 'w' : 'b';
14316     *p++ = ' ';
14317
14318   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14319     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14320   } else {
14321   if(nrCastlingRights) {
14322      q = p;
14323      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14324        /* [HGM] write directly from rights */
14325            if(boards[move][CASTLING][2] != NoRights &&
14326               boards[move][CASTLING][0] != NoRights   )
14327                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14328            if(boards[move][CASTLING][2] != NoRights &&
14329               boards[move][CASTLING][1] != NoRights   )
14330                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14331            if(boards[move][CASTLING][5] != NoRights &&
14332               boards[move][CASTLING][3] != NoRights   )
14333                 *p++ = boards[move][CASTLING][3] + AAA;
14334            if(boards[move][CASTLING][5] != NoRights &&
14335               boards[move][CASTLING][4] != NoRights   )
14336                 *p++ = boards[move][CASTLING][4] + AAA;
14337      } else {
14338
14339         /* [HGM] write true castling rights */
14340         if( nrCastlingRights == 6 ) {
14341             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14342                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14343             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14344                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14345             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14346                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14347             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14348                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14349         }
14350      }
14351      if (q == p) *p++ = '-'; /* No castling rights */
14352      *p++ = ' ';
14353   }
14354
14355   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14356      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14357     /* En passant target square */
14358     if (move > backwardMostMove) {
14359         fromX = moveList[move - 1][0] - AAA;
14360         fromY = moveList[move - 1][1] - ONE;
14361         toX = moveList[move - 1][2] - AAA;
14362         toY = moveList[move - 1][3] - ONE;
14363         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14364             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14365             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14366             fromX == toX) {
14367             /* 2-square pawn move just happened */
14368             *p++ = toX + AAA;
14369             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14370         } else {
14371             *p++ = '-';
14372         }
14373     } else if(move == backwardMostMove) {
14374         // [HGM] perhaps we should always do it like this, and forget the above?
14375         if((signed char)boards[move][EP_STATUS] >= 0) {
14376             *p++ = boards[move][EP_STATUS] + AAA;
14377             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14378         } else {
14379             *p++ = '-';
14380         }
14381     } else {
14382         *p++ = '-';
14383     }
14384     *p++ = ' ';
14385   }
14386   }
14387
14388     /* [HGM] find reversible plies */
14389     {   int i = 0, j=move;
14390
14391         if (appData.debugMode) { int k;
14392             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14393             for(k=backwardMostMove; k<=forwardMostMove; k++)
14394                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14395
14396         }
14397
14398         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14399         if( j == backwardMostMove ) i += initialRulePlies;
14400         sprintf(p, "%d ", i);
14401         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14402     }
14403     /* Fullmove number */
14404     sprintf(p, "%d", (move / 2) + 1);
14405     
14406     return StrSave(buf);
14407 }
14408
14409 Boolean
14410 ParseFEN(board, blackPlaysFirst, fen)
14411     Board board;
14412      int *blackPlaysFirst;
14413      char *fen;
14414 {
14415     int i, j;
14416     char *p;
14417     int emptycount;
14418     ChessSquare piece;
14419
14420     p = fen;
14421
14422     /* [HGM] by default clear Crazyhouse holdings, if present */
14423     if(gameInfo.holdingsWidth) {
14424        for(i=0; i<BOARD_HEIGHT; i++) {
14425            board[i][0]             = EmptySquare; /* black holdings */
14426            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14427            board[i][1]             = (ChessSquare) 0; /* black counts */
14428            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14429        }
14430     }
14431
14432     /* Piece placement data */
14433     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14434         j = 0;
14435         for (;;) {
14436             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14437                 if (*p == '/') p++;
14438                 emptycount = gameInfo.boardWidth - j;
14439                 while (emptycount--)
14440                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14441                 break;
14442 #if(BOARD_FILES >= 10)
14443             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14444                 p++; emptycount=10;
14445                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14446                 while (emptycount--)
14447                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14448 #endif
14449             } else if (isdigit(*p)) {
14450                 emptycount = *p++ - '0';
14451                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14452                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14453                 while (emptycount--)
14454                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14455             } else if (*p == '+' || isalpha(*p)) {
14456                 if (j >= gameInfo.boardWidth) return FALSE;
14457                 if(*p=='+') {
14458                     piece = CharToPiece(*++p);
14459                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14460                     piece = (ChessSquare) (PROMOTED piece ); p++;
14461                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14462                 } else piece = CharToPiece(*p++);
14463
14464                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14465                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14466                     piece = (ChessSquare) (PROMOTED piece);
14467                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14468                     p++;
14469                 }
14470                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14471             } else {
14472                 return FALSE;
14473             }
14474         }
14475     }
14476     while (*p == '/' || *p == ' ') p++;
14477
14478     /* [HGM] look for Crazyhouse holdings here */
14479     while(*p==' ') p++;
14480     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14481         if(*p == '[') p++;
14482         if(*p == '-' ) *p++; /* empty holdings */ else {
14483             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14484             /* if we would allow FEN reading to set board size, we would   */
14485             /* have to add holdings and shift the board read so far here   */
14486             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14487                 *p++;
14488                 if((int) piece >= (int) BlackPawn ) {
14489                     i = (int)piece - (int)BlackPawn;
14490                     i = PieceToNumber((ChessSquare)i);
14491                     if( i >= gameInfo.holdingsSize ) return FALSE;
14492                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14493                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14494                 } else {
14495                     i = (int)piece - (int)WhitePawn;
14496                     i = PieceToNumber((ChessSquare)i);
14497                     if( i >= gameInfo.holdingsSize ) return FALSE;
14498                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14499                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14500                 }
14501             }
14502         }
14503         if(*p == ']') *p++;
14504     }
14505
14506     while(*p == ' ') p++;
14507
14508     /* Active color */
14509     switch (*p++) {
14510       case 'w':
14511         *blackPlaysFirst = FALSE;
14512         break;
14513       case 'b': 
14514         *blackPlaysFirst = TRUE;
14515         break;
14516       default:
14517         return FALSE;
14518     }
14519
14520     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14521     /* return the extra info in global variiables             */
14522
14523     /* set defaults in case FEN is incomplete */
14524     board[EP_STATUS] = EP_UNKNOWN;
14525     for(i=0; i<nrCastlingRights; i++ ) {
14526         board[CASTLING][i] =
14527             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14528     }   /* assume possible unless obviously impossible */
14529     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14530     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14531     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14532                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14533     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14534     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14535     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14536                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14537     FENrulePlies = 0;
14538
14539     while(*p==' ') p++;
14540     if(nrCastlingRights) {
14541       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14542           /* castling indicator present, so default becomes no castlings */
14543           for(i=0; i<nrCastlingRights; i++ ) {
14544                  board[CASTLING][i] = NoRights;
14545           }
14546       }
14547       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14548              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14549              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14550              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14551         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14552
14553         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14554             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14555             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14556         }
14557         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14558             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14559         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14560                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14561         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14562                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14563         switch(c) {
14564           case'K':
14565               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14566               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14567               board[CASTLING][2] = whiteKingFile;
14568               break;
14569           case'Q':
14570               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14571               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14572               board[CASTLING][2] = whiteKingFile;
14573               break;
14574           case'k':
14575               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14576               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14577               board[CASTLING][5] = blackKingFile;
14578               break;
14579           case'q':
14580               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14581               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14582               board[CASTLING][5] = blackKingFile;
14583           case '-':
14584               break;
14585           default: /* FRC castlings */
14586               if(c >= 'a') { /* black rights */
14587                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14588                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14589                   if(i == BOARD_RGHT) break;
14590                   board[CASTLING][5] = i;
14591                   c -= AAA;
14592                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14593                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14594                   if(c > i)
14595                       board[CASTLING][3] = c;
14596                   else
14597                       board[CASTLING][4] = c;
14598               } else { /* white rights */
14599                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14600                     if(board[0][i] == WhiteKing) break;
14601                   if(i == BOARD_RGHT) break;
14602                   board[CASTLING][2] = i;
14603                   c -= AAA - 'a' + 'A';
14604                   if(board[0][c] >= WhiteKing) break;
14605                   if(c > i)
14606                       board[CASTLING][0] = c;
14607                   else
14608                       board[CASTLING][1] = c;
14609               }
14610         }
14611       }
14612       for(i=0; i<nrCastlingRights; i++)
14613         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14614     if (appData.debugMode) {
14615         fprintf(debugFP, "FEN castling rights:");
14616         for(i=0; i<nrCastlingRights; i++)
14617         fprintf(debugFP, " %d", board[CASTLING][i]);
14618         fprintf(debugFP, "\n");
14619     }
14620
14621       while(*p==' ') p++;
14622     }
14623
14624     /* read e.p. field in games that know e.p. capture */
14625     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14626        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14627       if(*p=='-') {
14628         p++; board[EP_STATUS] = EP_NONE;
14629       } else {
14630          char c = *p++ - AAA;
14631
14632          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14633          if(*p >= '0' && *p <='9') *p++;
14634          board[EP_STATUS] = c;
14635       }
14636     }
14637
14638
14639     if(sscanf(p, "%d", &i) == 1) {
14640         FENrulePlies = i; /* 50-move ply counter */
14641         /* (The move number is still ignored)    */
14642     }
14643
14644     return TRUE;
14645 }
14646       
14647 void
14648 EditPositionPasteFEN(char *fen)
14649 {
14650   if (fen != NULL) {
14651     Board initial_position;
14652
14653     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14654       DisplayError(_("Bad FEN position in clipboard"), 0);
14655       return ;
14656     } else {
14657       int savedBlackPlaysFirst = blackPlaysFirst;
14658       EditPositionEvent();
14659       blackPlaysFirst = savedBlackPlaysFirst;
14660       CopyBoard(boards[0], initial_position);
14661       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14662       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14663       DisplayBothClocks();
14664       DrawPosition(FALSE, boards[currentMove]);
14665     }
14666   }
14667 }
14668
14669 static char cseq[12] = "\\   ";
14670
14671 Boolean set_cont_sequence(char *new_seq)
14672 {
14673     int len;
14674     Boolean ret;
14675
14676     // handle bad attempts to set the sequence
14677         if (!new_seq)
14678                 return 0; // acceptable error - no debug
14679
14680     len = strlen(new_seq);
14681     ret = (len > 0) && (len < sizeof(cseq));
14682     if (ret)
14683         strcpy(cseq, new_seq);
14684     else if (appData.debugMode)
14685         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14686     return ret;
14687 }
14688
14689 /*
14690     reformat a source message so words don't cross the width boundary.  internal
14691     newlines are not removed.  returns the wrapped size (no null character unless
14692     included in source message).  If dest is NULL, only calculate the size required
14693     for the dest buffer.  lp argument indicats line position upon entry, and it's
14694     passed back upon exit.
14695 */
14696 int wrap(char *dest, char *src, int count, int width, int *lp)
14697 {
14698     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14699
14700     cseq_len = strlen(cseq);
14701     old_line = line = *lp;
14702     ansi = len = clen = 0;
14703
14704     for (i=0; i < count; i++)
14705     {
14706         if (src[i] == '\033')
14707             ansi = 1;
14708
14709         // if we hit the width, back up
14710         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14711         {
14712             // store i & len in case the word is too long
14713             old_i = i, old_len = len;
14714
14715             // find the end of the last word
14716             while (i && src[i] != ' ' && src[i] != '\n')
14717             {
14718                 i--;
14719                 len--;
14720             }
14721
14722             // word too long?  restore i & len before splitting it
14723             if ((old_i-i+clen) >= width)
14724             {
14725                 i = old_i;
14726                 len = old_len;
14727             }
14728
14729             // extra space?
14730             if (i && src[i-1] == ' ')
14731                 len--;
14732
14733             if (src[i] != ' ' && src[i] != '\n')
14734             {
14735                 i--;
14736                 if (len)
14737                     len--;
14738             }
14739
14740             // now append the newline and continuation sequence
14741             if (dest)
14742                 dest[len] = '\n';
14743             len++;
14744             if (dest)
14745                 strncpy(dest+len, cseq, cseq_len);
14746             len += cseq_len;
14747             line = cseq_len;
14748             clen = cseq_len;
14749             continue;
14750         }
14751
14752         if (dest)
14753             dest[len] = src[i];
14754         len++;
14755         if (!ansi)
14756             line++;
14757         if (src[i] == '\n')
14758             line = 0;
14759         if (src[i] == 'm')
14760             ansi = 0;
14761     }
14762     if (dest && appData.debugMode)
14763     {
14764         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14765             count, width, line, len, *lp);
14766         show_bytes(debugFP, src, count);
14767         fprintf(debugFP, "\ndest: ");
14768         show_bytes(debugFP, dest, len);
14769         fprintf(debugFP, "\n");
14770     }
14771     *lp = dest ? line : old_line;
14772
14773     return len;
14774 }
14775
14776 // [HGM] vari: routines for shelving variations
14777
14778 void 
14779 PushTail(int firstMove, int lastMove)
14780 {
14781         int i, j, nrMoves = lastMove - firstMove;
14782
14783         if(appData.icsActive) { // only in local mode
14784                 forwardMostMove = currentMove; // mimic old ICS behavior
14785                 return;
14786         }
14787         if(storedGames >= MAX_VARIATIONS-1) return;
14788
14789         // push current tail of game on stack
14790         savedResult[storedGames] = gameInfo.result;
14791         savedDetails[storedGames] = gameInfo.resultDetails;
14792         gameInfo.resultDetails = NULL;
14793         savedFirst[storedGames] = firstMove;
14794         savedLast [storedGames] = lastMove;
14795         savedFramePtr[storedGames] = framePtr;
14796         framePtr -= nrMoves; // reserve space for the boards
14797         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14798             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14799             for(j=0; j<MOVE_LEN; j++)
14800                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14801             for(j=0; j<2*MOVE_LEN; j++)
14802                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14803             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14804             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14805             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14806             pvInfoList[firstMove+i-1].depth = 0;
14807             commentList[framePtr+i] = commentList[firstMove+i];
14808             commentList[firstMove+i] = NULL;
14809         }
14810
14811         storedGames++;
14812         forwardMostMove = currentMove; // truncte game so we can start variation
14813         if(storedGames == 1) GreyRevert(FALSE);
14814 }
14815
14816 Boolean
14817 PopTail(Boolean annotate)
14818 {
14819         int i, j, nrMoves;
14820         char buf[8000], moveBuf[20];
14821
14822         if(appData.icsActive) return FALSE; // only in local mode
14823         if(!storedGames) return FALSE; // sanity
14824
14825         storedGames--;
14826         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14827         nrMoves = savedLast[storedGames] - currentMove;
14828         if(annotate) {
14829                 int cnt = 10;
14830                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14831                 else strcpy(buf, "(");
14832                 for(i=currentMove; i<forwardMostMove; i++) {
14833                         if(WhiteOnMove(i))
14834                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14835                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14836                         strcat(buf, moveBuf);
14837                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14838                 }
14839                 strcat(buf, ")");
14840         }
14841         for(i=1; i<nrMoves; i++) { // copy last variation back
14842             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14843             for(j=0; j<MOVE_LEN; j++)
14844                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14845             for(j=0; j<2*MOVE_LEN; j++)
14846                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14847             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14848             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14849             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14850             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14851             commentList[currentMove+i] = commentList[framePtr+i];
14852             commentList[framePtr+i] = NULL;
14853         }
14854         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14855         framePtr = savedFramePtr[storedGames];
14856         gameInfo.result = savedResult[storedGames];
14857         if(gameInfo.resultDetails != NULL) {
14858             free(gameInfo.resultDetails);
14859       }
14860         gameInfo.resultDetails = savedDetails[storedGames];
14861         forwardMostMove = currentMove + nrMoves;
14862         if(storedGames == 0) GreyRevert(TRUE);
14863         return TRUE;
14864 }
14865
14866 void 
14867 CleanupTail()
14868 {       // remove all shelved variations
14869         int i;
14870         for(i=0; i<storedGames; i++) {
14871             if(savedDetails[i])
14872                 free(savedDetails[i]);
14873             savedDetails[i] = NULL;
14874         }
14875         for(i=framePtr; i<MAX_MOVES; i++) {
14876                 if(commentList[i]) free(commentList[i]);
14877                 commentList[i] = NULL;
14878         }
14879         framePtr = MAX_MOVES-1;
14880         storedGames = 0;
14881 }