updated year in copyright info
[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, 2010 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 Boolean
5553 OnlyMove(int *x, int *y) {
5554     DisambiguateClosure cl;
5555     if (appData.zippyPlay) return FALSE;
5556     switch(gameMode) {
5557       case MachinePlaysBlack:
5558       case IcsPlayingWhite:
5559       case BeginningOfGame:
5560         if(!WhiteOnMove(currentMove)) return FALSE;
5561         break;
5562       case MachinePlaysWhite:
5563       case IcsPlayingBlack:
5564         if(WhiteOnMove(currentMove)) return FALSE;
5565         break;
5566       default:
5567         return FALSE;
5568     }
5569     cl.pieceIn = EmptySquare; 
5570     cl.rfIn = *y;
5571     cl.ffIn = *x;
5572     cl.rtIn = -1;
5573     cl.ftIn = -1;
5574     cl.promoCharIn = NULLCHAR;
5575     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5576     if(cl.kind == NormalMove) {
5577       fromX = cl.ff;
5578       fromY = cl.rf;
5579       *x = cl.ft;
5580       *y = cl.rt;
5581       return TRUE;
5582     }
5583     if(cl.kind != ImpossibleMove) return FALSE;
5584     cl.pieceIn = EmptySquare;
5585     cl.rfIn = -1;
5586     cl.ffIn = -1;
5587     cl.rtIn = *y;
5588     cl.ftIn = *x;
5589     cl.promoCharIn = NULLCHAR;
5590     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5591     if(cl.kind == NormalMove) {
5592       fromX = cl.ff;
5593       fromY = cl.rf;
5594       *x = cl.ft;
5595       *y = cl.rt;
5596       return TRUE;
5597     }
5598     return FALSE;
5599 }
5600
5601 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5602 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5603 int lastLoadGameUseList = FALSE;
5604 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5605 ChessMove lastLoadGameStart = (ChessMove) 0;
5606
5607 ChessMove
5608 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5609      int fromX, fromY, toX, toY;
5610      int promoChar;
5611      Boolean captureOwn;
5612 {
5613     ChessMove moveType;
5614     ChessSquare pdown, pup;
5615
5616     /* Check if the user is playing in turn.  This is complicated because we
5617        let the user "pick up" a piece before it is his turn.  So the piece he
5618        tried to pick up may have been captured by the time he puts it down!
5619        Therefore we use the color the user is supposed to be playing in this
5620        test, not the color of the piece that is currently on the starting
5621        square---except in EditGame mode, where the user is playing both
5622        sides; fortunately there the capture race can't happen.  (It can
5623        now happen in IcsExamining mode, but that's just too bad.  The user
5624        will get a somewhat confusing message in that case.)
5625        */
5626
5627     switch (gameMode) {
5628       case PlayFromGameFile:
5629       case AnalyzeFile:
5630       case TwoMachinesPlay:
5631       case EndOfGame:
5632       case IcsObserving:
5633       case IcsIdle:
5634         /* We switched into a game mode where moves are not accepted,
5635            perhaps while the mouse button was down. */
5636         return ImpossibleMove;
5637
5638       case MachinePlaysWhite:
5639         /* User is moving for Black */
5640         if (WhiteOnMove(currentMove)) {
5641             DisplayMoveError(_("It is White's turn"));
5642             return ImpossibleMove;
5643         }
5644         break;
5645
5646       case MachinePlaysBlack:
5647         /* User is moving for White */
5648         if (!WhiteOnMove(currentMove)) {
5649             DisplayMoveError(_("It is Black's turn"));
5650             return ImpossibleMove;
5651         }
5652         break;
5653
5654       case EditGame:
5655       case IcsExamining:
5656       case BeginningOfGame:
5657       case AnalyzeMode:
5658       case Training:
5659         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5660             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5661             /* User is moving for Black */
5662             if (WhiteOnMove(currentMove)) {
5663                 DisplayMoveError(_("It is White's turn"));
5664                 return ImpossibleMove;
5665             }
5666         } else {
5667             /* User is moving for White */
5668             if (!WhiteOnMove(currentMove)) {
5669                 DisplayMoveError(_("It is Black's turn"));
5670                 return ImpossibleMove;
5671             }
5672         }
5673         break;
5674
5675       case IcsPlayingBlack:
5676         /* User is moving for Black */
5677         if (WhiteOnMove(currentMove)) {
5678             if (!appData.premove) {
5679                 DisplayMoveError(_("It is White's turn"));
5680             } else if (toX >= 0 && toY >= 0) {
5681                 premoveToX = toX;
5682                 premoveToY = toY;
5683                 premoveFromX = fromX;
5684                 premoveFromY = fromY;
5685                 premovePromoChar = promoChar;
5686                 gotPremove = 1;
5687                 if (appData.debugMode) 
5688                     fprintf(debugFP, "Got premove: fromX %d,"
5689                             "fromY %d, toX %d, toY %d\n",
5690                             fromX, fromY, toX, toY);
5691             }
5692             return ImpossibleMove;
5693         }
5694         break;
5695
5696       case IcsPlayingWhite:
5697         /* User is moving for White */
5698         if (!WhiteOnMove(currentMove)) {
5699             if (!appData.premove) {
5700                 DisplayMoveError(_("It is Black's turn"));
5701             } else if (toX >= 0 && toY >= 0) {
5702                 premoveToX = toX;
5703                 premoveToY = toY;
5704                 premoveFromX = fromX;
5705                 premoveFromY = fromY;
5706                 premovePromoChar = promoChar;
5707                 gotPremove = 1;
5708                 if (appData.debugMode) 
5709                     fprintf(debugFP, "Got premove: fromX %d,"
5710                             "fromY %d, toX %d, toY %d\n",
5711                             fromX, fromY, toX, toY);
5712             }
5713             return ImpossibleMove;
5714         }
5715         break;
5716
5717       default:
5718         break;
5719
5720       case EditPosition:
5721         /* EditPosition, empty square, or different color piece;
5722            click-click move is possible */
5723         if (toX == -2 || toY == -2) {
5724             boards[0][fromY][fromX] = EmptySquare;
5725             return AmbiguousMove;
5726         } else if (toX >= 0 && toY >= 0) {
5727             boards[0][toY][toX] = boards[0][fromY][fromX];
5728             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5729                 if(boards[0][fromY][0] != EmptySquare) {
5730                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5731                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5732                 }
5733             } else
5734             if(fromX == BOARD_RGHT+1) {
5735                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5736                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5737                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5738                 }
5739             } else
5740             boards[0][fromY][fromX] = EmptySquare;
5741             return AmbiguousMove;
5742         }
5743         return ImpossibleMove;
5744     }
5745
5746     if(toX < 0 || toY < 0) return ImpossibleMove;
5747     pdown = boards[currentMove][fromY][fromX];
5748     pup = boards[currentMove][toY][toX];
5749
5750     /* [HGM] If move started in holdings, it means a drop */
5751     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5752          if( pup != EmptySquare ) return ImpossibleMove;
5753          if(appData.testLegality) {
5754              /* it would be more logical if LegalityTest() also figured out
5755               * which drops are legal. For now we forbid pawns on back rank.
5756               * Shogi is on its own here...
5757               */
5758              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5759                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5760                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5761          }
5762          return WhiteDrop; /* Not needed to specify white or black yet */
5763     }
5764
5765     /* [HGM] always test for legality, to get promotion info */
5766     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5767                                          fromY, fromX, toY, toX, promoChar);
5768     /* [HGM] but possibly ignore an IllegalMove result */
5769     if (appData.testLegality) {
5770         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5771             DisplayMoveError(_("Illegal move"));
5772             return ImpossibleMove;
5773         }
5774     }
5775
5776     return moveType;
5777     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5778        function is made into one that returns an OK move type if FinishMove
5779        should be called. This to give the calling driver routine the
5780        opportunity to finish the userMove input with a promotion popup,
5781        without bothering the user with this for invalid or illegal moves */
5782
5783 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5784 }
5785
5786 /* Common tail of UserMoveEvent and DropMenuEvent */
5787 int
5788 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5789      ChessMove moveType;
5790      int fromX, fromY, toX, toY;
5791      /*char*/int promoChar;
5792 {
5793     char *bookHit = 0;
5794
5795     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5796         // [HGM] superchess: suppress promotions to non-available piece
5797         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5798         if(WhiteOnMove(currentMove)) {
5799             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5800         } else {
5801             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5802         }
5803     }
5804
5805     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5806        move type in caller when we know the move is a legal promotion */
5807     if(moveType == NormalMove && promoChar)
5808         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5809
5810     /* [HGM] convert drag-and-drop piece drops to standard form */
5811     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5812          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5813            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5814                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5815            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5816            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5817            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5818            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5819          fromY = DROP_RANK;
5820     }
5821
5822     /* [HGM] <popupFix> The following if has been moved here from
5823        UserMoveEvent(). Because it seemed to belong here (why not allow
5824        piece drops in training games?), and because it can only be
5825        performed after it is known to what we promote. */
5826     if (gameMode == Training) {
5827       /* compare the move played on the board to the next move in the
5828        * game. If they match, display the move and the opponent's response. 
5829        * If they don't match, display an error message.
5830        */
5831       int saveAnimate;
5832       Board testBoard;
5833       CopyBoard(testBoard, boards[currentMove]);
5834       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5835
5836       if (CompareBoards(testBoard, boards[currentMove+1])) {
5837         ForwardInner(currentMove+1);
5838
5839         /* Autoplay the opponent's response.
5840          * if appData.animate was TRUE when Training mode was entered,
5841          * the response will be animated.
5842          */
5843         saveAnimate = appData.animate;
5844         appData.animate = animateTraining;
5845         ForwardInner(currentMove+1);
5846         appData.animate = saveAnimate;
5847
5848         /* check for the end of the game */
5849         if (currentMove >= forwardMostMove) {
5850           gameMode = PlayFromGameFile;
5851           ModeHighlight();
5852           SetTrainingModeOff();
5853           DisplayInformation(_("End of game"));
5854         }
5855       } else {
5856         DisplayError(_("Incorrect move"), 0);
5857       }
5858       return 1;
5859     }
5860
5861   /* Ok, now we know that the move is good, so we can kill
5862      the previous line in Analysis Mode */
5863   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5864                                 && currentMove < forwardMostMove) {
5865     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5866   }
5867
5868   /* If we need the chess program but it's dead, restart it */
5869   ResurrectChessProgram();
5870
5871   /* A user move restarts a paused game*/
5872   if (pausing)
5873     PauseEvent();
5874
5875   thinkOutput[0] = NULLCHAR;
5876
5877   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5878
5879   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5880
5881   if (gameMode == BeginningOfGame) {
5882     if (appData.noChessProgram) {
5883       gameMode = EditGame;
5884       SetGameInfo();
5885     } else {
5886       char buf[MSG_SIZ];
5887       gameMode = MachinePlaysBlack;
5888       StartClocks();
5889       SetGameInfo();
5890       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5891       DisplayTitle(buf);
5892       if (first.sendName) {
5893         sprintf(buf, "name %s\n", gameInfo.white);
5894         SendToProgram(buf, &first);
5895       }
5896       StartClocks();
5897     }
5898     ModeHighlight();
5899   }
5900
5901   /* Relay move to ICS or chess engine */
5902   if (appData.icsActive) {
5903     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5904         gameMode == IcsExamining) {
5905       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5906         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5907         SendToICS("draw ");
5908         SendMoveToICS(moveType, fromX, fromY, toX, toY);
5909       }
5910       // also send plain move, in case ICS does not understand atomic claims
5911       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5912       ics_user_moved = 1;
5913     }
5914   } else {
5915     if (first.sendTime && (gameMode == BeginningOfGame ||
5916                            gameMode == MachinePlaysWhite ||
5917                            gameMode == MachinePlaysBlack)) {
5918       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5919     }
5920     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5921          // [HGM] book: if program might be playing, let it use book
5922         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5923         first.maybeThinking = TRUE;
5924     } else SendMoveToProgram(forwardMostMove-1, &first);
5925     if (currentMove == cmailOldMove + 1) {
5926       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5927     }
5928   }
5929
5930   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5931
5932   switch (gameMode) {
5933   case EditGame:
5934     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5935     case MT_NONE:
5936     case MT_CHECK:
5937       break;
5938     case MT_CHECKMATE:
5939     case MT_STAINMATE:
5940       if (WhiteOnMove(currentMove)) {
5941         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5942       } else {
5943         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5944       }
5945       break;
5946     case MT_STALEMATE:
5947       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5948       break;
5949     }
5950     break;
5951     
5952   case MachinePlaysBlack:
5953   case MachinePlaysWhite:
5954     /* disable certain menu options while machine is thinking */
5955     SetMachineThinkingEnables();
5956     break;
5957
5958   default:
5959     break;
5960   }
5961
5962   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5963         
5964   if(bookHit) { // [HGM] book: simulate book reply
5965         static char bookMove[MSG_SIZ]; // a bit generous?
5966
5967         programStats.nodes = programStats.depth = programStats.time = 
5968         programStats.score = programStats.got_only_move = 0;
5969         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5970
5971         strcpy(bookMove, "move ");
5972         strcat(bookMove, bookHit);
5973         HandleMachineMove(bookMove, &first);
5974   }
5975   return 1;
5976 }
5977
5978 void
5979 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5980      int fromX, fromY, toX, toY;
5981      int promoChar;
5982 {
5983     /* [HGM] This routine was added to allow calling of its two logical
5984        parts from other modules in the old way. Before, UserMoveEvent()
5985        automatically called FinishMove() if the move was OK, and returned
5986        otherwise. I separated the two, in order to make it possible to
5987        slip a promotion popup in between. But that it always needs two
5988        calls, to the first part, (now called UserMoveTest() ), and to
5989        FinishMove if the first part succeeded. Calls that do not need
5990        to do anything in between, can call this routine the old way. 
5991     */
5992     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5993 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5994     if(moveType == AmbiguousMove)
5995         DrawPosition(FALSE, boards[currentMove]);
5996     else if(moveType != ImpossibleMove && moveType != Comment)
5997         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5998 }
5999
6000 void
6001 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6002      Board board;
6003      int flags;
6004      ChessMove kind;
6005      int rf, ff, rt, ft;
6006      VOIDSTAR closure;
6007 {
6008     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6009     Markers *m = (Markers *) closure;
6010     if(rf == fromY && ff == fromX)
6011         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6012                          || kind == WhiteCapturesEnPassant
6013                          || kind == BlackCapturesEnPassant);
6014     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6015 }
6016
6017 void
6018 MarkTargetSquares(int clear)
6019 {
6020   int x, y;
6021   if(!appData.markers || !appData.highlightDragging || 
6022      !appData.testLegality || gameMode == EditPosition) return;
6023   if(clear) {
6024     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6025   } else {
6026     int capt = 0;
6027     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6028     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6029       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6030       if(capt)
6031       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6032     }
6033   }
6034   DrawPosition(TRUE, NULL);
6035 }
6036
6037 void LeftClick(ClickType clickType, int xPix, int yPix)
6038 {
6039     int x, y;
6040     Boolean saveAnimate;
6041     static int second = 0, promotionChoice = 0;
6042     char promoChoice = NULLCHAR;
6043
6044     if(appData.seekGraph && appData.icsActive && loggedOn &&
6045         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6046         SeekGraphClick(clickType, xPix, yPix, 0);
6047         return;
6048     }
6049
6050     if (clickType == Press) ErrorPopDown();
6051     MarkTargetSquares(1);
6052
6053     x = EventToSquare(xPix, BOARD_WIDTH);
6054     y = EventToSquare(yPix, BOARD_HEIGHT);
6055     if (!flipView && y >= 0) {
6056         y = BOARD_HEIGHT - 1 - y;
6057     }
6058     if (flipView && x >= 0) {
6059         x = BOARD_WIDTH - 1 - x;
6060     }
6061
6062     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6063         if(clickType == Release) return; // ignore upclick of click-click destination
6064         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6065         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6066         if(gameInfo.holdingsWidth && 
6067                 (WhiteOnMove(currentMove) 
6068                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6069                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6070             // click in right holdings, for determining promotion piece
6071             ChessSquare p = boards[currentMove][y][x];
6072             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6073             if(p != EmptySquare) {
6074                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6075                 fromX = fromY = -1;
6076                 return;
6077             }
6078         }
6079         DrawPosition(FALSE, boards[currentMove]);
6080         return;
6081     }
6082
6083     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6084     if(clickType == Press
6085             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6086               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6087               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6088         return;
6089
6090     if (fromX == -1) {
6091       if(!appData.oneClick || !OnlyMove(&x, &y)) {
6092         if (clickType == Press) {
6093             /* First square */
6094             if (OKToStartUserMove(x, y)) {
6095                 fromX = x;
6096                 fromY = y;
6097                 second = 0;
6098                 MarkTargetSquares(0);
6099                 DragPieceBegin(xPix, yPix);
6100                 if (appData.highlightDragging) {
6101                     SetHighlights(x, y, -1, -1);
6102                 }
6103             }
6104         }
6105         return;
6106       }
6107     }
6108
6109     /* fromX != -1 */
6110     if (clickType == Press && gameMode != EditPosition) {
6111         ChessSquare fromP;
6112         ChessSquare toP;
6113         int frc;
6114
6115         // ignore off-board to clicks
6116         if(y < 0 || x < 0) return;
6117
6118         /* Check if clicking again on the same color piece */
6119         fromP = boards[currentMove][fromY][fromX];
6120         toP = boards[currentMove][y][x];
6121         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6122         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6123              WhitePawn <= toP && toP <= WhiteKing &&
6124              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6125              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6126             (BlackPawn <= fromP && fromP <= BlackKing && 
6127              BlackPawn <= toP && toP <= BlackKing &&
6128              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6129              !(fromP == BlackKing && toP == BlackRook && frc))) {
6130             /* Clicked again on same color piece -- changed his mind */
6131             second = (x == fromX && y == fromY);
6132             if (appData.highlightDragging) {
6133                 SetHighlights(x, y, -1, -1);
6134             } else {
6135                 ClearHighlights();
6136             }
6137             if (OKToStartUserMove(x, y)) {
6138                 fromX = x;
6139                 fromY = y;
6140                 MarkTargetSquares(0);
6141                 DragPieceBegin(xPix, yPix);
6142             }
6143             return;
6144         }
6145         // ignore clicks on holdings
6146         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6147     }
6148
6149     if (clickType == Release && x == fromX && y == fromY) {
6150         DragPieceEnd(xPix, yPix);
6151         if (appData.animateDragging) {
6152             /* Undo animation damage if any */
6153             DrawPosition(FALSE, NULL);
6154         }
6155         if (second) {
6156             /* Second up/down in same square; just abort move */
6157             second = 0;
6158             fromX = fromY = -1;
6159             ClearHighlights();
6160             gotPremove = 0;
6161             ClearPremoveHighlights();
6162         } else {
6163             /* First upclick in same square; start click-click mode */
6164             SetHighlights(x, y, -1, -1);
6165         }
6166         return;
6167     }
6168
6169     /* we now have a different from- and (possibly off-board) to-square */
6170     /* Completed move */
6171     toX = x;
6172     toY = y;
6173     saveAnimate = appData.animate;
6174     if (clickType == Press) {
6175         /* Finish clickclick move */
6176         if (appData.animate || appData.highlightLastMove) {
6177             SetHighlights(fromX, fromY, toX, toY);
6178         } else {
6179             ClearHighlights();
6180         }
6181     } else {
6182         /* Finish drag move */
6183         if (appData.highlightLastMove) {
6184             SetHighlights(fromX, fromY, toX, toY);
6185         } else {
6186             ClearHighlights();
6187         }
6188         DragPieceEnd(xPix, yPix);
6189         /* Don't animate move and drag both */
6190         appData.animate = FALSE;
6191     }
6192
6193     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6194     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6195         ChessSquare piece = boards[currentMove][fromY][fromX];
6196         if(gameMode == EditPosition && piece != EmptySquare &&
6197            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6198             int n;
6199              
6200             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6201                 n = PieceToNumber(piece - (int)BlackPawn);
6202                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6203                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6204                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6205             } else
6206             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6207                 n = PieceToNumber(piece);
6208                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6209                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6210                 boards[currentMove][n][BOARD_WIDTH-2]++;
6211             }
6212             boards[currentMove][fromY][fromX] = EmptySquare;
6213         }
6214         ClearHighlights();
6215         fromX = fromY = -1;
6216         DrawPosition(TRUE, boards[currentMove]);
6217         return;
6218     }
6219
6220     // off-board moves should not be highlighted
6221     if(x < 0 || x < 0) ClearHighlights();
6222
6223     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6224         SetHighlights(fromX, fromY, toX, toY);
6225         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6226             // [HGM] super: promotion to captured piece selected from holdings
6227             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6228             promotionChoice = TRUE;
6229             // kludge follows to temporarily execute move on display, without promoting yet
6230             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6231             boards[currentMove][toY][toX] = p;
6232             DrawPosition(FALSE, boards[currentMove]);
6233             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6234             boards[currentMove][toY][toX] = q;
6235             DisplayMessage("Click in holdings to choose piece", "");
6236             return;
6237         }
6238         PromotionPopUp();
6239     } else {
6240         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6241         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6242         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6243         fromX = fromY = -1;
6244     }
6245     appData.animate = saveAnimate;
6246     if (appData.animate || appData.animateDragging) {
6247         /* Undo animation damage if needed */
6248         DrawPosition(FALSE, NULL);
6249     }
6250 }
6251
6252 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6253 {   // front-end-free part taken out of PieceMenuPopup
6254     int whichMenu; int xSqr, ySqr;
6255
6256     if(seekGraphUp) { // [HGM] seekgraph
6257         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6258         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6259         return -2;
6260     }
6261
6262     xSqr = EventToSquare(x, BOARD_WIDTH);
6263     ySqr = EventToSquare(y, BOARD_HEIGHT);
6264     if (action == Release) UnLoadPV(); // [HGM] pv
6265     if (action != Press) return -2; // return code to be ignored
6266     switch (gameMode) {
6267       case IcsExamining:
6268         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6269       case EditPosition:
6270         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6271         if (xSqr < 0 || ySqr < 0) return -1;\r
6272         whichMenu = 0; // edit-position menu
6273         break;
6274       case IcsObserving:
6275         if(!appData.icsEngineAnalyze) return -1;
6276       case IcsPlayingWhite:
6277       case IcsPlayingBlack:
6278         if(!appData.zippyPlay) goto noZip;
6279       case AnalyzeMode:
6280       case AnalyzeFile:
6281       case MachinePlaysWhite:
6282       case MachinePlaysBlack:
6283       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6284         if (!appData.dropMenu) {
6285           LoadPV(x, y);
6286           return 2; // flag front-end to grab mouse events
6287         }
6288         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6289            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6290       case EditGame:
6291       noZip:
6292         if (xSqr < 0 || ySqr < 0) return -1;
6293         if (!appData.dropMenu || appData.testLegality &&
6294             gameInfo.variant != VariantBughouse &&
6295             gameInfo.variant != VariantCrazyhouse) return -1;
6296         whichMenu = 1; // drop menu
6297         break;
6298       default:
6299         return -1;
6300     }
6301
6302     if (((*fromX = xSqr) < 0) ||
6303         ((*fromY = ySqr) < 0)) {
6304         *fromX = *fromY = -1;
6305         return -1;
6306     }
6307     if (flipView)
6308       *fromX = BOARD_WIDTH - 1 - *fromX;
6309     else
6310       *fromY = BOARD_HEIGHT - 1 - *fromY;
6311
6312     return whichMenu;
6313 }
6314
6315 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6316 {
6317 //    char * hint = lastHint;
6318     FrontEndProgramStats stats;
6319
6320     stats.which = cps == &first ? 0 : 1;
6321     stats.depth = cpstats->depth;
6322     stats.nodes = cpstats->nodes;
6323     stats.score = cpstats->score;
6324     stats.time = cpstats->time;
6325     stats.pv = cpstats->movelist;
6326     stats.hint = lastHint;
6327     stats.an_move_index = 0;
6328     stats.an_move_count = 0;
6329
6330     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6331         stats.hint = cpstats->move_name;
6332         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6333         stats.an_move_count = cpstats->nr_moves;
6334     }
6335
6336     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6337
6338     SetProgramStats( &stats );
6339 }
6340
6341 int
6342 Adjudicate(ChessProgramState *cps)
6343 {       // [HGM] some adjudications useful with buggy engines
6344         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6345         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6346         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6347         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6348         int k, count = 0; static int bare = 1;
6349         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6350         Boolean canAdjudicate = !appData.icsActive;
6351
6352         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6353         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6354             if( appData.testLegality )
6355             {   /* [HGM] Some more adjudications for obstinate engines */
6356                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6357                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6358                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6359                 static int moveCount = 6;
6360                 ChessMove result;
6361                 char *reason = NULL;
6362
6363                 /* Count what is on board. */
6364                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6365                 {   ChessSquare p = boards[forwardMostMove][i][j];
6366                     int m=i;
6367
6368                     switch((int) p)
6369                     {   /* count B,N,R and other of each side */
6370                         case WhiteKing:
6371                         case BlackKing:
6372                              NrK++; break; // [HGM] atomic: count Kings
6373                         case WhiteKnight:
6374                              NrWN++; break;
6375                         case WhiteBishop:
6376                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6377                              bishopsColor |= 1 << ((i^j)&1);
6378                              NrWB++; break;
6379                         case BlackKnight:
6380                              NrBN++; break;
6381                         case BlackBishop:
6382                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6383                              bishopsColor |= 1 << ((i^j)&1);
6384                              NrBB++; break;
6385                         case WhiteRook:
6386                              NrWR++; break;
6387                         case BlackRook:
6388                              NrBR++; break;
6389                         case WhiteQueen:
6390                              NrWQ++; break;
6391                         case BlackQueen:
6392                              NrBQ++; break;
6393                         case EmptySquare: 
6394                              break;
6395                         case BlackPawn:
6396                              m = 7-i;
6397                         case WhitePawn:
6398                              PawnAdvance += m; NrPawns++;
6399                     }
6400                     NrPieces += (p != EmptySquare);
6401                     NrW += ((int)p < (int)BlackPawn);
6402                     if(gameInfo.variant == VariantXiangqi && 
6403                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6404                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6405                         NrW -= ((int)p < (int)BlackPawn);
6406                     }
6407                 }
6408
6409                 /* Some material-based adjudications that have to be made before stalemate test */
6410                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6411                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6412                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6413                      if(canAdjudicate && appData.checkMates) {
6414                          if(engineOpponent)
6415                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6416                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6417                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6418                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6419                          return 1;
6420                      }
6421                 }
6422
6423                 /* Bare King in Shatranj (loses) or Losers (wins) */
6424                 if( NrW == 1 || NrPieces - NrW == 1) {
6425                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6426                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6427                      if(canAdjudicate && appData.checkMates) {
6428                          if(engineOpponent)
6429                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6430                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6431                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6432                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6433                          return 1;
6434                      }
6435                   } else
6436                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6437                   {    /* bare King */
6438                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6439                         if(canAdjudicate && appData.checkMates) {
6440                             /* but only adjudicate if adjudication enabled */
6441                             if(engineOpponent)
6442                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6443                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6444                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6445                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6446                             return 1;
6447                         }
6448                   }
6449                 } else bare = 1;
6450
6451
6452             // don't wait for engine to announce game end if we can judge ourselves
6453             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6454               case MT_CHECK:
6455                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6456                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6457                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6458                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6459                             checkCnt++;
6460                         if(checkCnt >= 2) {
6461                             reason = "Xboard adjudication: 3rd check";
6462                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6463                             break;
6464                         }
6465                     }
6466                 }
6467               case MT_NONE:
6468               default:
6469                 break;
6470               case MT_STALEMATE:
6471               case MT_STAINMATE:
6472                 reason = "Xboard adjudication: Stalemate";
6473                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6474                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6475                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6476                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6477                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6478                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6479                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6480                                                                         EP_CHECKMATE : EP_WINS);
6481                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6482                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6483                 }
6484                 break;
6485               case MT_CHECKMATE:
6486                 reason = "Xboard adjudication: Checkmate";
6487                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6488                 break;
6489             }
6490
6491                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6492                     case EP_STALEMATE:
6493                         result = GameIsDrawn; break;
6494                     case EP_CHECKMATE:
6495                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6496                     case EP_WINS:
6497                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6498                     default:
6499                         result = (ChessMove) 0;
6500                 }
6501                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6502                     if(engineOpponent)
6503                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6504                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6505                     GameEnds( result, reason, GE_XBOARD );
6506                     return 1;
6507                 }
6508
6509                 /* Next absolutely insufficient mating material. */
6510                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6511                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6512                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6513                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6514                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6515
6516                      /* always flag draws, for judging claims */
6517                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6518
6519                      if(canAdjudicate && appData.materialDraws) {
6520                          /* but only adjudicate them if adjudication enabled */
6521                          if(engineOpponent) {
6522                            SendToProgram("force\n", engineOpponent); // suppress reply
6523                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6524                          }
6525                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6526                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6527                          return 1;
6528                      }
6529                 }
6530
6531                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6532                 if(NrPieces == 4 && 
6533                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6534                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6535                    || NrWN==2 || NrBN==2     /* KNNK */
6536                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6537                   ) ) {
6538                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6539                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6540                           if(engineOpponent) {
6541                             SendToProgram("force\n", engineOpponent); // suppress reply
6542                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6543                           }
6544                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6545                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6546                           return 1;
6547                      }
6548                 } else moveCount = 6;
6549             }
6550         }
6551           
6552         if (appData.debugMode) { int i;
6553             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6554                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6555                     appData.drawRepeats);
6556             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6557               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6558             
6559         }
6560
6561         // Repetition draws and 50-move rule can be applied independently of legality testing
6562
6563                 /* Check for rep-draws */
6564                 count = 0;
6565                 for(k = forwardMostMove-2;
6566                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6567                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6568                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6569                     k-=2)
6570                 {   int rights=0;
6571                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6572                         /* compare castling rights */
6573                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6574                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6575                                 rights++; /* King lost rights, while rook still had them */
6576                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6577                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6578                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6579                                    rights++; /* but at least one rook lost them */
6580                         }
6581                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6582                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6583                                 rights++; 
6584                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6585                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6586                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6587                                    rights++;
6588                         }
6589                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6590                             && appData.drawRepeats > 1) {
6591                              /* adjudicate after user-specified nr of repeats */
6592                              if(engineOpponent) {
6593                                SendToProgram("force\n", engineOpponent); // suppress reply
6594                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6595                              }
6596                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6597                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6598                                 // [HGM] xiangqi: check for forbidden perpetuals
6599                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6600                                 for(m=forwardMostMove; m>k; m-=2) {
6601                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6602                                         ourPerpetual = 0; // the current mover did not always check
6603                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6604                                         hisPerpetual = 0; // the opponent did not always check
6605                                 }
6606                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6607                                                                         ourPerpetual, hisPerpetual);
6608                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6609                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6610                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6611                                     return 1;
6612                                 }
6613                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6614                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6615                                 // Now check for perpetual chases
6616                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6617                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6618                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6619                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6620                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6621                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6622                                         return 1;
6623                                     }
6624                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6625                                         break; // Abort repetition-checking loop.
6626                                 }
6627                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6628                              }
6629                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6630                              return 1;
6631                         }
6632                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6633                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6634                     }
6635                 }
6636
6637                 /* Now we test for 50-move draws. Determine ply count */
6638                 count = forwardMostMove;
6639                 /* look for last irreversble move */
6640                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6641                     count--;
6642                 /* if we hit starting position, add initial plies */
6643                 if( count == backwardMostMove )
6644                     count -= initialRulePlies;
6645                 count = forwardMostMove - count; 
6646                 if( count >= 100)
6647                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6648                          /* this is used to judge if draw claims are legal */
6649                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6650                          if(engineOpponent) {
6651                            SendToProgram("force\n", engineOpponent); // suppress reply
6652                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6653                          }
6654                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6655                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6656                          return 1;
6657                 }
6658
6659                 /* if draw offer is pending, treat it as a draw claim
6660                  * when draw condition present, to allow engines a way to
6661                  * claim draws before making their move to avoid a race
6662                  * condition occurring after their move
6663                  */
6664                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6665                          char *p = NULL;
6666                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6667                              p = "Draw claim: 50-move rule";
6668                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6669                              p = "Draw claim: 3-fold repetition";
6670                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6671                              p = "Draw claim: insufficient mating material";
6672                          if( p != NULL && canAdjudicate) {
6673                              if(engineOpponent) {
6674                                SendToProgram("force\n", engineOpponent); // suppress reply
6675                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6676                              }
6677                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6678                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6679                              return 1;
6680                          }
6681                 }
6682
6683                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6684                     if(engineOpponent) {
6685                       SendToProgram("force\n", engineOpponent); // suppress reply
6686                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6687                     }
6688                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6689                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6690                     return 1;
6691                 }
6692         return 0;
6693 }
6694
6695 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6696 {   // [HGM] book: this routine intercepts moves to simulate book replies
6697     char *bookHit = NULL;
6698
6699     //first determine if the incoming move brings opponent into his book
6700     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6701         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6702     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6703     if(bookHit != NULL && !cps->bookSuspend) {
6704         // make sure opponent is not going to reply after receiving move to book position
6705         SendToProgram("force\n", cps);
6706         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6707     }
6708     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6709     // now arrange restart after book miss
6710     if(bookHit) {
6711         // after a book hit we never send 'go', and the code after the call to this routine
6712         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6713         char buf[MSG_SIZ];
6714         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6715         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6716         SendToProgram(buf, cps);
6717         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6718     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6719         SendToProgram("go\n", cps);
6720         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6721     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6722         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6723             SendToProgram("go\n", cps); 
6724         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6725     }
6726     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6727 }
6728
6729 char *savedMessage;
6730 ChessProgramState *savedState;
6731 void DeferredBookMove(void)
6732 {
6733         if(savedState->lastPing != savedState->lastPong)
6734                     ScheduleDelayedEvent(DeferredBookMove, 10);
6735         else
6736         HandleMachineMove(savedMessage, savedState);
6737 }
6738
6739 void
6740 HandleMachineMove(message, cps)
6741      char *message;
6742      ChessProgramState *cps;
6743 {
6744     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6745     char realname[MSG_SIZ];
6746     int fromX, fromY, toX, toY;
6747     ChessMove moveType;
6748     char promoChar;
6749     char *p;
6750     int machineWhite;
6751     char *bookHit;
6752
6753     cps->userError = 0;
6754
6755 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6756     /*
6757      * Kludge to ignore BEL characters
6758      */
6759     while (*message == '\007') message++;
6760
6761     /*
6762      * [HGM] engine debug message: ignore lines starting with '#' character
6763      */
6764     if(cps->debug && *message == '#') return;
6765
6766     /*
6767      * Look for book output
6768      */
6769     if (cps == &first && bookRequested) {
6770         if (message[0] == '\t' || message[0] == ' ') {
6771             /* Part of the book output is here; append it */
6772             strcat(bookOutput, message);
6773             strcat(bookOutput, "  \n");
6774             return;
6775         } else if (bookOutput[0] != NULLCHAR) {
6776             /* All of book output has arrived; display it */
6777             char *p = bookOutput;
6778             while (*p != NULLCHAR) {
6779                 if (*p == '\t') *p = ' ';
6780                 p++;
6781             }
6782             DisplayInformation(bookOutput);
6783             bookRequested = FALSE;
6784             /* Fall through to parse the current output */
6785         }
6786     }
6787
6788     /*
6789      * Look for machine move.
6790      */
6791     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6792         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6793     {
6794         /* This method is only useful on engines that support ping */
6795         if (cps->lastPing != cps->lastPong) {
6796           if (gameMode == BeginningOfGame) {
6797             /* Extra move from before last new; ignore */
6798             if (appData.debugMode) {
6799                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6800             }
6801           } else {
6802             if (appData.debugMode) {
6803                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6804                         cps->which, gameMode);
6805             }
6806
6807             SendToProgram("undo\n", cps);
6808           }
6809           return;
6810         }
6811
6812         switch (gameMode) {
6813           case BeginningOfGame:
6814             /* Extra move from before last reset; ignore */
6815             if (appData.debugMode) {
6816                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6817             }
6818             return;
6819
6820           case EndOfGame:
6821           case IcsIdle:
6822           default:
6823             /* Extra move after we tried to stop.  The mode test is
6824                not a reliable way of detecting this problem, but it's
6825                the best we can do on engines that don't support ping.
6826             */
6827             if (appData.debugMode) {
6828                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6829                         cps->which, gameMode);
6830             }
6831             SendToProgram("undo\n", cps);
6832             return;
6833
6834           case MachinePlaysWhite:
6835           case IcsPlayingWhite:
6836             machineWhite = TRUE;
6837             break;
6838
6839           case MachinePlaysBlack:
6840           case IcsPlayingBlack:
6841             machineWhite = FALSE;
6842             break;
6843
6844           case TwoMachinesPlay:
6845             machineWhite = (cps->twoMachinesColor[0] == 'w');
6846             break;
6847         }
6848         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6849             if (appData.debugMode) {
6850                 fprintf(debugFP,
6851                         "Ignoring move out of turn by %s, gameMode %d"
6852                         ", forwardMost %d\n",
6853                         cps->which, gameMode, forwardMostMove);
6854             }
6855             return;
6856         }
6857
6858     if (appData.debugMode) { int f = forwardMostMove;
6859         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6860                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6861                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6862     }
6863         if(cps->alphaRank) AlphaRank(machineMove, 4);
6864         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6865                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6866             /* Machine move could not be parsed; ignore it. */
6867             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6868                     machineMove, cps->which);
6869             DisplayError(buf1, 0);
6870             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6871                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6872             if (gameMode == TwoMachinesPlay) {
6873               GameEnds(machineWhite ? BlackWins : WhiteWins,
6874                        buf1, GE_XBOARD);
6875             }
6876             return;
6877         }
6878
6879         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6880         /* So we have to redo legality test with true e.p. status here,  */
6881         /* to make sure an illegal e.p. capture does not slip through,   */
6882         /* to cause a forfeit on a justified illegal-move complaint      */
6883         /* of the opponent.                                              */
6884         if( gameMode==TwoMachinesPlay && appData.testLegality
6885             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6886                                                               ) {
6887            ChessMove moveType;
6888            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6889                              fromY, fromX, toY, toX, promoChar);
6890             if (appData.debugMode) {
6891                 int i;
6892                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6893                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6894                 fprintf(debugFP, "castling rights\n");
6895             }
6896             if(moveType == IllegalMove) {
6897                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6898                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6899                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6900                            buf1, GE_XBOARD);
6901                 return;
6902            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6903            /* [HGM] Kludge to handle engines that send FRC-style castling
6904               when they shouldn't (like TSCP-Gothic) */
6905            switch(moveType) {
6906              case WhiteASideCastleFR:
6907              case BlackASideCastleFR:
6908                toX+=2;
6909                currentMoveString[2]++;
6910                break;
6911              case WhiteHSideCastleFR:
6912              case BlackHSideCastleFR:
6913                toX--;
6914                currentMoveString[2]--;
6915                break;
6916              default: ; // nothing to do, but suppresses warning of pedantic compilers
6917            }
6918         }
6919         hintRequested = FALSE;
6920         lastHint[0] = NULLCHAR;
6921         bookRequested = FALSE;
6922         /* Program may be pondering now */
6923         cps->maybeThinking = TRUE;
6924         if (cps->sendTime == 2) cps->sendTime = 1;
6925         if (cps->offeredDraw) cps->offeredDraw--;
6926
6927         /* currentMoveString is set as a side-effect of ParseOneMove */
6928         strcpy(machineMove, currentMoveString);
6929         strcat(machineMove, "\n");
6930         strcpy(moveList[forwardMostMove], machineMove);
6931
6932         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6933
6934         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6935         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6936             int count = 0;
6937
6938             while( count < adjudicateLossPlies ) {
6939                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6940
6941                 if( count & 1 ) {
6942                     score = -score; /* Flip score for winning side */
6943                 }
6944
6945                 if( score > adjudicateLossThreshold ) {
6946                     break;
6947                 }
6948
6949                 count++;
6950             }
6951
6952             if( count >= adjudicateLossPlies ) {
6953                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6954
6955                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6956                     "Xboard adjudication", 
6957                     GE_XBOARD );
6958
6959                 return;
6960             }
6961         }
6962
6963         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6964
6965 #if ZIPPY
6966         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6967             first.initDone) {
6968           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6969                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6970                 SendToICS("draw ");
6971                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6972           }
6973           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6974           ics_user_moved = 1;
6975           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6976                 char buf[3*MSG_SIZ];
6977
6978                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6979                         programStats.score / 100.,
6980                         programStats.depth,
6981                         programStats.time / 100.,
6982                         (unsigned int)programStats.nodes,
6983                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6984                         programStats.movelist);
6985                 SendToICS(buf);
6986 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6987           }
6988         }
6989 #endif
6990
6991         /* [AS] Save move info and clear stats for next move */
6992         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
6993         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
6994         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6995         ClearProgramStats();
6996         thinkOutput[0] = NULLCHAR;
6997         hiddenThinkOutputState = 0;
6998
6999         bookHit = NULL;
7000         if (gameMode == TwoMachinesPlay) {
7001             /* [HGM] relaying draw offers moved to after reception of move */
7002             /* and interpreting offer as claim if it brings draw condition */
7003             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7004                 SendToProgram("draw\n", cps->other);
7005             }
7006             if (cps->other->sendTime) {
7007                 SendTimeRemaining(cps->other,
7008                                   cps->other->twoMachinesColor[0] == 'w');
7009             }
7010             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7011             if (firstMove && !bookHit) {
7012                 firstMove = FALSE;
7013                 if (cps->other->useColors) {
7014                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7015                 }
7016                 SendToProgram("go\n", cps->other);
7017             }
7018             cps->other->maybeThinking = TRUE;
7019         }
7020
7021         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7022         
7023         if (!pausing && appData.ringBellAfterMoves) {
7024             RingBell();
7025         }
7026
7027         /* 
7028          * Reenable menu items that were disabled while
7029          * machine was thinking
7030          */
7031         if (gameMode != TwoMachinesPlay)
7032             SetUserThinkingEnables();
7033
7034         // [HGM] book: after book hit opponent has received move and is now in force mode
7035         // force the book reply into it, and then fake that it outputted this move by jumping
7036         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7037         if(bookHit) {
7038                 static char bookMove[MSG_SIZ]; // a bit generous?
7039
7040                 strcpy(bookMove, "move ");
7041                 strcat(bookMove, bookHit);
7042                 message = bookMove;
7043                 cps = cps->other;
7044                 programStats.nodes = programStats.depth = programStats.time = 
7045                 programStats.score = programStats.got_only_move = 0;
7046                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7047
7048                 if(cps->lastPing != cps->lastPong) {
7049                     savedMessage = message; // args for deferred call
7050                     savedState = cps;
7051                     ScheduleDelayedEvent(DeferredBookMove, 10);
7052                     return;
7053                 }
7054                 goto FakeBookMove;
7055         }
7056
7057         return;
7058     }
7059
7060     /* Set special modes for chess engines.  Later something general
7061      *  could be added here; for now there is just one kludge feature,
7062      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7063      *  when "xboard" is given as an interactive command.
7064      */
7065     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7066         cps->useSigint = FALSE;
7067         cps->useSigterm = FALSE;
7068     }
7069     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7070       ParseFeatures(message+8, cps);
7071       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7072     }
7073
7074     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7075      * want this, I was asked to put it in, and obliged.
7076      */
7077     if (!strncmp(message, "setboard ", 9)) {
7078         Board initial_position;
7079
7080         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7081
7082         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7083             DisplayError(_("Bad FEN received from engine"), 0);
7084             return ;
7085         } else {
7086            Reset(TRUE, FALSE);
7087            CopyBoard(boards[0], initial_position);
7088            initialRulePlies = FENrulePlies;
7089            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7090            else gameMode = MachinePlaysBlack;                 
7091            DrawPosition(FALSE, boards[currentMove]);
7092         }
7093         return;
7094     }
7095
7096     /*
7097      * Look for communication commands
7098      */
7099     if (!strncmp(message, "telluser ", 9)) {
7100         DisplayNote(message + 9);
7101         return;
7102     }
7103     if (!strncmp(message, "tellusererror ", 14)) {
7104         cps->userError = 1;
7105         DisplayError(message + 14, 0);
7106         return;
7107     }
7108     if (!strncmp(message, "tellopponent ", 13)) {
7109       if (appData.icsActive) {
7110         if (loggedOn) {
7111           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7112           SendToICS(buf1);
7113         }
7114       } else {
7115         DisplayNote(message + 13);
7116       }
7117       return;
7118     }
7119     if (!strncmp(message, "tellothers ", 11)) {
7120       if (appData.icsActive) {
7121         if (loggedOn) {
7122           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7123           SendToICS(buf1);
7124         }
7125       }
7126       return;
7127     }
7128     if (!strncmp(message, "tellall ", 8)) {
7129       if (appData.icsActive) {
7130         if (loggedOn) {
7131           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7132           SendToICS(buf1);
7133         }
7134       } else {
7135         DisplayNote(message + 8);
7136       }
7137       return;
7138     }
7139     if (strncmp(message, "warning", 7) == 0) {
7140         /* Undocumented feature, use tellusererror in new code */
7141         DisplayError(message, 0);
7142         return;
7143     }
7144     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7145         strcpy(realname, cps->tidy);
7146         strcat(realname, " query");
7147         AskQuestion(realname, buf2, buf1, cps->pr);
7148         return;
7149     }
7150     /* Commands from the engine directly to ICS.  We don't allow these to be 
7151      *  sent until we are logged on. Crafty kibitzes have been known to 
7152      *  interfere with the login process.
7153      */
7154     if (loggedOn) {
7155         if (!strncmp(message, "tellics ", 8)) {
7156             SendToICS(message + 8);
7157             SendToICS("\n");
7158             return;
7159         }
7160         if (!strncmp(message, "tellicsnoalias ", 15)) {
7161             SendToICS(ics_prefix);
7162             SendToICS(message + 15);
7163             SendToICS("\n");
7164             return;
7165         }
7166         /* The following are for backward compatibility only */
7167         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7168             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7169             SendToICS(ics_prefix);
7170             SendToICS(message);
7171             SendToICS("\n");
7172             return;
7173         }
7174     }
7175     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7176         return;
7177     }
7178     /*
7179      * If the move is illegal, cancel it and redraw the board.
7180      * Also deal with other error cases.  Matching is rather loose
7181      * here to accommodate engines written before the spec.
7182      */
7183     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7184         strncmp(message, "Error", 5) == 0) {
7185         if (StrStr(message, "name") || 
7186             StrStr(message, "rating") || StrStr(message, "?") ||
7187             StrStr(message, "result") || StrStr(message, "board") ||
7188             StrStr(message, "bk") || StrStr(message, "computer") ||
7189             StrStr(message, "variant") || StrStr(message, "hint") ||
7190             StrStr(message, "random") || StrStr(message, "depth") ||
7191             StrStr(message, "accepted")) {
7192             return;
7193         }
7194         if (StrStr(message, "protover")) {
7195           /* Program is responding to input, so it's apparently done
7196              initializing, and this error message indicates it is
7197              protocol version 1.  So we don't need to wait any longer
7198              for it to initialize and send feature commands. */
7199           FeatureDone(cps, 1);
7200           cps->protocolVersion = 1;
7201           return;
7202         }
7203         cps->maybeThinking = FALSE;
7204
7205         if (StrStr(message, "draw")) {
7206             /* Program doesn't have "draw" command */
7207             cps->sendDrawOffers = 0;
7208             return;
7209         }
7210         if (cps->sendTime != 1 &&
7211             (StrStr(message, "time") || StrStr(message, "otim"))) {
7212           /* Program apparently doesn't have "time" or "otim" command */
7213           cps->sendTime = 0;
7214           return;
7215         }
7216         if (StrStr(message, "analyze")) {
7217             cps->analysisSupport = FALSE;
7218             cps->analyzing = FALSE;
7219             Reset(FALSE, TRUE);
7220             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7221             DisplayError(buf2, 0);
7222             return;
7223         }
7224         if (StrStr(message, "(no matching move)st")) {
7225           /* Special kludge for GNU Chess 4 only */
7226           cps->stKludge = TRUE;
7227           SendTimeControl(cps, movesPerSession, timeControl,
7228                           timeIncrement, appData.searchDepth,
7229                           searchTime);
7230           return;
7231         }
7232         if (StrStr(message, "(no matching move)sd")) {
7233           /* Special kludge for GNU Chess 4 only */
7234           cps->sdKludge = TRUE;
7235           SendTimeControl(cps, movesPerSession, timeControl,
7236                           timeIncrement, appData.searchDepth,
7237                           searchTime);
7238           return;
7239         }
7240         if (!StrStr(message, "llegal")) {
7241             return;
7242         }
7243         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7244             gameMode == IcsIdle) return;
7245         if (forwardMostMove <= backwardMostMove) return;
7246         if (pausing) PauseEvent();
7247       if(appData.forceIllegal) {
7248             // [HGM] illegal: machine refused move; force position after move into it
7249           SendToProgram("force\n", cps);
7250           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7251                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7252                 // when black is to move, while there might be nothing on a2 or black
7253                 // might already have the move. So send the board as if white has the move.
7254                 // But first we must change the stm of the engine, as it refused the last move
7255                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7256                 if(WhiteOnMove(forwardMostMove)) {
7257                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7258                     SendBoard(cps, forwardMostMove); // kludgeless board
7259                 } else {
7260                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7261                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7262                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7263                 }
7264           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7265             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7266                  gameMode == TwoMachinesPlay)
7267               SendToProgram("go\n", cps);
7268             return;
7269       } else
7270         if (gameMode == PlayFromGameFile) {
7271             /* Stop reading this game file */
7272             gameMode = EditGame;
7273             ModeHighlight();
7274         }
7275         currentMove = --forwardMostMove;
7276         DisplayMove(currentMove-1); /* before DisplayMoveError */
7277         SwitchClocks();
7278         DisplayBothClocks();
7279         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7280                 parseList[currentMove], cps->which);
7281         DisplayMoveError(buf1);
7282         DrawPosition(FALSE, boards[currentMove]);
7283
7284         /* [HGM] illegal-move claim should forfeit game when Xboard */
7285         /* only passes fully legal moves                            */
7286         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7287             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7288                                 "False illegal-move claim", GE_XBOARD );
7289         }
7290         return;
7291     }
7292     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7293         /* Program has a broken "time" command that
7294            outputs a string not ending in newline.
7295            Don't use it. */
7296         cps->sendTime = 0;
7297     }
7298     
7299     /*
7300      * If chess program startup fails, exit with an error message.
7301      * Attempts to recover here are futile.
7302      */
7303     if ((StrStr(message, "unknown host") != NULL)
7304         || (StrStr(message, "No remote directory") != NULL)
7305         || (StrStr(message, "not found") != NULL)
7306         || (StrStr(message, "No such file") != NULL)
7307         || (StrStr(message, "can't alloc") != NULL)
7308         || (StrStr(message, "Permission denied") != NULL)) {
7309
7310         cps->maybeThinking = FALSE;
7311         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7312                 cps->which, cps->program, cps->host, message);
7313         RemoveInputSource(cps->isr);
7314         DisplayFatalError(buf1, 0, 1);
7315         return;
7316     }
7317     
7318     /* 
7319      * Look for hint output
7320      */
7321     if (sscanf(message, "Hint: %s", buf1) == 1) {
7322         if (cps == &first && hintRequested) {
7323             hintRequested = FALSE;
7324             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7325                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7326                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7327                                     PosFlags(forwardMostMove),
7328                                     fromY, fromX, toY, toX, promoChar, buf1);
7329                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7330                 DisplayInformation(buf2);
7331             } else {
7332                 /* Hint move could not be parsed!? */
7333               snprintf(buf2, sizeof(buf2),
7334                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7335                         buf1, cps->which);
7336                 DisplayError(buf2, 0);
7337             }
7338         } else {
7339             strcpy(lastHint, buf1);
7340         }
7341         return;
7342     }
7343
7344     /*
7345      * Ignore other messages if game is not in progress
7346      */
7347     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7348         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7349
7350     /*
7351      * look for win, lose, draw, or draw offer
7352      */
7353     if (strncmp(message, "1-0", 3) == 0) {
7354         char *p, *q, *r = "";
7355         p = strchr(message, '{');
7356         if (p) {
7357             q = strchr(p, '}');
7358             if (q) {
7359                 *q = NULLCHAR;
7360                 r = p + 1;
7361             }
7362         }
7363         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7364         return;
7365     } else if (strncmp(message, "0-1", 3) == 0) {
7366         char *p, *q, *r = "";
7367         p = strchr(message, '{');
7368         if (p) {
7369             q = strchr(p, '}');
7370             if (q) {
7371                 *q = NULLCHAR;
7372                 r = p + 1;
7373             }
7374         }
7375         /* Kludge for Arasan 4.1 bug */
7376         if (strcmp(r, "Black resigns") == 0) {
7377             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7378             return;
7379         }
7380         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7381         return;
7382     } else if (strncmp(message, "1/2", 3) == 0) {
7383         char *p, *q, *r = "";
7384         p = strchr(message, '{');
7385         if (p) {
7386             q = strchr(p, '}');
7387             if (q) {
7388                 *q = NULLCHAR;
7389                 r = p + 1;
7390             }
7391         }
7392             
7393         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7394         return;
7395
7396     } else if (strncmp(message, "White resign", 12) == 0) {
7397         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7398         return;
7399     } else if (strncmp(message, "Black resign", 12) == 0) {
7400         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7401         return;
7402     } else if (strncmp(message, "White matches", 13) == 0 ||
7403                strncmp(message, "Black matches", 13) == 0   ) {
7404         /* [HGM] ignore GNUShogi noises */
7405         return;
7406     } else if (strncmp(message, "White", 5) == 0 &&
7407                message[5] != '(' &&
7408                StrStr(message, "Black") == NULL) {
7409         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7410         return;
7411     } else if (strncmp(message, "Black", 5) == 0 &&
7412                message[5] != '(') {
7413         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7414         return;
7415     } else if (strcmp(message, "resign") == 0 ||
7416                strcmp(message, "computer resigns") == 0) {
7417         switch (gameMode) {
7418           case MachinePlaysBlack:
7419           case IcsPlayingBlack:
7420             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7421             break;
7422           case MachinePlaysWhite:
7423           case IcsPlayingWhite:
7424             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7425             break;
7426           case TwoMachinesPlay:
7427             if (cps->twoMachinesColor[0] == 'w')
7428               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7429             else
7430               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7431             break;
7432           default:
7433             /* can't happen */
7434             break;
7435         }
7436         return;
7437     } else if (strncmp(message, "opponent mates", 14) == 0) {
7438         switch (gameMode) {
7439           case MachinePlaysBlack:
7440           case IcsPlayingBlack:
7441             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7442             break;
7443           case MachinePlaysWhite:
7444           case IcsPlayingWhite:
7445             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7446             break;
7447           case TwoMachinesPlay:
7448             if (cps->twoMachinesColor[0] == 'w')
7449               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7450             else
7451               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7452             break;
7453           default:
7454             /* can't happen */
7455             break;
7456         }
7457         return;
7458     } else if (strncmp(message, "computer mates", 14) == 0) {
7459         switch (gameMode) {
7460           case MachinePlaysBlack:
7461           case IcsPlayingBlack:
7462             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7463             break;
7464           case MachinePlaysWhite:
7465           case IcsPlayingWhite:
7466             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7467             break;
7468           case TwoMachinesPlay:
7469             if (cps->twoMachinesColor[0] == 'w')
7470               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7471             else
7472               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7473             break;
7474           default:
7475             /* can't happen */
7476             break;
7477         }
7478         return;
7479     } else if (strncmp(message, "checkmate", 9) == 0) {
7480         if (WhiteOnMove(forwardMostMove)) {
7481             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7482         } else {
7483             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7484         }
7485         return;
7486     } else if (strstr(message, "Draw") != NULL ||
7487                strstr(message, "game is a draw") != NULL) {
7488         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7489         return;
7490     } else if (strstr(message, "offer") != NULL &&
7491                strstr(message, "draw") != NULL) {
7492 #if ZIPPY
7493         if (appData.zippyPlay && first.initDone) {
7494             /* Relay offer to ICS */
7495             SendToICS(ics_prefix);
7496             SendToICS("draw\n");
7497         }
7498 #endif
7499         cps->offeredDraw = 2; /* valid until this engine moves twice */
7500         if (gameMode == TwoMachinesPlay) {
7501             if (cps->other->offeredDraw) {
7502                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7503             /* [HGM] in two-machine mode we delay relaying draw offer      */
7504             /* until after we also have move, to see if it is really claim */
7505             }
7506         } else if (gameMode == MachinePlaysWhite ||
7507                    gameMode == MachinePlaysBlack) {
7508           if (userOfferedDraw) {
7509             DisplayInformation(_("Machine accepts your draw offer"));
7510             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7511           } else {
7512             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7513           }
7514         }
7515     }
7516
7517     
7518     /*
7519      * Look for thinking output
7520      */
7521     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7522           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7523                                 ) {
7524         int plylev, mvleft, mvtot, curscore, time;
7525         char mvname[MOVE_LEN];
7526         u64 nodes; // [DM]
7527         char plyext;
7528         int ignore = FALSE;
7529         int prefixHint = FALSE;
7530         mvname[0] = NULLCHAR;
7531
7532         switch (gameMode) {
7533           case MachinePlaysBlack:
7534           case IcsPlayingBlack:
7535             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7536             break;
7537           case MachinePlaysWhite:
7538           case IcsPlayingWhite:
7539             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7540             break;
7541           case AnalyzeMode:
7542           case AnalyzeFile:
7543             break;
7544           case IcsObserving: /* [DM] icsEngineAnalyze */
7545             if (!appData.icsEngineAnalyze) ignore = TRUE;
7546             break;
7547           case TwoMachinesPlay:
7548             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7549                 ignore = TRUE;
7550             }
7551             break;
7552           default:
7553             ignore = TRUE;
7554             break;
7555         }
7556
7557         if (!ignore) {
7558             buf1[0] = NULLCHAR;
7559             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7560                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7561
7562                 if (plyext != ' ' && plyext != '\t') {
7563                     time *= 100;
7564                 }
7565
7566                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7567                 if( cps->scoreIsAbsolute && 
7568                     ( gameMode == MachinePlaysBlack ||
7569                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7570                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7571                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7572                      !WhiteOnMove(currentMove)
7573                     ) )
7574                 {
7575                     curscore = -curscore;
7576                 }
7577
7578
7579                 programStats.depth = plylev;
7580                 programStats.nodes = nodes;
7581                 programStats.time = time;
7582                 programStats.score = curscore;
7583                 programStats.got_only_move = 0;
7584
7585                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7586                         int ticklen;
7587
7588                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7589                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7590                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7591                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7592                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7593                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7594                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7595                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7596                 }
7597
7598                 /* Buffer overflow protection */
7599                 if (buf1[0] != NULLCHAR) {
7600                     if (strlen(buf1) >= sizeof(programStats.movelist)
7601                         && appData.debugMode) {
7602                         fprintf(debugFP,
7603                                 "PV is too long; using the first %u bytes.\n",
7604                                 (unsigned) sizeof(programStats.movelist) - 1);
7605                     }
7606
7607                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7608                 } else {
7609                     sprintf(programStats.movelist, " no PV\n");
7610                 }
7611
7612                 if (programStats.seen_stat) {
7613                     programStats.ok_to_send = 1;
7614                 }
7615
7616                 if (strchr(programStats.movelist, '(') != NULL) {
7617                     programStats.line_is_book = 1;
7618                     programStats.nr_moves = 0;
7619                     programStats.moves_left = 0;
7620                 } else {
7621                     programStats.line_is_book = 0;
7622                 }
7623
7624                 SendProgramStatsToFrontend( cps, &programStats );
7625
7626                 /* 
7627                     [AS] Protect the thinkOutput buffer from overflow... this
7628                     is only useful if buf1 hasn't overflowed first!
7629                 */
7630                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7631                         plylev, 
7632                         (gameMode == TwoMachinesPlay ?
7633                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7634                         ((double) curscore) / 100.0,
7635                         prefixHint ? lastHint : "",
7636                         prefixHint ? " " : "" );
7637
7638                 if( buf1[0] != NULLCHAR ) {
7639                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7640
7641                     if( strlen(buf1) > max_len ) {
7642                         if( appData.debugMode) {
7643                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7644                         }
7645                         buf1[max_len+1] = '\0';
7646                     }
7647
7648                     strcat( thinkOutput, buf1 );
7649                 }
7650
7651                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7652                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7653                     DisplayMove(currentMove - 1);
7654                 }
7655                 return;
7656
7657             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7658                 /* crafty (9.25+) says "(only move) <move>"
7659                  * if there is only 1 legal move
7660                  */
7661                 sscanf(p, "(only move) %s", buf1);
7662                 sprintf(thinkOutput, "%s (only move)", buf1);
7663                 sprintf(programStats.movelist, "%s (only move)", buf1);
7664                 programStats.depth = 1;
7665                 programStats.nr_moves = 1;
7666                 programStats.moves_left = 1;
7667                 programStats.nodes = 1;
7668                 programStats.time = 1;
7669                 programStats.got_only_move = 1;
7670
7671                 /* Not really, but we also use this member to
7672                    mean "line isn't going to change" (Crafty
7673                    isn't searching, so stats won't change) */
7674                 programStats.line_is_book = 1;
7675
7676                 SendProgramStatsToFrontend( cps, &programStats );
7677                 
7678                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7679                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7680                     DisplayMove(currentMove - 1);
7681                 }
7682                 return;
7683             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7684                               &time, &nodes, &plylev, &mvleft,
7685                               &mvtot, mvname) >= 5) {
7686                 /* The stat01: line is from Crafty (9.29+) in response
7687                    to the "." command */
7688                 programStats.seen_stat = 1;
7689                 cps->maybeThinking = TRUE;
7690
7691                 if (programStats.got_only_move || !appData.periodicUpdates)
7692                   return;
7693
7694                 programStats.depth = plylev;
7695                 programStats.time = time;
7696                 programStats.nodes = nodes;
7697                 programStats.moves_left = mvleft;
7698                 programStats.nr_moves = mvtot;
7699                 strcpy(programStats.move_name, mvname);
7700                 programStats.ok_to_send = 1;
7701                 programStats.movelist[0] = '\0';
7702
7703                 SendProgramStatsToFrontend( cps, &programStats );
7704
7705                 return;
7706
7707             } else if (strncmp(message,"++",2) == 0) {
7708                 /* Crafty 9.29+ outputs this */
7709                 programStats.got_fail = 2;
7710                 return;
7711
7712             } else if (strncmp(message,"--",2) == 0) {
7713                 /* Crafty 9.29+ outputs this */
7714                 programStats.got_fail = 1;
7715                 return;
7716
7717             } else if (thinkOutput[0] != NULLCHAR &&
7718                        strncmp(message, "    ", 4) == 0) {
7719                 unsigned message_len;
7720
7721                 p = message;
7722                 while (*p && *p == ' ') p++;
7723
7724                 message_len = strlen( p );
7725
7726                 /* [AS] Avoid buffer overflow */
7727                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7728                     strcat(thinkOutput, " ");
7729                     strcat(thinkOutput, p);
7730                 }
7731
7732                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7733                     strcat(programStats.movelist, " ");
7734                     strcat(programStats.movelist, p);
7735                 }
7736
7737                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7738                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7739                     DisplayMove(currentMove - 1);
7740                 }
7741                 return;
7742             }
7743         }
7744         else {
7745             buf1[0] = NULLCHAR;
7746
7747             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7748                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7749             {
7750                 ChessProgramStats cpstats;
7751
7752                 if (plyext != ' ' && plyext != '\t') {
7753                     time *= 100;
7754                 }
7755
7756                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7757                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7758                     curscore = -curscore;
7759                 }
7760
7761                 cpstats.depth = plylev;
7762                 cpstats.nodes = nodes;
7763                 cpstats.time = time;
7764                 cpstats.score = curscore;
7765                 cpstats.got_only_move = 0;
7766                 cpstats.movelist[0] = '\0';
7767
7768                 if (buf1[0] != NULLCHAR) {
7769                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7770                 }
7771
7772                 cpstats.ok_to_send = 0;
7773                 cpstats.line_is_book = 0;
7774                 cpstats.nr_moves = 0;
7775                 cpstats.moves_left = 0;
7776
7777                 SendProgramStatsToFrontend( cps, &cpstats );
7778             }
7779         }
7780     }
7781 }
7782
7783
7784 /* Parse a game score from the character string "game", and
7785    record it as the history of the current game.  The game
7786    score is NOT assumed to start from the standard position. 
7787    The display is not updated in any way.
7788    */
7789 void
7790 ParseGameHistory(game)
7791      char *game;
7792 {
7793     ChessMove moveType;
7794     int fromX, fromY, toX, toY, boardIndex;
7795     char promoChar;
7796     char *p, *q;
7797     char buf[MSG_SIZ];
7798
7799     if (appData.debugMode)
7800       fprintf(debugFP, "Parsing game history: %s\n", game);
7801
7802     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7803     gameInfo.site = StrSave(appData.icsHost);
7804     gameInfo.date = PGNDate();
7805     gameInfo.round = StrSave("-");
7806
7807     /* Parse out names of players */
7808     while (*game == ' ') game++;
7809     p = buf;
7810     while (*game != ' ') *p++ = *game++;
7811     *p = NULLCHAR;
7812     gameInfo.white = StrSave(buf);
7813     while (*game == ' ') game++;
7814     p = buf;
7815     while (*game != ' ' && *game != '\n') *p++ = *game++;
7816     *p = NULLCHAR;
7817     gameInfo.black = StrSave(buf);
7818
7819     /* Parse moves */
7820     boardIndex = blackPlaysFirst ? 1 : 0;
7821     yynewstr(game);
7822     for (;;) {
7823         yyboardindex = boardIndex;
7824         moveType = (ChessMove) yylex();
7825         switch (moveType) {
7826           case IllegalMove:             /* maybe suicide chess, etc. */
7827   if (appData.debugMode) {
7828     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7829     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7830     setbuf(debugFP, NULL);
7831   }
7832           case WhitePromotionChancellor:
7833           case BlackPromotionChancellor:
7834           case WhitePromotionArchbishop:
7835           case BlackPromotionArchbishop:
7836           case WhitePromotionQueen:
7837           case BlackPromotionQueen:
7838           case WhitePromotionRook:
7839           case BlackPromotionRook:
7840           case WhitePromotionBishop:
7841           case BlackPromotionBishop:
7842           case WhitePromotionKnight:
7843           case BlackPromotionKnight:
7844           case WhitePromotionKing:
7845           case BlackPromotionKing:
7846           case NormalMove:
7847           case WhiteCapturesEnPassant:
7848           case BlackCapturesEnPassant:
7849           case WhiteKingSideCastle:
7850           case WhiteQueenSideCastle:
7851           case BlackKingSideCastle:
7852           case BlackQueenSideCastle:
7853           case WhiteKingSideCastleWild:
7854           case WhiteQueenSideCastleWild:
7855           case BlackKingSideCastleWild:
7856           case BlackQueenSideCastleWild:
7857           /* PUSH Fabien */
7858           case WhiteHSideCastleFR:
7859           case WhiteASideCastleFR:
7860           case BlackHSideCastleFR:
7861           case BlackASideCastleFR:
7862           /* POP Fabien */
7863             fromX = currentMoveString[0] - AAA;
7864             fromY = currentMoveString[1] - ONE;
7865             toX = currentMoveString[2] - AAA;
7866             toY = currentMoveString[3] - ONE;
7867             promoChar = currentMoveString[4];
7868             break;
7869           case WhiteDrop:
7870           case BlackDrop:
7871             fromX = moveType == WhiteDrop ?
7872               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7873             (int) CharToPiece(ToLower(currentMoveString[0]));
7874             fromY = DROP_RANK;
7875             toX = currentMoveString[2] - AAA;
7876             toY = currentMoveString[3] - ONE;
7877             promoChar = NULLCHAR;
7878             break;
7879           case AmbiguousMove:
7880             /* bug? */
7881             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7882   if (appData.debugMode) {
7883     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7884     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7885     setbuf(debugFP, NULL);
7886   }
7887             DisplayError(buf, 0);
7888             return;
7889           case ImpossibleMove:
7890             /* bug? */
7891             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7892   if (appData.debugMode) {
7893     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7894     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7895     setbuf(debugFP, NULL);
7896   }
7897             DisplayError(buf, 0);
7898             return;
7899           case (ChessMove) 0:   /* end of file */
7900             if (boardIndex < backwardMostMove) {
7901                 /* Oops, gap.  How did that happen? */
7902                 DisplayError(_("Gap in move list"), 0);
7903                 return;
7904             }
7905             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7906             if (boardIndex > forwardMostMove) {
7907                 forwardMostMove = boardIndex;
7908             }
7909             return;
7910           case ElapsedTime:
7911             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7912                 strcat(parseList[boardIndex-1], " ");
7913                 strcat(parseList[boardIndex-1], yy_text);
7914             }
7915             continue;
7916           case Comment:
7917           case PGNTag:
7918           case NAG:
7919           default:
7920             /* ignore */
7921             continue;
7922           case WhiteWins:
7923           case BlackWins:
7924           case GameIsDrawn:
7925           case GameUnfinished:
7926             if (gameMode == IcsExamining) {
7927                 if (boardIndex < backwardMostMove) {
7928                     /* Oops, gap.  How did that happen? */
7929                     return;
7930                 }
7931                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7932                 return;
7933             }
7934             gameInfo.result = moveType;
7935             p = strchr(yy_text, '{');
7936             if (p == NULL) p = strchr(yy_text, '(');
7937             if (p == NULL) {
7938                 p = yy_text;
7939                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7940             } else {
7941                 q = strchr(p, *p == '{' ? '}' : ')');
7942                 if (q != NULL) *q = NULLCHAR;
7943                 p++;
7944             }
7945             gameInfo.resultDetails = StrSave(p);
7946             continue;
7947         }
7948         if (boardIndex >= forwardMostMove &&
7949             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7950             backwardMostMove = blackPlaysFirst ? 1 : 0;
7951             return;
7952         }
7953         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7954                                  fromY, fromX, toY, toX, promoChar,
7955                                  parseList[boardIndex]);
7956         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7957         /* currentMoveString is set as a side-effect of yylex */
7958         strcpy(moveList[boardIndex], currentMoveString);
7959         strcat(moveList[boardIndex], "\n");
7960         boardIndex++;
7961         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7962         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7963           case MT_NONE:
7964           case MT_STALEMATE:
7965           default:
7966             break;
7967           case MT_CHECK:
7968             if(gameInfo.variant != VariantShogi)
7969                 strcat(parseList[boardIndex - 1], "+");
7970             break;
7971           case MT_CHECKMATE:
7972           case MT_STAINMATE:
7973             strcat(parseList[boardIndex - 1], "#");
7974             break;
7975         }
7976     }
7977 }
7978
7979
7980 /* Apply a move to the given board  */
7981 void
7982 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7983      int fromX, fromY, toX, toY;
7984      int promoChar;
7985      Board board;
7986 {
7987   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7988   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7989
7990     /* [HGM] compute & store e.p. status and castling rights for new position */
7991     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7992     { int i;
7993
7994       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7995       oldEP = (signed char)board[EP_STATUS];
7996       board[EP_STATUS] = EP_NONE;
7997
7998       if( board[toY][toX] != EmptySquare ) 
7999            board[EP_STATUS] = EP_CAPTURE;  
8000
8001       if( board[fromY][fromX] == WhitePawn ) {
8002            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8003                board[EP_STATUS] = EP_PAWN_MOVE;
8004            if( toY-fromY==2) {
8005                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8006                         gameInfo.variant != VariantBerolina || toX < fromX)
8007                       board[EP_STATUS] = toX | berolina;
8008                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8009                         gameInfo.variant != VariantBerolina || toX > fromX) 
8010                       board[EP_STATUS] = toX;
8011            }
8012       } else 
8013       if( board[fromY][fromX] == BlackPawn ) {
8014            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8015                board[EP_STATUS] = EP_PAWN_MOVE; 
8016            if( toY-fromY== -2) {
8017                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8018                         gameInfo.variant != VariantBerolina || toX < fromX)
8019                       board[EP_STATUS] = toX | berolina;
8020                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8021                         gameInfo.variant != VariantBerolina || toX > fromX) 
8022                       board[EP_STATUS] = toX;
8023            }
8024        }
8025
8026        for(i=0; i<nrCastlingRights; i++) {
8027            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8028               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8029              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8030        }
8031
8032     }
8033
8034   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8035   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8036        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8037          
8038   if (fromX == toX && fromY == toY) return;
8039
8040   if (fromY == DROP_RANK) {
8041         /* must be first */
8042         piece = board[toY][toX] = (ChessSquare) fromX;
8043   } else {
8044      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8045      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8046      if(gameInfo.variant == VariantKnightmate)
8047          king += (int) WhiteUnicorn - (int) WhiteKing;
8048
8049     /* Code added by Tord: */
8050     /* FRC castling assumed when king captures friendly rook. */
8051     if (board[fromY][fromX] == WhiteKing &&
8052              board[toY][toX] == WhiteRook) {
8053       board[fromY][fromX] = EmptySquare;
8054       board[toY][toX] = EmptySquare;
8055       if(toX > fromX) {
8056         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8057       } else {
8058         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8059       }
8060     } else if (board[fromY][fromX] == BlackKing &&
8061                board[toY][toX] == BlackRook) {
8062       board[fromY][fromX] = EmptySquare;
8063       board[toY][toX] = EmptySquare;
8064       if(toX > fromX) {
8065         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8066       } else {
8067         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8068       }
8069     /* End of code added by Tord */
8070
8071     } else if (board[fromY][fromX] == king
8072         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8073         && toY == fromY && toX > fromX+1) {
8074         board[fromY][fromX] = EmptySquare;
8075         board[toY][toX] = king;
8076         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8077         board[fromY][BOARD_RGHT-1] = EmptySquare;
8078     } else if (board[fromY][fromX] == king
8079         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8080                && toY == fromY && toX < fromX-1) {
8081         board[fromY][fromX] = EmptySquare;
8082         board[toY][toX] = king;
8083         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8084         board[fromY][BOARD_LEFT] = EmptySquare;
8085     } else if (board[fromY][fromX] == WhitePawn
8086                && toY >= BOARD_HEIGHT-promoRank
8087                && gameInfo.variant != VariantXiangqi
8088                ) {
8089         /* white pawn promotion */
8090         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8091         if (board[toY][toX] == EmptySquare) {
8092             board[toY][toX] = WhiteQueen;
8093         }
8094         if(gameInfo.variant==VariantBughouse ||
8095            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8096             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8097         board[fromY][fromX] = EmptySquare;
8098     } else if ((fromY == BOARD_HEIGHT-4)
8099                && (toX != fromX)
8100                && gameInfo.variant != VariantXiangqi
8101                && gameInfo.variant != VariantBerolina
8102                && (board[fromY][fromX] == WhitePawn)
8103                && (board[toY][toX] == EmptySquare)) {
8104         board[fromY][fromX] = EmptySquare;
8105         board[toY][toX] = WhitePawn;
8106         captured = board[toY - 1][toX];
8107         board[toY - 1][toX] = EmptySquare;
8108     } else if ((fromY == BOARD_HEIGHT-4)
8109                && (toX == fromX)
8110                && gameInfo.variant == VariantBerolina
8111                && (board[fromY][fromX] == WhitePawn)
8112                && (board[toY][toX] == EmptySquare)) {
8113         board[fromY][fromX] = EmptySquare;
8114         board[toY][toX] = WhitePawn;
8115         if(oldEP & EP_BEROLIN_A) {
8116                 captured = board[fromY][fromX-1];
8117                 board[fromY][fromX-1] = EmptySquare;
8118         }else{  captured = board[fromY][fromX+1];
8119                 board[fromY][fromX+1] = EmptySquare;
8120         }
8121     } else if (board[fromY][fromX] == king
8122         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8123                && toY == fromY && toX > fromX+1) {
8124         board[fromY][fromX] = EmptySquare;
8125         board[toY][toX] = king;
8126         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8127         board[fromY][BOARD_RGHT-1] = EmptySquare;
8128     } else if (board[fromY][fromX] == king
8129         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8130                && toY == fromY && toX < fromX-1) {
8131         board[fromY][fromX] = EmptySquare;
8132         board[toY][toX] = king;
8133         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8134         board[fromY][BOARD_LEFT] = EmptySquare;
8135     } else if (fromY == 7 && fromX == 3
8136                && board[fromY][fromX] == BlackKing
8137                && toY == 7 && toX == 5) {
8138         board[fromY][fromX] = EmptySquare;
8139         board[toY][toX] = BlackKing;
8140         board[fromY][7] = EmptySquare;
8141         board[toY][4] = BlackRook;
8142     } else if (fromY == 7 && fromX == 3
8143                && board[fromY][fromX] == BlackKing
8144                && toY == 7 && toX == 1) {
8145         board[fromY][fromX] = EmptySquare;
8146         board[toY][toX] = BlackKing;
8147         board[fromY][0] = EmptySquare;
8148         board[toY][2] = BlackRook;
8149     } else if (board[fromY][fromX] == BlackPawn
8150                && toY < promoRank
8151                && gameInfo.variant != VariantXiangqi
8152                ) {
8153         /* black pawn promotion */
8154         board[toY][toX] = CharToPiece(ToLower(promoChar));
8155         if (board[toY][toX] == EmptySquare) {
8156             board[toY][toX] = BlackQueen;
8157         }
8158         if(gameInfo.variant==VariantBughouse ||
8159            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8160             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8161         board[fromY][fromX] = EmptySquare;
8162     } else if ((fromY == 3)
8163                && (toX != fromX)
8164                && gameInfo.variant != VariantXiangqi
8165                && gameInfo.variant != VariantBerolina
8166                && (board[fromY][fromX] == BlackPawn)
8167                && (board[toY][toX] == EmptySquare)) {
8168         board[fromY][fromX] = EmptySquare;
8169         board[toY][toX] = BlackPawn;
8170         captured = board[toY + 1][toX];
8171         board[toY + 1][toX] = EmptySquare;
8172     } else if ((fromY == 3)
8173                && (toX == fromX)
8174                && gameInfo.variant == VariantBerolina
8175                && (board[fromY][fromX] == BlackPawn)
8176                && (board[toY][toX] == EmptySquare)) {
8177         board[fromY][fromX] = EmptySquare;
8178         board[toY][toX] = BlackPawn;
8179         if(oldEP & EP_BEROLIN_A) {
8180                 captured = board[fromY][fromX-1];
8181                 board[fromY][fromX-1] = EmptySquare;
8182         }else{  captured = board[fromY][fromX+1];
8183                 board[fromY][fromX+1] = EmptySquare;
8184         }
8185     } else {
8186         board[toY][toX] = board[fromY][fromX];
8187         board[fromY][fromX] = EmptySquare;
8188     }
8189
8190     /* [HGM] now we promote for Shogi, if needed */
8191     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8192         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8193   }
8194
8195     if (gameInfo.holdingsWidth != 0) {
8196
8197       /* !!A lot more code needs to be written to support holdings  */
8198       /* [HGM] OK, so I have written it. Holdings are stored in the */
8199       /* penultimate board files, so they are automaticlly stored   */
8200       /* in the game history.                                       */
8201       if (fromY == DROP_RANK) {
8202         /* Delete from holdings, by decreasing count */
8203         /* and erasing image if necessary            */
8204         p = (int) fromX;
8205         if(p < (int) BlackPawn) { /* white drop */
8206              p -= (int)WhitePawn;
8207                  p = PieceToNumber((ChessSquare)p);
8208              if(p >= gameInfo.holdingsSize) p = 0;
8209              if(--board[p][BOARD_WIDTH-2] <= 0)
8210                   board[p][BOARD_WIDTH-1] = EmptySquare;
8211              if((int)board[p][BOARD_WIDTH-2] < 0)
8212                         board[p][BOARD_WIDTH-2] = 0;
8213         } else {                  /* black drop */
8214              p -= (int)BlackPawn;
8215                  p = PieceToNumber((ChessSquare)p);
8216              if(p >= gameInfo.holdingsSize) p = 0;
8217              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8218                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8219              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8220                         board[BOARD_HEIGHT-1-p][1] = 0;
8221         }
8222       }
8223       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8224           && gameInfo.variant != VariantBughouse        ) {
8225         /* [HGM] holdings: Add to holdings, if holdings exist */
8226         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8227                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8228                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8229         }
8230         p = (int) captured;
8231         if (p >= (int) BlackPawn) {
8232           p -= (int)BlackPawn;
8233           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8234                   /* in Shogi restore piece to its original  first */
8235                   captured = (ChessSquare) (DEMOTED captured);
8236                   p = DEMOTED p;
8237           }
8238           p = PieceToNumber((ChessSquare)p);
8239           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8240           board[p][BOARD_WIDTH-2]++;
8241           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8242         } else {
8243           p -= (int)WhitePawn;
8244           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8245                   captured = (ChessSquare) (DEMOTED captured);
8246                   p = DEMOTED p;
8247           }
8248           p = PieceToNumber((ChessSquare)p);
8249           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8250           board[BOARD_HEIGHT-1-p][1]++;
8251           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8252         }
8253       }
8254     } else if (gameInfo.variant == VariantAtomic) {
8255       if (captured != EmptySquare) {
8256         int y, x;
8257         for (y = toY-1; y <= toY+1; y++) {
8258           for (x = toX-1; x <= toX+1; x++) {
8259             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8260                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8261               board[y][x] = EmptySquare;
8262             }
8263           }
8264         }
8265         board[toY][toX] = EmptySquare;
8266       }
8267     }
8268     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8269         /* [HGM] Shogi promotions */
8270         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8271     }
8272
8273     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8274                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8275         // [HGM] superchess: take promotion piece out of holdings
8276         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8277         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8278             if(!--board[k][BOARD_WIDTH-2])
8279                 board[k][BOARD_WIDTH-1] = EmptySquare;
8280         } else {
8281             if(!--board[BOARD_HEIGHT-1-k][1])
8282                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8283         }
8284     }
8285
8286 }
8287
8288 /* Updates forwardMostMove */
8289 void
8290 MakeMove(fromX, fromY, toX, toY, promoChar)
8291      int fromX, fromY, toX, toY;
8292      int promoChar;
8293 {
8294 //    forwardMostMove++; // [HGM] bare: moved downstream
8295
8296     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8297         int timeLeft; static int lastLoadFlag=0; int king, piece;
8298         piece = boards[forwardMostMove][fromY][fromX];
8299         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8300         if(gameInfo.variant == VariantKnightmate)
8301             king += (int) WhiteUnicorn - (int) WhiteKing;
8302         if(forwardMostMove == 0) {
8303             if(blackPlaysFirst) 
8304                 fprintf(serverMoves, "%s;", second.tidy);
8305             fprintf(serverMoves, "%s;", first.tidy);
8306             if(!blackPlaysFirst) 
8307                 fprintf(serverMoves, "%s;", second.tidy);
8308         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8309         lastLoadFlag = loadFlag;
8310         // print base move
8311         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8312         // print castling suffix
8313         if( toY == fromY && piece == king ) {
8314             if(toX-fromX > 1)
8315                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8316             if(fromX-toX >1)
8317                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8318         }
8319         // e.p. suffix
8320         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8321              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8322              boards[forwardMostMove][toY][toX] == EmptySquare
8323              && fromX != toX )
8324                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8325         // promotion suffix
8326         if(promoChar != NULLCHAR)
8327                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8328         if(!loadFlag) {
8329             fprintf(serverMoves, "/%d/%d",
8330                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8331             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8332             else                      timeLeft = blackTimeRemaining/1000;
8333             fprintf(serverMoves, "/%d", timeLeft);
8334         }
8335         fflush(serverMoves);
8336     }
8337
8338     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8339       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8340                         0, 1);
8341       return;
8342     }
8343     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8344     if (commentList[forwardMostMove+1] != NULL) {
8345         free(commentList[forwardMostMove+1]);
8346         commentList[forwardMostMove+1] = NULL;
8347     }
8348     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8349     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8350     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8351     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8352     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8353     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8354     gameInfo.result = GameUnfinished;
8355     if (gameInfo.resultDetails != NULL) {
8356         free(gameInfo.resultDetails);
8357         gameInfo.resultDetails = NULL;
8358     }
8359     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8360                               moveList[forwardMostMove - 1]);
8361     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8362                              PosFlags(forwardMostMove - 1),
8363                              fromY, fromX, toY, toX, promoChar,
8364                              parseList[forwardMostMove - 1]);
8365     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8366       case MT_NONE:
8367       case MT_STALEMATE:
8368       default:
8369         break;
8370       case MT_CHECK:
8371         if(gameInfo.variant != VariantShogi)
8372             strcat(parseList[forwardMostMove - 1], "+");
8373         break;
8374       case MT_CHECKMATE:
8375       case MT_STAINMATE:
8376         strcat(parseList[forwardMostMove - 1], "#");
8377         break;
8378     }
8379     if (appData.debugMode) {
8380         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8381     }
8382
8383 }
8384
8385 /* Updates currentMove if not pausing */
8386 void
8387 ShowMove(fromX, fromY, toX, toY)
8388 {
8389     int instant = (gameMode == PlayFromGameFile) ?
8390         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8391     if(appData.noGUI) return;
8392     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8393         if (!instant) {
8394             if (forwardMostMove == currentMove + 1) {
8395                 AnimateMove(boards[forwardMostMove - 1],
8396                             fromX, fromY, toX, toY);
8397             }
8398             if (appData.highlightLastMove) {
8399                 SetHighlights(fromX, fromY, toX, toY);
8400             }
8401         }
8402         currentMove = forwardMostMove;
8403     }
8404
8405     if (instant) return;
8406
8407     DisplayMove(currentMove - 1);
8408     DrawPosition(FALSE, boards[currentMove]);
8409     DisplayBothClocks();
8410     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8411 }
8412
8413 void SendEgtPath(ChessProgramState *cps)
8414 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8415         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8416
8417         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8418
8419         while(*p) {
8420             char c, *q = name+1, *r, *s;
8421
8422             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8423             while(*p && *p != ',') *q++ = *p++;
8424             *q++ = ':'; *q = 0;
8425             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8426                 strcmp(name, ",nalimov:") == 0 ) {
8427                 // take nalimov path from the menu-changeable option first, if it is defined
8428                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8429                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8430             } else
8431             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8432                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8433                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8434                 s = r = StrStr(s, ":") + 1; // beginning of path info
8435                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8436                 c = *r; *r = 0;             // temporarily null-terminate path info
8437                     *--q = 0;               // strip of trailig ':' from name
8438                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8439                 *r = c;
8440                 SendToProgram(buf,cps);     // send egtbpath command for this format
8441             }
8442             if(*p == ',') p++; // read away comma to position for next format name
8443         }
8444 }
8445
8446 void
8447 InitChessProgram(cps, setup)
8448      ChessProgramState *cps;
8449      int setup; /* [HGM] needed to setup FRC opening position */
8450 {
8451     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8452     if (appData.noChessProgram) return;
8453     hintRequested = FALSE;
8454     bookRequested = FALSE;
8455
8456     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8457     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8458     if(cps->memSize) { /* [HGM] memory */
8459         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8460         SendToProgram(buf, cps);
8461     }
8462     SendEgtPath(cps); /* [HGM] EGT */
8463     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8464         sprintf(buf, "cores %d\n", appData.smpCores);
8465         SendToProgram(buf, cps);
8466     }
8467
8468     SendToProgram(cps->initString, cps);
8469     if (gameInfo.variant != VariantNormal &&
8470         gameInfo.variant != VariantLoadable
8471         /* [HGM] also send variant if board size non-standard */
8472         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8473                                             ) {
8474       char *v = VariantName(gameInfo.variant);
8475       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8476         /* [HGM] in protocol 1 we have to assume all variants valid */
8477         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8478         DisplayFatalError(buf, 0, 1);
8479         return;
8480       }
8481
8482       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8483       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8484       if( gameInfo.variant == VariantXiangqi )
8485            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8486       if( gameInfo.variant == VariantShogi )
8487            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8488       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8489            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8490       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8491                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8492            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8493       if( gameInfo.variant == VariantCourier )
8494            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8495       if( gameInfo.variant == VariantSuper )
8496            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8497       if( gameInfo.variant == VariantGreat )
8498            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8499
8500       if(overruled) {
8501            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8502                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8503            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8504            if(StrStr(cps->variants, b) == NULL) { 
8505                // specific sized variant not known, check if general sizing allowed
8506                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8507                    if(StrStr(cps->variants, "boardsize") == NULL) {
8508                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8509                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8510                        DisplayFatalError(buf, 0, 1);
8511                        return;
8512                    }
8513                    /* [HGM] here we really should compare with the maximum supported board size */
8514                }
8515            }
8516       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8517       sprintf(buf, "variant %s\n", b);
8518       SendToProgram(buf, cps);
8519     }
8520     currentlyInitializedVariant = gameInfo.variant;
8521
8522     /* [HGM] send opening position in FRC to first engine */
8523     if(setup) {
8524           SendToProgram("force\n", cps);
8525           SendBoard(cps, 0);
8526           /* engine is now in force mode! Set flag to wake it up after first move. */
8527           setboardSpoiledMachineBlack = 1;
8528     }
8529
8530     if (cps->sendICS) {
8531       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8532       SendToProgram(buf, cps);
8533     }
8534     cps->maybeThinking = FALSE;
8535     cps->offeredDraw = 0;
8536     if (!appData.icsActive) {
8537         SendTimeControl(cps, movesPerSession, timeControl,
8538                         timeIncrement, appData.searchDepth,
8539                         searchTime);
8540     }
8541     if (appData.showThinking 
8542         // [HGM] thinking: four options require thinking output to be sent
8543         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8544                                 ) {
8545         SendToProgram("post\n", cps);
8546     }
8547     SendToProgram("hard\n", cps);
8548     if (!appData.ponderNextMove) {
8549         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8550            it without being sure what state we are in first.  "hard"
8551            is not a toggle, so that one is OK.
8552          */
8553         SendToProgram("easy\n", cps);
8554     }
8555     if (cps->usePing) {
8556       sprintf(buf, "ping %d\n", ++cps->lastPing);
8557       SendToProgram(buf, cps);
8558     }
8559     cps->initDone = TRUE;
8560 }   
8561
8562
8563 void
8564 StartChessProgram(cps)
8565      ChessProgramState *cps;
8566 {
8567     char buf[MSG_SIZ];
8568     int err;
8569
8570     if (appData.noChessProgram) return;
8571     cps->initDone = FALSE;
8572
8573     if (strcmp(cps->host, "localhost") == 0) {
8574         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8575     } else if (*appData.remoteShell == NULLCHAR) {
8576         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8577     } else {
8578         if (*appData.remoteUser == NULLCHAR) {
8579           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8580                     cps->program);
8581         } else {
8582           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8583                     cps->host, appData.remoteUser, cps->program);
8584         }
8585         err = StartChildProcess(buf, "", &cps->pr);
8586     }
8587     
8588     if (err != 0) {
8589         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8590         DisplayFatalError(buf, err, 1);
8591         cps->pr = NoProc;
8592         cps->isr = NULL;
8593         return;
8594     }
8595     
8596     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8597     if (cps->protocolVersion > 1) {
8598       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8599       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8600       cps->comboCnt = 0;  //                and values of combo boxes
8601       SendToProgram(buf, cps);
8602     } else {
8603       SendToProgram("xboard\n", cps);
8604     }
8605 }
8606
8607
8608 void
8609 TwoMachinesEventIfReady P((void))
8610 {
8611   if (first.lastPing != first.lastPong) {
8612     DisplayMessage("", _("Waiting for first chess program"));
8613     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8614     return;
8615   }
8616   if (second.lastPing != second.lastPong) {
8617     DisplayMessage("", _("Waiting for second chess program"));
8618     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8619     return;
8620   }
8621   ThawUI();
8622   TwoMachinesEvent();
8623 }
8624
8625 void
8626 NextMatchGame P((void))
8627 {
8628     int index; /* [HGM] autoinc: step load index during match */
8629     Reset(FALSE, TRUE);
8630     if (*appData.loadGameFile != NULLCHAR) {
8631         index = appData.loadGameIndex;
8632         if(index < 0) { // [HGM] autoinc
8633             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8634             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8635         } 
8636         LoadGameFromFile(appData.loadGameFile,
8637                          index,
8638                          appData.loadGameFile, FALSE);
8639     } else if (*appData.loadPositionFile != NULLCHAR) {
8640         index = appData.loadPositionIndex;
8641         if(index < 0) { // [HGM] autoinc
8642             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8643             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8644         } 
8645         LoadPositionFromFile(appData.loadPositionFile,
8646                              index,
8647                              appData.loadPositionFile);
8648     }
8649     TwoMachinesEventIfReady();
8650 }
8651
8652 void UserAdjudicationEvent( int result )
8653 {
8654     ChessMove gameResult = GameIsDrawn;
8655
8656     if( result > 0 ) {
8657         gameResult = WhiteWins;
8658     }
8659     else if( result < 0 ) {
8660         gameResult = BlackWins;
8661     }
8662
8663     if( gameMode == TwoMachinesPlay ) {
8664         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8665     }
8666 }
8667
8668
8669 // [HGM] save: calculate checksum of game to make games easily identifiable
8670 int StringCheckSum(char *s)
8671 {
8672         int i = 0;
8673         if(s==NULL) return 0;
8674         while(*s) i = i*259 + *s++;
8675         return i;
8676 }
8677
8678 int GameCheckSum()
8679 {
8680         int i, sum=0;
8681         for(i=backwardMostMove; i<forwardMostMove; i++) {
8682                 sum += pvInfoList[i].depth;
8683                 sum += StringCheckSum(parseList[i]);
8684                 sum += StringCheckSum(commentList[i]);
8685                 sum *= 261;
8686         }
8687         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8688         return sum + StringCheckSum(commentList[i]);
8689 } // end of save patch
8690
8691 void
8692 GameEnds(result, resultDetails, whosays)
8693      ChessMove result;
8694      char *resultDetails;
8695      int whosays;
8696 {
8697     GameMode nextGameMode;
8698     int isIcsGame;
8699     char buf[MSG_SIZ];
8700
8701     if(endingGame) return; /* [HGM] crash: forbid recursion */
8702     endingGame = 1;
8703
8704     if (appData.debugMode) {
8705       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8706               result, resultDetails ? resultDetails : "(null)", whosays);
8707     }
8708
8709     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8710         /* If we are playing on ICS, the server decides when the
8711            game is over, but the engine can offer to draw, claim 
8712            a draw, or resign. 
8713          */
8714 #if ZIPPY
8715         if (appData.zippyPlay && first.initDone) {
8716             if (result == GameIsDrawn) {
8717                 /* In case draw still needs to be claimed */
8718                 SendToICS(ics_prefix);
8719                 SendToICS("draw\n");
8720             } else if (StrCaseStr(resultDetails, "resign")) {
8721                 SendToICS(ics_prefix);
8722                 SendToICS("resign\n");
8723             }
8724         }
8725 #endif
8726         endingGame = 0; /* [HGM] crash */
8727         return;
8728     }
8729
8730     /* If we're loading the game from a file, stop */
8731     if (whosays == GE_FILE) {
8732       (void) StopLoadGameTimer();
8733       gameFileFP = NULL;
8734     }
8735
8736     /* Cancel draw offers */
8737     first.offeredDraw = second.offeredDraw = 0;
8738
8739     /* If this is an ICS game, only ICS can really say it's done;
8740        if not, anyone can. */
8741     isIcsGame = (gameMode == IcsPlayingWhite || 
8742                  gameMode == IcsPlayingBlack || 
8743                  gameMode == IcsObserving    || 
8744                  gameMode == IcsExamining);
8745
8746     if (!isIcsGame || whosays == GE_ICS) {
8747         /* OK -- not an ICS game, or ICS said it was done */
8748         StopClocks();
8749         if (!isIcsGame && !appData.noChessProgram) 
8750           SetUserThinkingEnables();
8751     
8752         /* [HGM] if a machine claims the game end we verify this claim */
8753         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8754             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8755                 char claimer;
8756                 ChessMove trueResult = (ChessMove) -1;
8757
8758                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8759                                             first.twoMachinesColor[0] :
8760                                             second.twoMachinesColor[0] ;
8761
8762                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8763                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8764                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8765                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8766                 } else
8767                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8768                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8769                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8770                 } else
8771                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8772                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8773                 }
8774
8775                 // now verify win claims, but not in drop games, as we don't understand those yet
8776                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8777                                                  || gameInfo.variant == VariantGreat) &&
8778                     (result == WhiteWins && claimer == 'w' ||
8779                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8780                       if (appData.debugMode) {
8781                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8782                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8783                       }
8784                       if(result != trueResult) {
8785                               sprintf(buf, "False win claim: '%s'", resultDetails);
8786                               result = claimer == 'w' ? BlackWins : WhiteWins;
8787                               resultDetails = buf;
8788                       }
8789                 } else
8790                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8791                     && (forwardMostMove <= backwardMostMove ||
8792                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8793                         (claimer=='b')==(forwardMostMove&1))
8794                                                                                   ) {
8795                       /* [HGM] verify: draws that were not flagged are false claims */
8796                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8797                       result = claimer == 'w' ? BlackWins : WhiteWins;
8798                       resultDetails = buf;
8799                 }
8800                 /* (Claiming a loss is accepted no questions asked!) */
8801             }
8802             /* [HGM] bare: don't allow bare King to win */
8803             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8804                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8805                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8806                && result != GameIsDrawn)
8807             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8808                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8809                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8810                         if(p >= 0 && p <= (int)WhiteKing) k++;
8811                 }
8812                 if (appData.debugMode) {
8813                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8814                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8815                 }
8816                 if(k <= 1) {
8817                         result = GameIsDrawn;
8818                         sprintf(buf, "%s but bare king", resultDetails);
8819                         resultDetails = buf;
8820                 }
8821             }
8822         }
8823
8824
8825         if(serverMoves != NULL && !loadFlag) { char c = '=';
8826             if(result==WhiteWins) c = '+';
8827             if(result==BlackWins) c = '-';
8828             if(resultDetails != NULL)
8829                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8830         }
8831         if (resultDetails != NULL) {
8832             gameInfo.result = result;
8833             gameInfo.resultDetails = StrSave(resultDetails);
8834
8835             /* display last move only if game was not loaded from file */
8836             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8837                 DisplayMove(currentMove - 1);
8838     
8839             if (forwardMostMove != 0) {
8840                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8841                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8842                                                                 ) {
8843                     if (*appData.saveGameFile != NULLCHAR) {
8844                         SaveGameToFile(appData.saveGameFile, TRUE);
8845                     } else if (appData.autoSaveGames) {
8846                         AutoSaveGame();
8847                     }
8848                     if (*appData.savePositionFile != NULLCHAR) {
8849                         SavePositionToFile(appData.savePositionFile);
8850                     }
8851                 }
8852             }
8853
8854             /* Tell program how game ended in case it is learning */
8855             /* [HGM] Moved this to after saving the PGN, just in case */
8856             /* engine died and we got here through time loss. In that */
8857             /* case we will get a fatal error writing the pipe, which */
8858             /* would otherwise lose us the PGN.                       */
8859             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8860             /* output during GameEnds should never be fatal anymore   */
8861             if (gameMode == MachinePlaysWhite ||
8862                 gameMode == MachinePlaysBlack ||
8863                 gameMode == TwoMachinesPlay ||
8864                 gameMode == IcsPlayingWhite ||
8865                 gameMode == IcsPlayingBlack ||
8866                 gameMode == BeginningOfGame) {
8867                 char buf[MSG_SIZ];
8868                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8869                         resultDetails);
8870                 if (first.pr != NoProc) {
8871                     SendToProgram(buf, &first);
8872                 }
8873                 if (second.pr != NoProc &&
8874                     gameMode == TwoMachinesPlay) {
8875                     SendToProgram(buf, &second);
8876                 }
8877             }
8878         }
8879
8880         if (appData.icsActive) {
8881             if (appData.quietPlay &&
8882                 (gameMode == IcsPlayingWhite ||
8883                  gameMode == IcsPlayingBlack)) {
8884                 SendToICS(ics_prefix);
8885                 SendToICS("set shout 1\n");
8886             }
8887             nextGameMode = IcsIdle;
8888             ics_user_moved = FALSE;
8889             /* clean up premove.  It's ugly when the game has ended and the
8890              * premove highlights are still on the board.
8891              */
8892             if (gotPremove) {
8893               gotPremove = FALSE;
8894               ClearPremoveHighlights();
8895               DrawPosition(FALSE, boards[currentMove]);
8896             }
8897             if (whosays == GE_ICS) {
8898                 switch (result) {
8899                 case WhiteWins:
8900                     if (gameMode == IcsPlayingWhite)
8901                         PlayIcsWinSound();
8902                     else if(gameMode == IcsPlayingBlack)
8903                         PlayIcsLossSound();
8904                     break;
8905                 case BlackWins:
8906                     if (gameMode == IcsPlayingBlack)
8907                         PlayIcsWinSound();
8908                     else if(gameMode == IcsPlayingWhite)
8909                         PlayIcsLossSound();
8910                     break;
8911                 case GameIsDrawn:
8912                     PlayIcsDrawSound();
8913                     break;
8914                 default:
8915                     PlayIcsUnfinishedSound();
8916                 }
8917             }
8918         } else if (gameMode == EditGame ||
8919                    gameMode == PlayFromGameFile || 
8920                    gameMode == AnalyzeMode || 
8921                    gameMode == AnalyzeFile) {
8922             nextGameMode = gameMode;
8923         } else {
8924             nextGameMode = EndOfGame;
8925         }
8926         pausing = FALSE;
8927         ModeHighlight();
8928     } else {
8929         nextGameMode = gameMode;
8930     }
8931
8932     if (appData.noChessProgram) {
8933         gameMode = nextGameMode;
8934         ModeHighlight();
8935         endingGame = 0; /* [HGM] crash */
8936         return;
8937     }
8938
8939     if (first.reuse) {
8940         /* Put first chess program into idle state */
8941         if (first.pr != NoProc &&
8942             (gameMode == MachinePlaysWhite ||
8943              gameMode == MachinePlaysBlack ||
8944              gameMode == TwoMachinesPlay ||
8945              gameMode == IcsPlayingWhite ||
8946              gameMode == IcsPlayingBlack ||
8947              gameMode == BeginningOfGame)) {
8948             SendToProgram("force\n", &first);
8949             if (first.usePing) {
8950               char buf[MSG_SIZ];
8951               sprintf(buf, "ping %d\n", ++first.lastPing);
8952               SendToProgram(buf, &first);
8953             }
8954         }
8955     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8956         /* Kill off first chess program */
8957         if (first.isr != NULL)
8958           RemoveInputSource(first.isr);
8959         first.isr = NULL;
8960     
8961         if (first.pr != NoProc) {
8962             ExitAnalyzeMode();
8963             DoSleep( appData.delayBeforeQuit );
8964             SendToProgram("quit\n", &first);
8965             DoSleep( appData.delayAfterQuit );
8966             DestroyChildProcess(first.pr, first.useSigterm);
8967         }
8968         first.pr = NoProc;
8969     }
8970     if (second.reuse) {
8971         /* Put second chess program into idle state */
8972         if (second.pr != NoProc &&
8973             gameMode == TwoMachinesPlay) {
8974             SendToProgram("force\n", &second);
8975             if (second.usePing) {
8976               char buf[MSG_SIZ];
8977               sprintf(buf, "ping %d\n", ++second.lastPing);
8978               SendToProgram(buf, &second);
8979             }
8980         }
8981     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8982         /* Kill off second chess program */
8983         if (second.isr != NULL)
8984           RemoveInputSource(second.isr);
8985         second.isr = NULL;
8986     
8987         if (second.pr != NoProc) {
8988             DoSleep( appData.delayBeforeQuit );
8989             SendToProgram("quit\n", &second);
8990             DoSleep( appData.delayAfterQuit );
8991             DestroyChildProcess(second.pr, second.useSigterm);
8992         }
8993         second.pr = NoProc;
8994     }
8995
8996     if (matchMode && gameMode == TwoMachinesPlay) {
8997         switch (result) {
8998         case WhiteWins:
8999           if (first.twoMachinesColor[0] == 'w') {
9000             first.matchWins++;
9001           } else {
9002             second.matchWins++;
9003           }
9004           break;
9005         case BlackWins:
9006           if (first.twoMachinesColor[0] == 'b') {
9007             first.matchWins++;
9008           } else {
9009             second.matchWins++;
9010           }
9011           break;
9012         default:
9013           break;
9014         }
9015         if (matchGame < appData.matchGames) {
9016             char *tmp;
9017             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9018                 tmp = first.twoMachinesColor;
9019                 first.twoMachinesColor = second.twoMachinesColor;
9020                 second.twoMachinesColor = tmp;
9021             }
9022             gameMode = nextGameMode;
9023             matchGame++;
9024             if(appData.matchPause>10000 || appData.matchPause<10)
9025                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9026             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9027             endingGame = 0; /* [HGM] crash */
9028             return;
9029         } else {
9030             char buf[MSG_SIZ];
9031             gameMode = nextGameMode;
9032             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9033                     first.tidy, second.tidy,
9034                     first.matchWins, second.matchWins,
9035                     appData.matchGames - (first.matchWins + second.matchWins));
9036             DisplayFatalError(buf, 0, 0);
9037         }
9038     }
9039     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9040         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9041       ExitAnalyzeMode();
9042     gameMode = nextGameMode;
9043     ModeHighlight();
9044     endingGame = 0;  /* [HGM] crash */
9045 }
9046
9047 /* Assumes program was just initialized (initString sent).
9048    Leaves program in force mode. */
9049 void
9050 FeedMovesToProgram(cps, upto) 
9051      ChessProgramState *cps;
9052      int upto;
9053 {
9054     int i;
9055     
9056     if (appData.debugMode)
9057       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9058               startedFromSetupPosition ? "position and " : "",
9059               backwardMostMove, upto, cps->which);
9060     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9061         // [HGM] variantswitch: make engine aware of new variant
9062         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9063                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9064         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9065         SendToProgram(buf, cps);
9066         currentlyInitializedVariant = gameInfo.variant;
9067     }
9068     SendToProgram("force\n", cps);
9069     if (startedFromSetupPosition) {
9070         SendBoard(cps, backwardMostMove);
9071     if (appData.debugMode) {
9072         fprintf(debugFP, "feedMoves\n");
9073     }
9074     }
9075     for (i = backwardMostMove; i < upto; i++) {
9076         SendMoveToProgram(i, cps);
9077     }
9078 }
9079
9080
9081 void
9082 ResurrectChessProgram()
9083 {
9084      /* The chess program may have exited.
9085         If so, restart it and feed it all the moves made so far. */
9086
9087     if (appData.noChessProgram || first.pr != NoProc) return;
9088     
9089     StartChessProgram(&first);
9090     InitChessProgram(&first, FALSE);
9091     FeedMovesToProgram(&first, currentMove);
9092
9093     if (!first.sendTime) {
9094         /* can't tell gnuchess what its clock should read,
9095            so we bow to its notion. */
9096         ResetClocks();
9097         timeRemaining[0][currentMove] = whiteTimeRemaining;
9098         timeRemaining[1][currentMove] = blackTimeRemaining;
9099     }
9100
9101     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9102                 appData.icsEngineAnalyze) && first.analysisSupport) {
9103       SendToProgram("analyze\n", &first);
9104       first.analyzing = TRUE;
9105     }
9106 }
9107
9108 /*
9109  * Button procedures
9110  */
9111 void
9112 Reset(redraw, init)
9113      int redraw, init;
9114 {
9115     int i;
9116
9117     if (appData.debugMode) {
9118         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9119                 redraw, init, gameMode);
9120     }
9121     CleanupTail(); // [HGM] vari: delete any stored variations
9122     pausing = pauseExamInvalid = FALSE;
9123     startedFromSetupPosition = blackPlaysFirst = FALSE;
9124     firstMove = TRUE;
9125     whiteFlag = blackFlag = FALSE;
9126     userOfferedDraw = FALSE;
9127     hintRequested = bookRequested = FALSE;
9128     first.maybeThinking = FALSE;
9129     second.maybeThinking = FALSE;
9130     first.bookSuspend = FALSE; // [HGM] book
9131     second.bookSuspend = FALSE;
9132     thinkOutput[0] = NULLCHAR;
9133     lastHint[0] = NULLCHAR;
9134     ClearGameInfo(&gameInfo);
9135     gameInfo.variant = StringToVariant(appData.variant);
9136     ics_user_moved = ics_clock_paused = FALSE;
9137     ics_getting_history = H_FALSE;
9138     ics_gamenum = -1;
9139     white_holding[0] = black_holding[0] = NULLCHAR;
9140     ClearProgramStats();
9141     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9142     
9143     ResetFrontEnd();
9144     ClearHighlights();
9145     flipView = appData.flipView;
9146     ClearPremoveHighlights();
9147     gotPremove = FALSE;
9148     alarmSounded = FALSE;
9149
9150     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9151     if(appData.serverMovesName != NULL) {
9152         /* [HGM] prepare to make moves file for broadcasting */
9153         clock_t t = clock();
9154         if(serverMoves != NULL) fclose(serverMoves);
9155         serverMoves = fopen(appData.serverMovesName, "r");
9156         if(serverMoves != NULL) {
9157             fclose(serverMoves);
9158             /* delay 15 sec before overwriting, so all clients can see end */
9159             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9160         }
9161         serverMoves = fopen(appData.serverMovesName, "w");
9162     }
9163
9164     ExitAnalyzeMode();
9165     gameMode = BeginningOfGame;
9166     ModeHighlight();
9167     if(appData.icsActive) gameInfo.variant = VariantNormal;
9168     currentMove = forwardMostMove = backwardMostMove = 0;
9169     InitPosition(redraw);
9170     for (i = 0; i < MAX_MOVES; i++) {
9171         if (commentList[i] != NULL) {
9172             free(commentList[i]);
9173             commentList[i] = NULL;
9174         }
9175     }
9176     ResetClocks();
9177     timeRemaining[0][0] = whiteTimeRemaining;
9178     timeRemaining[1][0] = blackTimeRemaining;
9179     if (first.pr == NULL) {
9180         StartChessProgram(&first);
9181     }
9182     if (init) {
9183             InitChessProgram(&first, startedFromSetupPosition);
9184     }
9185     DisplayTitle("");
9186     DisplayMessage("", "");
9187     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9188     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9189 }
9190
9191 void
9192 AutoPlayGameLoop()
9193 {
9194     for (;;) {
9195         if (!AutoPlayOneMove())
9196           return;
9197         if (matchMode || appData.timeDelay == 0)
9198           continue;
9199         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9200           return;
9201         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9202         break;
9203     }
9204 }
9205
9206
9207 int
9208 AutoPlayOneMove()
9209 {
9210     int fromX, fromY, toX, toY;
9211
9212     if (appData.debugMode) {
9213       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9214     }
9215
9216     if (gameMode != PlayFromGameFile)
9217       return FALSE;
9218
9219     if (currentMove >= forwardMostMove) {
9220       gameMode = EditGame;
9221       ModeHighlight();
9222
9223       /* [AS] Clear current move marker at the end of a game */
9224       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9225
9226       return FALSE;
9227     }
9228     
9229     toX = moveList[currentMove][2] - AAA;
9230     toY = moveList[currentMove][3] - ONE;
9231
9232     if (moveList[currentMove][1] == '@') {
9233         if (appData.highlightLastMove) {
9234             SetHighlights(-1, -1, toX, toY);
9235         }
9236     } else {
9237         fromX = moveList[currentMove][0] - AAA;
9238         fromY = moveList[currentMove][1] - ONE;
9239
9240         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9241
9242         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9243
9244         if (appData.highlightLastMove) {
9245             SetHighlights(fromX, fromY, toX, toY);
9246         }
9247     }
9248     DisplayMove(currentMove);
9249     SendMoveToProgram(currentMove++, &first);
9250     DisplayBothClocks();
9251     DrawPosition(FALSE, boards[currentMove]);
9252     // [HGM] PV info: always display, routine tests if empty
9253     DisplayComment(currentMove - 1, commentList[currentMove]);
9254     return TRUE;
9255 }
9256
9257
9258 int
9259 LoadGameOneMove(readAhead)
9260      ChessMove readAhead;
9261 {
9262     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9263     char promoChar = NULLCHAR;
9264     ChessMove moveType;
9265     char move[MSG_SIZ];
9266     char *p, *q;
9267     
9268     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9269         gameMode != AnalyzeMode && gameMode != Training) {
9270         gameFileFP = NULL;
9271         return FALSE;
9272     }
9273     
9274     yyboardindex = forwardMostMove;
9275     if (readAhead != (ChessMove)0) {
9276       moveType = readAhead;
9277     } else {
9278       if (gameFileFP == NULL)
9279           return FALSE;
9280       moveType = (ChessMove) yylex();
9281     }
9282     
9283     done = FALSE;
9284     switch (moveType) {
9285       case Comment:
9286         if (appData.debugMode) 
9287           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9288         p = yy_text;
9289
9290         /* append the comment but don't display it */
9291         AppendComment(currentMove, p, FALSE);
9292         return TRUE;
9293
9294       case WhiteCapturesEnPassant:
9295       case BlackCapturesEnPassant:
9296       case WhitePromotionChancellor:
9297       case BlackPromotionChancellor:
9298       case WhitePromotionArchbishop:
9299       case BlackPromotionArchbishop:
9300       case WhitePromotionCentaur:
9301       case BlackPromotionCentaur:
9302       case WhitePromotionQueen:
9303       case BlackPromotionQueen:
9304       case WhitePromotionRook:
9305       case BlackPromotionRook:
9306       case WhitePromotionBishop:
9307       case BlackPromotionBishop:
9308       case WhitePromotionKnight:
9309       case BlackPromotionKnight:
9310       case WhitePromotionKing:
9311       case BlackPromotionKing:
9312       case NormalMove:
9313       case WhiteKingSideCastle:
9314       case WhiteQueenSideCastle:
9315       case BlackKingSideCastle:
9316       case BlackQueenSideCastle:
9317       case WhiteKingSideCastleWild:
9318       case WhiteQueenSideCastleWild:
9319       case BlackKingSideCastleWild:
9320       case BlackQueenSideCastleWild:
9321       /* PUSH Fabien */
9322       case WhiteHSideCastleFR:
9323       case WhiteASideCastleFR:
9324       case BlackHSideCastleFR:
9325       case BlackASideCastleFR:
9326       /* POP Fabien */
9327         if (appData.debugMode)
9328           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9329         fromX = currentMoveString[0] - AAA;
9330         fromY = currentMoveString[1] - ONE;
9331         toX = currentMoveString[2] - AAA;
9332         toY = currentMoveString[3] - ONE;
9333         promoChar = currentMoveString[4];
9334         break;
9335
9336       case WhiteDrop:
9337       case BlackDrop:
9338         if (appData.debugMode)
9339           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9340         fromX = moveType == WhiteDrop ?
9341           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9342         (int) CharToPiece(ToLower(currentMoveString[0]));
9343         fromY = DROP_RANK;
9344         toX = currentMoveString[2] - AAA;
9345         toY = currentMoveString[3] - ONE;
9346         break;
9347
9348       case WhiteWins:
9349       case BlackWins:
9350       case GameIsDrawn:
9351       case GameUnfinished:
9352         if (appData.debugMode)
9353           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9354         p = strchr(yy_text, '{');
9355         if (p == NULL) p = strchr(yy_text, '(');
9356         if (p == NULL) {
9357             p = yy_text;
9358             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9359         } else {
9360             q = strchr(p, *p == '{' ? '}' : ')');
9361             if (q != NULL) *q = NULLCHAR;
9362             p++;
9363         }
9364         GameEnds(moveType, p, GE_FILE);
9365         done = TRUE;
9366         if (cmailMsgLoaded) {
9367             ClearHighlights();
9368             flipView = WhiteOnMove(currentMove);
9369             if (moveType == GameUnfinished) flipView = !flipView;
9370             if (appData.debugMode)
9371               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9372         }
9373         break;
9374
9375       case (ChessMove) 0:       /* end of file */
9376         if (appData.debugMode)
9377           fprintf(debugFP, "Parser hit end of file\n");
9378         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9379           case MT_NONE:
9380           case MT_CHECK:
9381             break;
9382           case MT_CHECKMATE:
9383           case MT_STAINMATE:
9384             if (WhiteOnMove(currentMove)) {
9385                 GameEnds(BlackWins, "Black mates", GE_FILE);
9386             } else {
9387                 GameEnds(WhiteWins, "White mates", GE_FILE);
9388             }
9389             break;
9390           case MT_STALEMATE:
9391             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9392             break;
9393         }
9394         done = TRUE;
9395         break;
9396
9397       case MoveNumberOne:
9398         if (lastLoadGameStart == GNUChessGame) {
9399             /* GNUChessGames have numbers, but they aren't move numbers */
9400             if (appData.debugMode)
9401               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9402                       yy_text, (int) moveType);
9403             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9404         }
9405         /* else fall thru */
9406
9407       case XBoardGame:
9408       case GNUChessGame:
9409       case PGNTag:
9410         /* Reached start of next game in file */
9411         if (appData.debugMode)
9412           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9413         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9414           case MT_NONE:
9415           case MT_CHECK:
9416             break;
9417           case MT_CHECKMATE:
9418           case MT_STAINMATE:
9419             if (WhiteOnMove(currentMove)) {
9420                 GameEnds(BlackWins, "Black mates", GE_FILE);
9421             } else {
9422                 GameEnds(WhiteWins, "White mates", GE_FILE);
9423             }
9424             break;
9425           case MT_STALEMATE:
9426             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9427             break;
9428         }
9429         done = TRUE;
9430         break;
9431
9432       case PositionDiagram:     /* should not happen; ignore */
9433       case ElapsedTime:         /* ignore */
9434       case NAG:                 /* ignore */
9435         if (appData.debugMode)
9436           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9437                   yy_text, (int) moveType);
9438         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9439
9440       case IllegalMove:
9441         if (appData.testLegality) {
9442             if (appData.debugMode)
9443               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9444             sprintf(move, _("Illegal move: %d.%s%s"),
9445                     (forwardMostMove / 2) + 1,
9446                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9447             DisplayError(move, 0);
9448             done = TRUE;
9449         } else {
9450             if (appData.debugMode)
9451               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9452                       yy_text, currentMoveString);
9453             fromX = currentMoveString[0] - AAA;
9454             fromY = currentMoveString[1] - ONE;
9455             toX = currentMoveString[2] - AAA;
9456             toY = currentMoveString[3] - ONE;
9457             promoChar = currentMoveString[4];
9458         }
9459         break;
9460
9461       case AmbiguousMove:
9462         if (appData.debugMode)
9463           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9464         sprintf(move, _("Ambiguous move: %d.%s%s"),
9465                 (forwardMostMove / 2) + 1,
9466                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9467         DisplayError(move, 0);
9468         done = TRUE;
9469         break;
9470
9471       default:
9472       case ImpossibleMove:
9473         if (appData.debugMode)
9474           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9475         sprintf(move, _("Illegal move: %d.%s%s"),
9476                 (forwardMostMove / 2) + 1,
9477                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9478         DisplayError(move, 0);
9479         done = TRUE;
9480         break;
9481     }
9482
9483     if (done) {
9484         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9485             DrawPosition(FALSE, boards[currentMove]);
9486             DisplayBothClocks();
9487             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9488               DisplayComment(currentMove - 1, commentList[currentMove]);
9489         }
9490         (void) StopLoadGameTimer();
9491         gameFileFP = NULL;
9492         cmailOldMove = forwardMostMove;
9493         return FALSE;
9494     } else {
9495         /* currentMoveString is set as a side-effect of yylex */
9496         strcat(currentMoveString, "\n");
9497         strcpy(moveList[forwardMostMove], currentMoveString);
9498         
9499         thinkOutput[0] = NULLCHAR;
9500         MakeMove(fromX, fromY, toX, toY, promoChar);
9501         currentMove = forwardMostMove;
9502         return TRUE;
9503     }
9504 }
9505
9506 /* Load the nth game from the given file */
9507 int
9508 LoadGameFromFile(filename, n, title, useList)
9509      char *filename;
9510      int n;
9511      char *title;
9512      /*Boolean*/ int useList;
9513 {
9514     FILE *f;
9515     char buf[MSG_SIZ];
9516
9517     if (strcmp(filename, "-") == 0) {
9518         f = stdin;
9519         title = "stdin";
9520     } else {
9521         f = fopen(filename, "rb");
9522         if (f == NULL) {
9523           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9524             DisplayError(buf, errno);
9525             return FALSE;
9526         }
9527     }
9528     if (fseek(f, 0, 0) == -1) {
9529         /* f is not seekable; probably a pipe */
9530         useList = FALSE;
9531     }
9532     if (useList && n == 0) {
9533         int error = GameListBuild(f);
9534         if (error) {
9535             DisplayError(_("Cannot build game list"), error);
9536         } else if (!ListEmpty(&gameList) &&
9537                    ((ListGame *) gameList.tailPred)->number > 1) {
9538             GameListPopUp(f, title);
9539             return TRUE;
9540         }
9541         GameListDestroy();
9542         n = 1;
9543     }
9544     if (n == 0) n = 1;
9545     return LoadGame(f, n, title, FALSE);
9546 }
9547
9548
9549 void
9550 MakeRegisteredMove()
9551 {
9552     int fromX, fromY, toX, toY;
9553     char promoChar;
9554     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9555         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9556           case CMAIL_MOVE:
9557           case CMAIL_DRAW:
9558             if (appData.debugMode)
9559               fprintf(debugFP, "Restoring %s for game %d\n",
9560                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9561     
9562             thinkOutput[0] = NULLCHAR;
9563             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9564             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9565             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9566             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9567             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9568             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9569             MakeMove(fromX, fromY, toX, toY, promoChar);
9570             ShowMove(fromX, fromY, toX, toY);
9571               
9572             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9573               case MT_NONE:
9574               case MT_CHECK:
9575                 break;
9576                 
9577               case MT_CHECKMATE:
9578               case MT_STAINMATE:
9579                 if (WhiteOnMove(currentMove)) {
9580                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9581                 } else {
9582                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9583                 }
9584                 break;
9585                 
9586               case MT_STALEMATE:
9587                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9588                 break;
9589             }
9590
9591             break;
9592             
9593           case CMAIL_RESIGN:
9594             if (WhiteOnMove(currentMove)) {
9595                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9596             } else {
9597                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9598             }
9599             break;
9600             
9601           case CMAIL_ACCEPT:
9602             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9603             break;
9604               
9605           default:
9606             break;
9607         }
9608     }
9609
9610     return;
9611 }
9612
9613 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9614 int
9615 CmailLoadGame(f, gameNumber, title, useList)
9616      FILE *f;
9617      int gameNumber;
9618      char *title;
9619      int useList;
9620 {
9621     int retVal;
9622
9623     if (gameNumber > nCmailGames) {
9624         DisplayError(_("No more games in this message"), 0);
9625         return FALSE;
9626     }
9627     if (f == lastLoadGameFP) {
9628         int offset = gameNumber - lastLoadGameNumber;
9629         if (offset == 0) {
9630             cmailMsg[0] = NULLCHAR;
9631             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9632                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9633                 nCmailMovesRegistered--;
9634             }
9635             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9636             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9637                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9638             }
9639         } else {
9640             if (! RegisterMove()) return FALSE;
9641         }
9642     }
9643
9644     retVal = LoadGame(f, gameNumber, title, useList);
9645
9646     /* Make move registered during previous look at this game, if any */
9647     MakeRegisteredMove();
9648
9649     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9650         commentList[currentMove]
9651           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9652         DisplayComment(currentMove - 1, commentList[currentMove]);
9653     }
9654
9655     return retVal;
9656 }
9657
9658 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9659 int
9660 ReloadGame(offset)
9661      int offset;
9662 {
9663     int gameNumber = lastLoadGameNumber + offset;
9664     if (lastLoadGameFP == NULL) {
9665         DisplayError(_("No game has been loaded yet"), 0);
9666         return FALSE;
9667     }
9668     if (gameNumber <= 0) {
9669         DisplayError(_("Can't back up any further"), 0);
9670         return FALSE;
9671     }
9672     if (cmailMsgLoaded) {
9673         return CmailLoadGame(lastLoadGameFP, gameNumber,
9674                              lastLoadGameTitle, lastLoadGameUseList);
9675     } else {
9676         return LoadGame(lastLoadGameFP, gameNumber,
9677                         lastLoadGameTitle, lastLoadGameUseList);
9678     }
9679 }
9680
9681
9682
9683 /* Load the nth game from open file f */
9684 int
9685 LoadGame(f, gameNumber, title, useList)
9686      FILE *f;
9687      int gameNumber;
9688      char *title;
9689      int useList;
9690 {
9691     ChessMove cm;
9692     char buf[MSG_SIZ];
9693     int gn = gameNumber;
9694     ListGame *lg = NULL;
9695     int numPGNTags = 0;
9696     int err;
9697     GameMode oldGameMode;
9698     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9699
9700     if (appData.debugMode) 
9701         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9702
9703     if (gameMode == Training )
9704         SetTrainingModeOff();
9705
9706     oldGameMode = gameMode;
9707     if (gameMode != BeginningOfGame) {
9708       Reset(FALSE, TRUE);
9709     }
9710
9711     gameFileFP = f;
9712     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9713         fclose(lastLoadGameFP);
9714     }
9715
9716     if (useList) {
9717         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9718         
9719         if (lg) {
9720             fseek(f, lg->offset, 0);
9721             GameListHighlight(gameNumber);
9722             gn = 1;
9723         }
9724         else {
9725             DisplayError(_("Game number out of range"), 0);
9726             return FALSE;
9727         }
9728     } else {
9729         GameListDestroy();
9730         if (fseek(f, 0, 0) == -1) {
9731             if (f == lastLoadGameFP ?
9732                 gameNumber == lastLoadGameNumber + 1 :
9733                 gameNumber == 1) {
9734                 gn = 1;
9735             } else {
9736                 DisplayError(_("Can't seek on game file"), 0);
9737                 return FALSE;
9738             }
9739         }
9740     }
9741     lastLoadGameFP = f;
9742     lastLoadGameNumber = gameNumber;
9743     strcpy(lastLoadGameTitle, title);
9744     lastLoadGameUseList = useList;
9745
9746     yynewfile(f);
9747
9748     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9749       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9750                 lg->gameInfo.black);
9751             DisplayTitle(buf);
9752     } else if (*title != NULLCHAR) {
9753         if (gameNumber > 1) {
9754             sprintf(buf, "%s %d", title, gameNumber);
9755             DisplayTitle(buf);
9756         } else {
9757             DisplayTitle(title);
9758         }
9759     }
9760
9761     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9762         gameMode = PlayFromGameFile;
9763         ModeHighlight();
9764     }
9765
9766     currentMove = forwardMostMove = backwardMostMove = 0;
9767     CopyBoard(boards[0], initialPosition);
9768     StopClocks();
9769
9770     /*
9771      * Skip the first gn-1 games in the file.
9772      * Also skip over anything that precedes an identifiable 
9773      * start of game marker, to avoid being confused by 
9774      * garbage at the start of the file.  Currently 
9775      * recognized start of game markers are the move number "1",
9776      * the pattern "gnuchess .* game", the pattern
9777      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9778      * A game that starts with one of the latter two patterns
9779      * will also have a move number 1, possibly
9780      * following a position diagram.
9781      * 5-4-02: Let's try being more lenient and allowing a game to
9782      * start with an unnumbered move.  Does that break anything?
9783      */
9784     cm = lastLoadGameStart = (ChessMove) 0;
9785     while (gn > 0) {
9786         yyboardindex = forwardMostMove;
9787         cm = (ChessMove) yylex();
9788         switch (cm) {
9789           case (ChessMove) 0:
9790             if (cmailMsgLoaded) {
9791                 nCmailGames = CMAIL_MAX_GAMES - gn;
9792             } else {
9793                 Reset(TRUE, TRUE);
9794                 DisplayError(_("Game not found in file"), 0);
9795             }
9796             return FALSE;
9797
9798           case GNUChessGame:
9799           case XBoardGame:
9800             gn--;
9801             lastLoadGameStart = cm;
9802             break;
9803             
9804           case MoveNumberOne:
9805             switch (lastLoadGameStart) {
9806               case GNUChessGame:
9807               case XBoardGame:
9808               case PGNTag:
9809                 break;
9810               case MoveNumberOne:
9811               case (ChessMove) 0:
9812                 gn--;           /* count this game */
9813                 lastLoadGameStart = cm;
9814                 break;
9815               default:
9816                 /* impossible */
9817                 break;
9818             }
9819             break;
9820
9821           case PGNTag:
9822             switch (lastLoadGameStart) {
9823               case GNUChessGame:
9824               case PGNTag:
9825               case MoveNumberOne:
9826               case (ChessMove) 0:
9827                 gn--;           /* count this game */
9828                 lastLoadGameStart = cm;
9829                 break;
9830               case XBoardGame:
9831                 lastLoadGameStart = cm; /* game counted already */
9832                 break;
9833               default:
9834                 /* impossible */
9835                 break;
9836             }
9837             if (gn > 0) {
9838                 do {
9839                     yyboardindex = forwardMostMove;
9840                     cm = (ChessMove) yylex();
9841                 } while (cm == PGNTag || cm == Comment);
9842             }
9843             break;
9844
9845           case WhiteWins:
9846           case BlackWins:
9847           case GameIsDrawn:
9848             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9849                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9850                     != CMAIL_OLD_RESULT) {
9851                     nCmailResults ++ ;
9852                     cmailResult[  CMAIL_MAX_GAMES
9853                                 - gn - 1] = CMAIL_OLD_RESULT;
9854                 }
9855             }
9856             break;
9857
9858           case NormalMove:
9859             /* Only a NormalMove can be at the start of a game
9860              * without a position diagram. */
9861             if (lastLoadGameStart == (ChessMove) 0) {
9862               gn--;
9863               lastLoadGameStart = MoveNumberOne;
9864             }
9865             break;
9866
9867           default:
9868             break;
9869         }
9870     }
9871     
9872     if (appData.debugMode)
9873       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9874
9875     if (cm == XBoardGame) {
9876         /* Skip any header junk before position diagram and/or move 1 */
9877         for (;;) {
9878             yyboardindex = forwardMostMove;
9879             cm = (ChessMove) yylex();
9880
9881             if (cm == (ChessMove) 0 ||
9882                 cm == GNUChessGame || cm == XBoardGame) {
9883                 /* Empty game; pretend end-of-file and handle later */
9884                 cm = (ChessMove) 0;
9885                 break;
9886             }
9887
9888             if (cm == MoveNumberOne || cm == PositionDiagram ||
9889                 cm == PGNTag || cm == Comment)
9890               break;
9891         }
9892     } else if (cm == GNUChessGame) {
9893         if (gameInfo.event != NULL) {
9894             free(gameInfo.event);
9895         }
9896         gameInfo.event = StrSave(yy_text);
9897     }   
9898
9899     startedFromSetupPosition = FALSE;
9900     while (cm == PGNTag) {
9901         if (appData.debugMode) 
9902           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9903         err = ParsePGNTag(yy_text, &gameInfo);
9904         if (!err) numPGNTags++;
9905
9906         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9907         if(gameInfo.variant != oldVariant) {
9908             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9909             InitPosition(TRUE);
9910             oldVariant = gameInfo.variant;
9911             if (appData.debugMode) 
9912               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9913         }
9914
9915
9916         if (gameInfo.fen != NULL) {
9917           Board initial_position;
9918           startedFromSetupPosition = TRUE;
9919           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9920             Reset(TRUE, TRUE);
9921             DisplayError(_("Bad FEN position in file"), 0);
9922             return FALSE;
9923           }
9924           CopyBoard(boards[0], initial_position);
9925           if (blackPlaysFirst) {
9926             currentMove = forwardMostMove = backwardMostMove = 1;
9927             CopyBoard(boards[1], initial_position);
9928             strcpy(moveList[0], "");
9929             strcpy(parseList[0], "");
9930             timeRemaining[0][1] = whiteTimeRemaining;
9931             timeRemaining[1][1] = blackTimeRemaining;
9932             if (commentList[0] != NULL) {
9933               commentList[1] = commentList[0];
9934               commentList[0] = NULL;
9935             }
9936           } else {
9937             currentMove = forwardMostMove = backwardMostMove = 0;
9938           }
9939           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9940           {   int i;
9941               initialRulePlies = FENrulePlies;
9942               for( i=0; i< nrCastlingRights; i++ )
9943                   initialRights[i] = initial_position[CASTLING][i];
9944           }
9945           yyboardindex = forwardMostMove;
9946           free(gameInfo.fen);
9947           gameInfo.fen = NULL;
9948         }
9949
9950         yyboardindex = forwardMostMove;
9951         cm = (ChessMove) yylex();
9952
9953         /* Handle comments interspersed among the tags */
9954         while (cm == Comment) {
9955             char *p;
9956             if (appData.debugMode) 
9957               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9958             p = yy_text;
9959             AppendComment(currentMove, p, FALSE);
9960             yyboardindex = forwardMostMove;
9961             cm = (ChessMove) yylex();
9962         }
9963     }
9964
9965     /* don't rely on existence of Event tag since if game was
9966      * pasted from clipboard the Event tag may not exist
9967      */
9968     if (numPGNTags > 0){
9969         char *tags;
9970         if (gameInfo.variant == VariantNormal) {
9971           gameInfo.variant = StringToVariant(gameInfo.event);
9972         }
9973         if (!matchMode) {
9974           if( appData.autoDisplayTags ) {
9975             tags = PGNTags(&gameInfo);
9976             TagsPopUp(tags, CmailMsg());
9977             free(tags);
9978           }
9979         }
9980     } else {
9981         /* Make something up, but don't display it now */
9982         SetGameInfo();
9983         TagsPopDown();
9984     }
9985
9986     if (cm == PositionDiagram) {
9987         int i, j;
9988         char *p;
9989         Board initial_position;
9990
9991         if (appData.debugMode)
9992           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9993
9994         if (!startedFromSetupPosition) {
9995             p = yy_text;
9996             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9997               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9998                 switch (*p) {
9999                   case '[':
10000                   case '-':
10001                   case ' ':
10002                   case '\t':
10003                   case '\n':
10004                   case '\r':
10005                     break;
10006                   default:
10007                     initial_position[i][j++] = CharToPiece(*p);
10008                     break;
10009                 }
10010             while (*p == ' ' || *p == '\t' ||
10011                    *p == '\n' || *p == '\r') p++;
10012         
10013             if (strncmp(p, "black", strlen("black"))==0)
10014               blackPlaysFirst = TRUE;
10015             else
10016               blackPlaysFirst = FALSE;
10017             startedFromSetupPosition = TRUE;
10018         
10019             CopyBoard(boards[0], initial_position);
10020             if (blackPlaysFirst) {
10021                 currentMove = forwardMostMove = backwardMostMove = 1;
10022                 CopyBoard(boards[1], initial_position);
10023                 strcpy(moveList[0], "");
10024                 strcpy(parseList[0], "");
10025                 timeRemaining[0][1] = whiteTimeRemaining;
10026                 timeRemaining[1][1] = blackTimeRemaining;
10027                 if (commentList[0] != NULL) {
10028                     commentList[1] = commentList[0];
10029                     commentList[0] = NULL;
10030                 }
10031             } else {
10032                 currentMove = forwardMostMove = backwardMostMove = 0;
10033             }
10034         }
10035         yyboardindex = forwardMostMove;
10036         cm = (ChessMove) yylex();
10037     }
10038
10039     if (first.pr == NoProc) {
10040         StartChessProgram(&first);
10041     }
10042     InitChessProgram(&first, FALSE);
10043     SendToProgram("force\n", &first);
10044     if (startedFromSetupPosition) {
10045         SendBoard(&first, forwardMostMove);
10046     if (appData.debugMode) {
10047         fprintf(debugFP, "Load Game\n");
10048     }
10049         DisplayBothClocks();
10050     }      
10051
10052     /* [HGM] server: flag to write setup moves in broadcast file as one */
10053     loadFlag = appData.suppressLoadMoves;
10054
10055     while (cm == Comment) {
10056         char *p;
10057         if (appData.debugMode) 
10058           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10059         p = yy_text;
10060         AppendComment(currentMove, p, FALSE);
10061         yyboardindex = forwardMostMove;
10062         cm = (ChessMove) yylex();
10063     }
10064
10065     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10066         cm == WhiteWins || cm == BlackWins ||
10067         cm == GameIsDrawn || cm == GameUnfinished) {
10068         DisplayMessage("", _("No moves in game"));
10069         if (cmailMsgLoaded) {
10070             if (appData.debugMode)
10071               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10072             ClearHighlights();
10073             flipView = FALSE;
10074         }
10075         DrawPosition(FALSE, boards[currentMove]);
10076         DisplayBothClocks();
10077         gameMode = EditGame;
10078         ModeHighlight();
10079         gameFileFP = NULL;
10080         cmailOldMove = 0;
10081         return TRUE;
10082     }
10083
10084     // [HGM] PV info: routine tests if comment empty
10085     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10086         DisplayComment(currentMove - 1, commentList[currentMove]);
10087     }
10088     if (!matchMode && appData.timeDelay != 0) 
10089       DrawPosition(FALSE, boards[currentMove]);
10090
10091     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10092       programStats.ok_to_send = 1;
10093     }
10094
10095     /* if the first token after the PGN tags is a move
10096      * and not move number 1, retrieve it from the parser 
10097      */
10098     if (cm != MoveNumberOne)
10099         LoadGameOneMove(cm);
10100
10101     /* load the remaining moves from the file */
10102     while (LoadGameOneMove((ChessMove)0)) {
10103       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10104       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10105     }
10106
10107     /* rewind to the start of the game */
10108     currentMove = backwardMostMove;
10109
10110     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10111
10112     if (oldGameMode == AnalyzeFile ||
10113         oldGameMode == AnalyzeMode) {
10114       AnalyzeFileEvent();
10115     }
10116
10117     if (matchMode || appData.timeDelay == 0) {
10118       ToEndEvent();
10119       gameMode = EditGame;
10120       ModeHighlight();
10121     } else if (appData.timeDelay > 0) {
10122       AutoPlayGameLoop();
10123     }
10124
10125     if (appData.debugMode) 
10126         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10127
10128     loadFlag = 0; /* [HGM] true game starts */
10129     return TRUE;
10130 }
10131
10132 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10133 int
10134 ReloadPosition(offset)
10135      int offset;
10136 {
10137     int positionNumber = lastLoadPositionNumber + offset;
10138     if (lastLoadPositionFP == NULL) {
10139         DisplayError(_("No position has been loaded yet"), 0);
10140         return FALSE;
10141     }
10142     if (positionNumber <= 0) {
10143         DisplayError(_("Can't back up any further"), 0);
10144         return FALSE;
10145     }
10146     return LoadPosition(lastLoadPositionFP, positionNumber,
10147                         lastLoadPositionTitle);
10148 }
10149
10150 /* Load the nth position from the given file */
10151 int
10152 LoadPositionFromFile(filename, n, title)
10153      char *filename;
10154      int n;
10155      char *title;
10156 {
10157     FILE *f;
10158     char buf[MSG_SIZ];
10159
10160     if (strcmp(filename, "-") == 0) {
10161         return LoadPosition(stdin, n, "stdin");
10162     } else {
10163         f = fopen(filename, "rb");
10164         if (f == NULL) {
10165             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10166             DisplayError(buf, errno);
10167             return FALSE;
10168         } else {
10169             return LoadPosition(f, n, title);
10170         }
10171     }
10172 }
10173
10174 /* Load the nth position from the given open file, and close it */
10175 int
10176 LoadPosition(f, positionNumber, title)
10177      FILE *f;
10178      int positionNumber;
10179      char *title;
10180 {
10181     char *p, line[MSG_SIZ];
10182     Board initial_position;
10183     int i, j, fenMode, pn;
10184     
10185     if (gameMode == Training )
10186         SetTrainingModeOff();
10187
10188     if (gameMode != BeginningOfGame) {
10189         Reset(FALSE, TRUE);
10190     }
10191     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10192         fclose(lastLoadPositionFP);
10193     }
10194     if (positionNumber == 0) positionNumber = 1;
10195     lastLoadPositionFP = f;
10196     lastLoadPositionNumber = positionNumber;
10197     strcpy(lastLoadPositionTitle, title);
10198     if (first.pr == NoProc) {
10199       StartChessProgram(&first);
10200       InitChessProgram(&first, FALSE);
10201     }    
10202     pn = positionNumber;
10203     if (positionNumber < 0) {
10204         /* Negative position number means to seek to that byte offset */
10205         if (fseek(f, -positionNumber, 0) == -1) {
10206             DisplayError(_("Can't seek on position file"), 0);
10207             return FALSE;
10208         };
10209         pn = 1;
10210     } else {
10211         if (fseek(f, 0, 0) == -1) {
10212             if (f == lastLoadPositionFP ?
10213                 positionNumber == lastLoadPositionNumber + 1 :
10214                 positionNumber == 1) {
10215                 pn = 1;
10216             } else {
10217                 DisplayError(_("Can't seek on position file"), 0);
10218                 return FALSE;
10219             }
10220         }
10221     }
10222     /* See if this file is FEN or old-style xboard */
10223     if (fgets(line, MSG_SIZ, f) == NULL) {
10224         DisplayError(_("Position not found in file"), 0);
10225         return FALSE;
10226     }
10227     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10228     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10229
10230     if (pn >= 2) {
10231         if (fenMode || line[0] == '#') pn--;
10232         while (pn > 0) {
10233             /* skip positions before number pn */
10234             if (fgets(line, MSG_SIZ, f) == NULL) {
10235                 Reset(TRUE, TRUE);
10236                 DisplayError(_("Position not found in file"), 0);
10237                 return FALSE;
10238             }
10239             if (fenMode || line[0] == '#') pn--;
10240         }
10241     }
10242
10243     if (fenMode) {
10244         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10245             DisplayError(_("Bad FEN position in file"), 0);
10246             return FALSE;
10247         }
10248     } else {
10249         (void) fgets(line, MSG_SIZ, f);
10250         (void) fgets(line, MSG_SIZ, f);
10251     
10252         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10253             (void) fgets(line, MSG_SIZ, f);
10254             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10255                 if (*p == ' ')
10256                   continue;
10257                 initial_position[i][j++] = CharToPiece(*p);
10258             }
10259         }
10260     
10261         blackPlaysFirst = FALSE;
10262         if (!feof(f)) {
10263             (void) fgets(line, MSG_SIZ, f);
10264             if (strncmp(line, "black", strlen("black"))==0)
10265               blackPlaysFirst = TRUE;
10266         }
10267     }
10268     startedFromSetupPosition = TRUE;
10269     
10270     SendToProgram("force\n", &first);
10271     CopyBoard(boards[0], initial_position);
10272     if (blackPlaysFirst) {
10273         currentMove = forwardMostMove = backwardMostMove = 1;
10274         strcpy(moveList[0], "");
10275         strcpy(parseList[0], "");
10276         CopyBoard(boards[1], initial_position);
10277         DisplayMessage("", _("Black to play"));
10278     } else {
10279         currentMove = forwardMostMove = backwardMostMove = 0;
10280         DisplayMessage("", _("White to play"));
10281     }
10282     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10283     SendBoard(&first, forwardMostMove);
10284     if (appData.debugMode) {
10285 int i, j;
10286   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10287   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10288         fprintf(debugFP, "Load Position\n");
10289     }
10290
10291     if (positionNumber > 1) {
10292         sprintf(line, "%s %d", title, positionNumber);
10293         DisplayTitle(line);
10294     } else {
10295         DisplayTitle(title);
10296     }
10297     gameMode = EditGame;
10298     ModeHighlight();
10299     ResetClocks();
10300     timeRemaining[0][1] = whiteTimeRemaining;
10301     timeRemaining[1][1] = blackTimeRemaining;
10302     DrawPosition(FALSE, boards[currentMove]);
10303    
10304     return TRUE;
10305 }
10306
10307
10308 void
10309 CopyPlayerNameIntoFileName(dest, src)
10310      char **dest, *src;
10311 {
10312     while (*src != NULLCHAR && *src != ',') {
10313         if (*src == ' ') {
10314             *(*dest)++ = '_';
10315             src++;
10316         } else {
10317             *(*dest)++ = *src++;
10318         }
10319     }
10320 }
10321
10322 char *DefaultFileName(ext)
10323      char *ext;
10324 {
10325     static char def[MSG_SIZ];
10326     char *p;
10327
10328     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10329         p = def;
10330         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10331         *p++ = '-';
10332         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10333         *p++ = '.';
10334         strcpy(p, ext);
10335     } else {
10336         def[0] = NULLCHAR;
10337     }
10338     return def;
10339 }
10340
10341 /* Save the current game to the given file */
10342 int
10343 SaveGameToFile(filename, append)
10344      char *filename;
10345      int append;
10346 {
10347     FILE *f;
10348     char buf[MSG_SIZ];
10349
10350     if (strcmp(filename, "-") == 0) {
10351         return SaveGame(stdout, 0, NULL);
10352     } else {
10353         f = fopen(filename, append ? "a" : "w");
10354         if (f == NULL) {
10355             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10356             DisplayError(buf, errno);
10357             return FALSE;
10358         } else {
10359             return SaveGame(f, 0, NULL);
10360         }
10361     }
10362 }
10363
10364 char *
10365 SavePart(str)
10366      char *str;
10367 {
10368     static char buf[MSG_SIZ];
10369     char *p;
10370     
10371     p = strchr(str, ' ');
10372     if (p == NULL) return str;
10373     strncpy(buf, str, p - str);
10374     buf[p - str] = NULLCHAR;
10375     return buf;
10376 }
10377
10378 #define PGN_MAX_LINE 75
10379
10380 #define PGN_SIDE_WHITE  0
10381 #define PGN_SIDE_BLACK  1
10382
10383 /* [AS] */
10384 static int FindFirstMoveOutOfBook( int side )
10385 {
10386     int result = -1;
10387
10388     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10389         int index = backwardMostMove;
10390         int has_book_hit = 0;
10391
10392         if( (index % 2) != side ) {
10393             index++;
10394         }
10395
10396         while( index < forwardMostMove ) {
10397             /* Check to see if engine is in book */
10398             int depth = pvInfoList[index].depth;
10399             int score = pvInfoList[index].score;
10400             int in_book = 0;
10401
10402             if( depth <= 2 ) {
10403                 in_book = 1;
10404             }
10405             else if( score == 0 && depth == 63 ) {
10406                 in_book = 1; /* Zappa */
10407             }
10408             else if( score == 2 && depth == 99 ) {
10409                 in_book = 1; /* Abrok */
10410             }
10411
10412             has_book_hit += in_book;
10413
10414             if( ! in_book ) {
10415                 result = index;
10416
10417                 break;
10418             }
10419
10420             index += 2;
10421         }
10422     }
10423
10424     return result;
10425 }
10426
10427 /* [AS] */
10428 void GetOutOfBookInfo( char * buf )
10429 {
10430     int oob[2];
10431     int i;
10432     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10433
10434     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10435     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10436
10437     *buf = '\0';
10438
10439     if( oob[0] >= 0 || oob[1] >= 0 ) {
10440         for( i=0; i<2; i++ ) {
10441             int idx = oob[i];
10442
10443             if( idx >= 0 ) {
10444                 if( i > 0 && oob[0] >= 0 ) {
10445                     strcat( buf, "   " );
10446                 }
10447
10448                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10449                 sprintf( buf+strlen(buf), "%s%.2f", 
10450                     pvInfoList[idx].score >= 0 ? "+" : "",
10451                     pvInfoList[idx].score / 100.0 );
10452             }
10453         }
10454     }
10455 }
10456
10457 /* Save game in PGN style and close the file */
10458 int
10459 SaveGamePGN(f)
10460      FILE *f;
10461 {
10462     int i, offset, linelen, newblock;
10463     time_t tm;
10464 //    char *movetext;
10465     char numtext[32];
10466     int movelen, numlen, blank;
10467     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10468
10469     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10470     
10471     tm = time((time_t *) NULL);
10472     
10473     PrintPGNTags(f, &gameInfo);
10474     
10475     if (backwardMostMove > 0 || startedFromSetupPosition) {
10476         char *fen = PositionToFEN(backwardMostMove, NULL);
10477         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10478         fprintf(f, "\n{--------------\n");
10479         PrintPosition(f, backwardMostMove);
10480         fprintf(f, "--------------}\n");
10481         free(fen);
10482     }
10483     else {
10484         /* [AS] Out of book annotation */
10485         if( appData.saveOutOfBookInfo ) {
10486             char buf[64];
10487
10488             GetOutOfBookInfo( buf );
10489
10490             if( buf[0] != '\0' ) {
10491                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10492             }
10493         }
10494
10495         fprintf(f, "\n");
10496     }
10497
10498     i = backwardMostMove;
10499     linelen = 0;
10500     newblock = TRUE;
10501
10502     while (i < forwardMostMove) {
10503         /* Print comments preceding this move */
10504         if (commentList[i] != NULL) {
10505             if (linelen > 0) fprintf(f, "\n");
10506             fprintf(f, "%s", commentList[i]);
10507             linelen = 0;
10508             newblock = TRUE;
10509         }
10510
10511         /* Format move number */
10512         if ((i % 2) == 0) {
10513             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10514         } else {
10515             if (newblock) {
10516                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10517             } else {
10518                 numtext[0] = NULLCHAR;
10519             }
10520         }
10521         numlen = strlen(numtext);
10522         newblock = FALSE;
10523
10524         /* Print move number */
10525         blank = linelen > 0 && numlen > 0;
10526         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10527             fprintf(f, "\n");
10528             linelen = 0;
10529             blank = 0;
10530         }
10531         if (blank) {
10532             fprintf(f, " ");
10533             linelen++;
10534         }
10535         fprintf(f, "%s", numtext);
10536         linelen += numlen;
10537
10538         /* Get move */
10539         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10540         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10541
10542         /* Print move */
10543         blank = linelen > 0 && movelen > 0;
10544         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10545             fprintf(f, "\n");
10546             linelen = 0;
10547             blank = 0;
10548         }
10549         if (blank) {
10550             fprintf(f, " ");
10551             linelen++;
10552         }
10553         fprintf(f, "%s", move_buffer);
10554         linelen += movelen;
10555
10556         /* [AS] Add PV info if present */
10557         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10558             /* [HGM] add time */
10559             char buf[MSG_SIZ]; int seconds;
10560
10561             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10562
10563             if( seconds <= 0) buf[0] = 0; else
10564             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10565                 seconds = (seconds + 4)/10; // round to full seconds
10566                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10567                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10568             }
10569
10570             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10571                 pvInfoList[i].score >= 0 ? "+" : "",
10572                 pvInfoList[i].score / 100.0,
10573                 pvInfoList[i].depth,
10574                 buf );
10575
10576             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10577
10578             /* Print score/depth */
10579             blank = linelen > 0 && movelen > 0;
10580             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10581                 fprintf(f, "\n");
10582                 linelen = 0;
10583                 blank = 0;
10584             }
10585             if (blank) {
10586                 fprintf(f, " ");
10587                 linelen++;
10588             }
10589             fprintf(f, "%s", move_buffer);
10590             linelen += movelen;
10591         }
10592
10593         i++;
10594     }
10595     
10596     /* Start a new line */
10597     if (linelen > 0) fprintf(f, "\n");
10598
10599     /* Print comments after last move */
10600     if (commentList[i] != NULL) {
10601         fprintf(f, "%s\n", commentList[i]);
10602     }
10603
10604     /* Print result */
10605     if (gameInfo.resultDetails != NULL &&
10606         gameInfo.resultDetails[0] != NULLCHAR) {
10607         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10608                 PGNResult(gameInfo.result));
10609     } else {
10610         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10611     }
10612
10613     fclose(f);
10614     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10615     return TRUE;
10616 }
10617
10618 /* Save game in old style and close the file */
10619 int
10620 SaveGameOldStyle(f)
10621      FILE *f;
10622 {
10623     int i, offset;
10624     time_t tm;
10625     
10626     tm = time((time_t *) NULL);
10627     
10628     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10629     PrintOpponents(f);
10630     
10631     if (backwardMostMove > 0 || startedFromSetupPosition) {
10632         fprintf(f, "\n[--------------\n");
10633         PrintPosition(f, backwardMostMove);
10634         fprintf(f, "--------------]\n");
10635     } else {
10636         fprintf(f, "\n");
10637     }
10638
10639     i = backwardMostMove;
10640     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10641
10642     while (i < forwardMostMove) {
10643         if (commentList[i] != NULL) {
10644             fprintf(f, "[%s]\n", commentList[i]);
10645         }
10646
10647         if ((i % 2) == 1) {
10648             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10649             i++;
10650         } else {
10651             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10652             i++;
10653             if (commentList[i] != NULL) {
10654                 fprintf(f, "\n");
10655                 continue;
10656             }
10657             if (i >= forwardMostMove) {
10658                 fprintf(f, "\n");
10659                 break;
10660             }
10661             fprintf(f, "%s\n", parseList[i]);
10662             i++;
10663         }
10664     }
10665     
10666     if (commentList[i] != NULL) {
10667         fprintf(f, "[%s]\n", commentList[i]);
10668     }
10669
10670     /* This isn't really the old style, but it's close enough */
10671     if (gameInfo.resultDetails != NULL &&
10672         gameInfo.resultDetails[0] != NULLCHAR) {
10673         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10674                 gameInfo.resultDetails);
10675     } else {
10676         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10677     }
10678
10679     fclose(f);
10680     return TRUE;
10681 }
10682
10683 /* Save the current game to open file f and close the file */
10684 int
10685 SaveGame(f, dummy, dummy2)
10686      FILE *f;
10687      int dummy;
10688      char *dummy2;
10689 {
10690     if (gameMode == EditPosition) EditPositionDone(TRUE);
10691     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10692     if (appData.oldSaveStyle)
10693       return SaveGameOldStyle(f);
10694     else
10695       return SaveGamePGN(f);
10696 }
10697
10698 /* Save the current position to the given file */
10699 int
10700 SavePositionToFile(filename)
10701      char *filename;
10702 {
10703     FILE *f;
10704     char buf[MSG_SIZ];
10705
10706     if (strcmp(filename, "-") == 0) {
10707         return SavePosition(stdout, 0, NULL);
10708     } else {
10709         f = fopen(filename, "a");
10710         if (f == NULL) {
10711             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10712             DisplayError(buf, errno);
10713             return FALSE;
10714         } else {
10715             SavePosition(f, 0, NULL);
10716             return TRUE;
10717         }
10718     }
10719 }
10720
10721 /* Save the current position to the given open file and close the file */
10722 int
10723 SavePosition(f, dummy, dummy2)
10724      FILE *f;
10725      int dummy;
10726      char *dummy2;
10727 {
10728     time_t tm;
10729     char *fen;
10730     
10731     if (gameMode == EditPosition) EditPositionDone(TRUE);
10732     if (appData.oldSaveStyle) {
10733         tm = time((time_t *) NULL);
10734     
10735         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10736         PrintOpponents(f);
10737         fprintf(f, "[--------------\n");
10738         PrintPosition(f, currentMove);
10739         fprintf(f, "--------------]\n");
10740     } else {
10741         fen = PositionToFEN(currentMove, NULL);
10742         fprintf(f, "%s\n", fen);
10743         free(fen);
10744     }
10745     fclose(f);
10746     return TRUE;
10747 }
10748
10749 void
10750 ReloadCmailMsgEvent(unregister)
10751      int unregister;
10752 {
10753 #if !WIN32
10754     static char *inFilename = NULL;
10755     static char *outFilename;
10756     int i;
10757     struct stat inbuf, outbuf;
10758     int status;
10759     
10760     /* Any registered moves are unregistered if unregister is set, */
10761     /* i.e. invoked by the signal handler */
10762     if (unregister) {
10763         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10764             cmailMoveRegistered[i] = FALSE;
10765             if (cmailCommentList[i] != NULL) {
10766                 free(cmailCommentList[i]);
10767                 cmailCommentList[i] = NULL;
10768             }
10769         }
10770         nCmailMovesRegistered = 0;
10771     }
10772
10773     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10774         cmailResult[i] = CMAIL_NOT_RESULT;
10775     }
10776     nCmailResults = 0;
10777
10778     if (inFilename == NULL) {
10779         /* Because the filenames are static they only get malloced once  */
10780         /* and they never get freed                                      */
10781         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10782         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10783
10784         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10785         sprintf(outFilename, "%s.out", appData.cmailGameName);
10786     }
10787     
10788     status = stat(outFilename, &outbuf);
10789     if (status < 0) {
10790         cmailMailedMove = FALSE;
10791     } else {
10792         status = stat(inFilename, &inbuf);
10793         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10794     }
10795     
10796     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10797        counts the games, notes how each one terminated, etc.
10798        
10799        It would be nice to remove this kludge and instead gather all
10800        the information while building the game list.  (And to keep it
10801        in the game list nodes instead of having a bunch of fixed-size
10802        parallel arrays.)  Note this will require getting each game's
10803        termination from the PGN tags, as the game list builder does
10804        not process the game moves.  --mann
10805        */
10806     cmailMsgLoaded = TRUE;
10807     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10808     
10809     /* Load first game in the file or popup game menu */
10810     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10811
10812 #endif /* !WIN32 */
10813     return;
10814 }
10815
10816 int
10817 RegisterMove()
10818 {
10819     FILE *f;
10820     char string[MSG_SIZ];
10821
10822     if (   cmailMailedMove
10823         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10824         return TRUE;            /* Allow free viewing  */
10825     }
10826
10827     /* Unregister move to ensure that we don't leave RegisterMove        */
10828     /* with the move registered when the conditions for registering no   */
10829     /* longer hold                                                       */
10830     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10831         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10832         nCmailMovesRegistered --;
10833
10834         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10835           {
10836               free(cmailCommentList[lastLoadGameNumber - 1]);
10837               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10838           }
10839     }
10840
10841     if (cmailOldMove == -1) {
10842         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10843         return FALSE;
10844     }
10845
10846     if (currentMove > cmailOldMove + 1) {
10847         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10848         return FALSE;
10849     }
10850
10851     if (currentMove < cmailOldMove) {
10852         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10853         return FALSE;
10854     }
10855
10856     if (forwardMostMove > currentMove) {
10857         /* Silently truncate extra moves */
10858         TruncateGame();
10859     }
10860
10861     if (   (currentMove == cmailOldMove + 1)
10862         || (   (currentMove == cmailOldMove)
10863             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10864                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10865         if (gameInfo.result != GameUnfinished) {
10866             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10867         }
10868
10869         if (commentList[currentMove] != NULL) {
10870             cmailCommentList[lastLoadGameNumber - 1]
10871               = StrSave(commentList[currentMove]);
10872         }
10873         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10874
10875         if (appData.debugMode)
10876           fprintf(debugFP, "Saving %s for game %d\n",
10877                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10878
10879         sprintf(string,
10880                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10881         
10882         f = fopen(string, "w");
10883         if (appData.oldSaveStyle) {
10884             SaveGameOldStyle(f); /* also closes the file */
10885             
10886             sprintf(string, "%s.pos.out", appData.cmailGameName);
10887             f = fopen(string, "w");
10888             SavePosition(f, 0, NULL); /* also closes the file */
10889         } else {
10890             fprintf(f, "{--------------\n");
10891             PrintPosition(f, currentMove);
10892             fprintf(f, "--------------}\n\n");
10893             
10894             SaveGame(f, 0, NULL); /* also closes the file*/
10895         }
10896         
10897         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10898         nCmailMovesRegistered ++;
10899     } else if (nCmailGames == 1) {
10900         DisplayError(_("You have not made a move yet"), 0);
10901         return FALSE;
10902     }
10903
10904     return TRUE;
10905 }
10906
10907 void
10908 MailMoveEvent()
10909 {
10910 #if !WIN32
10911     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10912     FILE *commandOutput;
10913     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10914     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10915     int nBuffers;
10916     int i;
10917     int archived;
10918     char *arcDir;
10919
10920     if (! cmailMsgLoaded) {
10921         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10922         return;
10923     }
10924
10925     if (nCmailGames == nCmailResults) {
10926         DisplayError(_("No unfinished games"), 0);
10927         return;
10928     }
10929
10930 #if CMAIL_PROHIBIT_REMAIL
10931     if (cmailMailedMove) {
10932         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);
10933         DisplayError(msg, 0);
10934         return;
10935     }
10936 #endif
10937
10938     if (! (cmailMailedMove || RegisterMove())) return;
10939     
10940     if (   cmailMailedMove
10941         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10942         sprintf(string, partCommandString,
10943                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10944         commandOutput = popen(string, "r");
10945
10946         if (commandOutput == NULL) {
10947             DisplayError(_("Failed to invoke cmail"), 0);
10948         } else {
10949             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10950                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10951             }
10952             if (nBuffers > 1) {
10953                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10954                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10955                 nBytes = MSG_SIZ - 1;
10956             } else {
10957                 (void) memcpy(msg, buffer, nBytes);
10958             }
10959             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10960
10961             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10962                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10963
10964                 archived = TRUE;
10965                 for (i = 0; i < nCmailGames; i ++) {
10966                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10967                         archived = FALSE;
10968                     }
10969                 }
10970                 if (   archived
10971                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10972                         != NULL)) {
10973                     sprintf(buffer, "%s/%s.%s.archive",
10974                             arcDir,
10975                             appData.cmailGameName,
10976                             gameInfo.date);
10977                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10978                     cmailMsgLoaded = FALSE;
10979                 }
10980             }
10981
10982             DisplayInformation(msg);
10983             pclose(commandOutput);
10984         }
10985     } else {
10986         if ((*cmailMsg) != '\0') {
10987             DisplayInformation(cmailMsg);
10988         }
10989     }
10990
10991     return;
10992 #endif /* !WIN32 */
10993 }
10994
10995 char *
10996 CmailMsg()
10997 {
10998 #if WIN32
10999     return NULL;
11000 #else
11001     int  prependComma = 0;
11002     char number[5];
11003     char string[MSG_SIZ];       /* Space for game-list */
11004     int  i;
11005     
11006     if (!cmailMsgLoaded) return "";
11007
11008     if (cmailMailedMove) {
11009         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11010     } else {
11011         /* Create a list of games left */
11012         sprintf(string, "[");
11013         for (i = 0; i < nCmailGames; i ++) {
11014             if (! (   cmailMoveRegistered[i]
11015                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11016                 if (prependComma) {
11017                     sprintf(number, ",%d", i + 1);
11018                 } else {
11019                     sprintf(number, "%d", i + 1);
11020                     prependComma = 1;
11021                 }
11022                 
11023                 strcat(string, number);
11024             }
11025         }
11026         strcat(string, "]");
11027
11028         if (nCmailMovesRegistered + nCmailResults == 0) {
11029             switch (nCmailGames) {
11030               case 1:
11031                 sprintf(cmailMsg,
11032                         _("Still need to make move for game\n"));
11033                 break;
11034                 
11035               case 2:
11036                 sprintf(cmailMsg,
11037                         _("Still need to make moves for both games\n"));
11038                 break;
11039                 
11040               default:
11041                 sprintf(cmailMsg,
11042                         _("Still need to make moves for all %d games\n"),
11043                         nCmailGames);
11044                 break;
11045             }
11046         } else {
11047             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11048               case 1:
11049                 sprintf(cmailMsg,
11050                         _("Still need to make a move for game %s\n"),
11051                         string);
11052                 break;
11053                 
11054               case 0:
11055                 if (nCmailResults == nCmailGames) {
11056                     sprintf(cmailMsg, _("No unfinished games\n"));
11057                 } else {
11058                     sprintf(cmailMsg, _("Ready to send mail\n"));
11059                 }
11060                 break;
11061                 
11062               default:
11063                 sprintf(cmailMsg,
11064                         _("Still need to make moves for games %s\n"),
11065                         string);
11066             }
11067         }
11068     }
11069     return cmailMsg;
11070 #endif /* WIN32 */
11071 }
11072
11073 void
11074 ResetGameEvent()
11075 {
11076     if (gameMode == Training)
11077       SetTrainingModeOff();
11078
11079     Reset(TRUE, TRUE);
11080     cmailMsgLoaded = FALSE;
11081     if (appData.icsActive) {
11082       SendToICS(ics_prefix);
11083       SendToICS("refresh\n");
11084     }
11085 }
11086
11087 void
11088 ExitEvent(status)
11089      int status;
11090 {
11091     exiting++;
11092     if (exiting > 2) {
11093       /* Give up on clean exit */
11094       exit(status);
11095     }
11096     if (exiting > 1) {
11097       /* Keep trying for clean exit */
11098       return;
11099     }
11100
11101     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11102
11103     if (telnetISR != NULL) {
11104       RemoveInputSource(telnetISR);
11105     }
11106     if (icsPR != NoProc) {
11107       DestroyChildProcess(icsPR, TRUE);
11108     }
11109
11110     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11111     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11112
11113     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11114     /* make sure this other one finishes before killing it!                  */
11115     if(endingGame) { int count = 0;
11116         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11117         while(endingGame && count++ < 10) DoSleep(1);
11118         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11119     }
11120
11121     /* Kill off chess programs */
11122     if (first.pr != NoProc) {
11123         ExitAnalyzeMode();
11124         
11125         DoSleep( appData.delayBeforeQuit );
11126         SendToProgram("quit\n", &first);
11127         DoSleep( appData.delayAfterQuit );
11128         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11129     }
11130     if (second.pr != NoProc) {
11131         DoSleep( appData.delayBeforeQuit );
11132         SendToProgram("quit\n", &second);
11133         DoSleep( appData.delayAfterQuit );
11134         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11135     }
11136     if (first.isr != NULL) {
11137         RemoveInputSource(first.isr);
11138     }
11139     if (second.isr != NULL) {
11140         RemoveInputSource(second.isr);
11141     }
11142
11143     ShutDownFrontEnd();
11144     exit(status);
11145 }
11146
11147 void
11148 PauseEvent()
11149 {
11150     if (appData.debugMode)
11151         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11152     if (pausing) {
11153         pausing = FALSE;
11154         ModeHighlight();
11155         if (gameMode == MachinePlaysWhite ||
11156             gameMode == MachinePlaysBlack) {
11157             StartClocks();
11158         } else {
11159             DisplayBothClocks();
11160         }
11161         if (gameMode == PlayFromGameFile) {
11162             if (appData.timeDelay >= 0) 
11163                 AutoPlayGameLoop();
11164         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11165             Reset(FALSE, TRUE);
11166             SendToICS(ics_prefix);
11167             SendToICS("refresh\n");
11168         } else if (currentMove < forwardMostMove) {
11169             ForwardInner(forwardMostMove);
11170         }
11171         pauseExamInvalid = FALSE;
11172     } else {
11173         switch (gameMode) {
11174           default:
11175             return;
11176           case IcsExamining:
11177             pauseExamForwardMostMove = forwardMostMove;
11178             pauseExamInvalid = FALSE;
11179             /* fall through */
11180           case IcsObserving:
11181           case IcsPlayingWhite:
11182           case IcsPlayingBlack:
11183             pausing = TRUE;
11184             ModeHighlight();
11185             return;
11186           case PlayFromGameFile:
11187             (void) StopLoadGameTimer();
11188             pausing = TRUE;
11189             ModeHighlight();
11190             break;
11191           case BeginningOfGame:
11192             if (appData.icsActive) return;
11193             /* else fall through */
11194           case MachinePlaysWhite:
11195           case MachinePlaysBlack:
11196           case TwoMachinesPlay:
11197             if (forwardMostMove == 0)
11198               return;           /* don't pause if no one has moved */
11199             if ((gameMode == MachinePlaysWhite &&
11200                  !WhiteOnMove(forwardMostMove)) ||
11201                 (gameMode == MachinePlaysBlack &&
11202                  WhiteOnMove(forwardMostMove))) {
11203                 StopClocks();
11204             }
11205             pausing = TRUE;
11206             ModeHighlight();
11207             break;
11208         }
11209     }
11210 }
11211
11212 void
11213 EditCommentEvent()
11214 {
11215     char title[MSG_SIZ];
11216
11217     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11218         strcpy(title, _("Edit comment"));
11219     } else {
11220         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11221                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11222                 parseList[currentMove - 1]);
11223     }
11224
11225     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11226 }
11227
11228
11229 void
11230 EditTagsEvent()
11231 {
11232     char *tags = PGNTags(&gameInfo);
11233     EditTagsPopUp(tags);
11234     free(tags);
11235 }
11236
11237 void
11238 AnalyzeModeEvent()
11239 {
11240     if (appData.noChessProgram || gameMode == AnalyzeMode)
11241       return;
11242
11243     if (gameMode != AnalyzeFile) {
11244         if (!appData.icsEngineAnalyze) {
11245                EditGameEvent();
11246                if (gameMode != EditGame) return;
11247         }
11248         ResurrectChessProgram();
11249         SendToProgram("analyze\n", &first);
11250         first.analyzing = TRUE;
11251         /*first.maybeThinking = TRUE;*/
11252         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11253         EngineOutputPopUp();
11254     }
11255     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11256     pausing = FALSE;
11257     ModeHighlight();
11258     SetGameInfo();
11259
11260     StartAnalysisClock();
11261     GetTimeMark(&lastNodeCountTime);
11262     lastNodeCount = 0;
11263 }
11264
11265 void
11266 AnalyzeFileEvent()
11267 {
11268     if (appData.noChessProgram || gameMode == AnalyzeFile)
11269       return;
11270
11271     if (gameMode != AnalyzeMode) {
11272         EditGameEvent();
11273         if (gameMode != EditGame) return;
11274         ResurrectChessProgram();
11275         SendToProgram("analyze\n", &first);
11276         first.analyzing = TRUE;
11277         /*first.maybeThinking = TRUE;*/
11278         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11279         EngineOutputPopUp();
11280     }
11281     gameMode = AnalyzeFile;
11282     pausing = FALSE;
11283     ModeHighlight();
11284     SetGameInfo();
11285
11286     StartAnalysisClock();
11287     GetTimeMark(&lastNodeCountTime);
11288     lastNodeCount = 0;
11289 }
11290
11291 void
11292 MachineWhiteEvent()
11293 {
11294     char buf[MSG_SIZ];
11295     char *bookHit = NULL;
11296
11297     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11298       return;
11299
11300
11301     if (gameMode == PlayFromGameFile || 
11302         gameMode == TwoMachinesPlay  || 
11303         gameMode == Training         || 
11304         gameMode == AnalyzeMode      || 
11305         gameMode == EndOfGame)
11306         EditGameEvent();
11307
11308     if (gameMode == EditPosition) 
11309         EditPositionDone(TRUE);
11310
11311     if (!WhiteOnMove(currentMove)) {
11312         DisplayError(_("It is not White's turn"), 0);
11313         return;
11314     }
11315   
11316     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11317       ExitAnalyzeMode();
11318
11319     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11320         gameMode == AnalyzeFile)
11321         TruncateGame();
11322
11323     ResurrectChessProgram();    /* in case it isn't running */
11324     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11325         gameMode = MachinePlaysWhite;
11326         ResetClocks();
11327     } else
11328     gameMode = MachinePlaysWhite;
11329     pausing = FALSE;
11330     ModeHighlight();
11331     SetGameInfo();
11332     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11333     DisplayTitle(buf);
11334     if (first.sendName) {
11335       sprintf(buf, "name %s\n", gameInfo.black);
11336       SendToProgram(buf, &first);
11337     }
11338     if (first.sendTime) {
11339       if (first.useColors) {
11340         SendToProgram("black\n", &first); /*gnu kludge*/
11341       }
11342       SendTimeRemaining(&first, TRUE);
11343     }
11344     if (first.useColors) {
11345       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11346     }
11347     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11348     SetMachineThinkingEnables();
11349     first.maybeThinking = TRUE;
11350     StartClocks();
11351     firstMove = FALSE;
11352
11353     if (appData.autoFlipView && !flipView) {
11354       flipView = !flipView;
11355       DrawPosition(FALSE, NULL);
11356       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11357     }
11358
11359     if(bookHit) { // [HGM] book: simulate book reply
11360         static char bookMove[MSG_SIZ]; // a bit generous?
11361
11362         programStats.nodes = programStats.depth = programStats.time = 
11363         programStats.score = programStats.got_only_move = 0;
11364         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11365
11366         strcpy(bookMove, "move ");
11367         strcat(bookMove, bookHit);
11368         HandleMachineMove(bookMove, &first);
11369     }
11370 }
11371
11372 void
11373 MachineBlackEvent()
11374 {
11375     char buf[MSG_SIZ];
11376    char *bookHit = NULL;
11377
11378     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11379         return;
11380
11381
11382     if (gameMode == PlayFromGameFile || 
11383         gameMode == TwoMachinesPlay  || 
11384         gameMode == Training         || 
11385         gameMode == AnalyzeMode      || 
11386         gameMode == EndOfGame)
11387         EditGameEvent();
11388
11389     if (gameMode == EditPosition) 
11390         EditPositionDone(TRUE);
11391
11392     if (WhiteOnMove(currentMove)) {
11393         DisplayError(_("It is not Black's turn"), 0);
11394         return;
11395     }
11396     
11397     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11398       ExitAnalyzeMode();
11399
11400     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11401         gameMode == AnalyzeFile)
11402         TruncateGame();
11403
11404     ResurrectChessProgram();    /* in case it isn't running */
11405     gameMode = MachinePlaysBlack;
11406     pausing = FALSE;
11407     ModeHighlight();
11408     SetGameInfo();
11409     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11410     DisplayTitle(buf);
11411     if (first.sendName) {
11412       sprintf(buf, "name %s\n", gameInfo.white);
11413       SendToProgram(buf, &first);
11414     }
11415     if (first.sendTime) {
11416       if (first.useColors) {
11417         SendToProgram("white\n", &first); /*gnu kludge*/
11418       }
11419       SendTimeRemaining(&first, FALSE);
11420     }
11421     if (first.useColors) {
11422       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11423     }
11424     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11425     SetMachineThinkingEnables();
11426     first.maybeThinking = TRUE;
11427     StartClocks();
11428
11429     if (appData.autoFlipView && flipView) {
11430       flipView = !flipView;
11431       DrawPosition(FALSE, NULL);
11432       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11433     }
11434     if(bookHit) { // [HGM] book: simulate book reply
11435         static char bookMove[MSG_SIZ]; // a bit generous?
11436
11437         programStats.nodes = programStats.depth = programStats.time = 
11438         programStats.score = programStats.got_only_move = 0;
11439         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11440
11441         strcpy(bookMove, "move ");
11442         strcat(bookMove, bookHit);
11443         HandleMachineMove(bookMove, &first);
11444     }
11445 }
11446
11447
11448 void
11449 DisplayTwoMachinesTitle()
11450 {
11451     char buf[MSG_SIZ];
11452     if (appData.matchGames > 0) {
11453         if (first.twoMachinesColor[0] == 'w') {
11454             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11455                     gameInfo.white, gameInfo.black,
11456                     first.matchWins, second.matchWins,
11457                     matchGame - 1 - (first.matchWins + second.matchWins));
11458         } else {
11459             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11460                     gameInfo.white, gameInfo.black,
11461                     second.matchWins, first.matchWins,
11462                     matchGame - 1 - (first.matchWins + second.matchWins));
11463         }
11464     } else {
11465         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11466     }
11467     DisplayTitle(buf);
11468 }
11469
11470 void
11471 TwoMachinesEvent P((void))
11472 {
11473     int i;
11474     char buf[MSG_SIZ];
11475     ChessProgramState *onmove;
11476     char *bookHit = NULL;
11477     
11478     if (appData.noChessProgram) return;
11479
11480     switch (gameMode) {
11481       case TwoMachinesPlay:
11482         return;
11483       case MachinePlaysWhite:
11484       case MachinePlaysBlack:
11485         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11486             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11487             return;
11488         }
11489         /* fall through */
11490       case BeginningOfGame:
11491       case PlayFromGameFile:
11492       case EndOfGame:
11493         EditGameEvent();
11494         if (gameMode != EditGame) return;
11495         break;
11496       case EditPosition:
11497         EditPositionDone(TRUE);
11498         break;
11499       case AnalyzeMode:
11500       case AnalyzeFile:
11501         ExitAnalyzeMode();
11502         break;
11503       case EditGame:
11504       default:
11505         break;
11506     }
11507
11508 //    forwardMostMove = currentMove;
11509     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11510     ResurrectChessProgram();    /* in case first program isn't running */
11511
11512     if (second.pr == NULL) {
11513         StartChessProgram(&second);
11514         if (second.protocolVersion == 1) {
11515           TwoMachinesEventIfReady();
11516         } else {
11517           /* kludge: allow timeout for initial "feature" command */
11518           FreezeUI();
11519           DisplayMessage("", _("Starting second chess program"));
11520           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11521         }
11522         return;
11523     }
11524     DisplayMessage("", "");
11525     InitChessProgram(&second, FALSE);
11526     SendToProgram("force\n", &second);
11527     if (startedFromSetupPosition) {
11528         SendBoard(&second, backwardMostMove);
11529     if (appData.debugMode) {
11530         fprintf(debugFP, "Two Machines\n");
11531     }
11532     }
11533     for (i = backwardMostMove; i < forwardMostMove; i++) {
11534         SendMoveToProgram(i, &second);
11535     }
11536
11537     gameMode = TwoMachinesPlay;
11538     pausing = FALSE;
11539     ModeHighlight();
11540     SetGameInfo();
11541     DisplayTwoMachinesTitle();
11542     firstMove = TRUE;
11543     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11544         onmove = &first;
11545     } else {
11546         onmove = &second;
11547     }
11548
11549     SendToProgram(first.computerString, &first);
11550     if (first.sendName) {
11551       sprintf(buf, "name %s\n", second.tidy);
11552       SendToProgram(buf, &first);
11553     }
11554     SendToProgram(second.computerString, &second);
11555     if (second.sendName) {
11556       sprintf(buf, "name %s\n", first.tidy);
11557       SendToProgram(buf, &second);
11558     }
11559
11560     ResetClocks();
11561     if (!first.sendTime || !second.sendTime) {
11562         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11563         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11564     }
11565     if (onmove->sendTime) {
11566       if (onmove->useColors) {
11567         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11568       }
11569       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11570     }
11571     if (onmove->useColors) {
11572       SendToProgram(onmove->twoMachinesColor, onmove);
11573     }
11574     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11575 //    SendToProgram("go\n", onmove);
11576     onmove->maybeThinking = TRUE;
11577     SetMachineThinkingEnables();
11578
11579     StartClocks();
11580
11581     if(bookHit) { // [HGM] book: simulate book reply
11582         static char bookMove[MSG_SIZ]; // a bit generous?
11583
11584         programStats.nodes = programStats.depth = programStats.time = 
11585         programStats.score = programStats.got_only_move = 0;
11586         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11587
11588         strcpy(bookMove, "move ");
11589         strcat(bookMove, bookHit);
11590         savedMessage = bookMove; // args for deferred call
11591         savedState = onmove;
11592         ScheduleDelayedEvent(DeferredBookMove, 1);
11593     }
11594 }
11595
11596 void
11597 TrainingEvent()
11598 {
11599     if (gameMode == Training) {
11600       SetTrainingModeOff();
11601       gameMode = PlayFromGameFile;
11602       DisplayMessage("", _("Training mode off"));
11603     } else {
11604       gameMode = Training;
11605       animateTraining = appData.animate;
11606
11607       /* make sure we are not already at the end of the game */
11608       if (currentMove < forwardMostMove) {
11609         SetTrainingModeOn();
11610         DisplayMessage("", _("Training mode on"));
11611       } else {
11612         gameMode = PlayFromGameFile;
11613         DisplayError(_("Already at end of game"), 0);
11614       }
11615     }
11616     ModeHighlight();
11617 }
11618
11619 void
11620 IcsClientEvent()
11621 {
11622     if (!appData.icsActive) return;
11623     switch (gameMode) {
11624       case IcsPlayingWhite:
11625       case IcsPlayingBlack:
11626       case IcsObserving:
11627       case IcsIdle:
11628       case BeginningOfGame:
11629       case IcsExamining:
11630         return;
11631
11632       case EditGame:
11633         break;
11634
11635       case EditPosition:
11636         EditPositionDone(TRUE);
11637         break;
11638
11639       case AnalyzeMode:
11640       case AnalyzeFile:
11641         ExitAnalyzeMode();
11642         break;
11643         
11644       default:
11645         EditGameEvent();
11646         break;
11647     }
11648
11649     gameMode = IcsIdle;
11650     ModeHighlight();
11651     return;
11652 }
11653
11654
11655 void
11656 EditGameEvent()
11657 {
11658     int i;
11659
11660     switch (gameMode) {
11661       case Training:
11662         SetTrainingModeOff();
11663         break;
11664       case MachinePlaysWhite:
11665       case MachinePlaysBlack:
11666       case BeginningOfGame:
11667         SendToProgram("force\n", &first);
11668         SetUserThinkingEnables();
11669         break;
11670       case PlayFromGameFile:
11671         (void) StopLoadGameTimer();
11672         if (gameFileFP != NULL) {
11673             gameFileFP = NULL;
11674         }
11675         break;
11676       case EditPosition:
11677         EditPositionDone(TRUE);
11678         break;
11679       case AnalyzeMode:
11680       case AnalyzeFile:
11681         ExitAnalyzeMode();
11682         SendToProgram("force\n", &first);
11683         break;
11684       case TwoMachinesPlay:
11685         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11686         ResurrectChessProgram();
11687         SetUserThinkingEnables();
11688         break;
11689       case EndOfGame:
11690         ResurrectChessProgram();
11691         break;
11692       case IcsPlayingBlack:
11693       case IcsPlayingWhite:
11694         DisplayError(_("Warning: You are still playing a game"), 0);
11695         break;
11696       case IcsObserving:
11697         DisplayError(_("Warning: You are still observing a game"), 0);
11698         break;
11699       case IcsExamining:
11700         DisplayError(_("Warning: You are still examining a game"), 0);
11701         break;
11702       case IcsIdle:
11703         break;
11704       case EditGame:
11705       default:
11706         return;
11707     }
11708     
11709     pausing = FALSE;
11710     StopClocks();
11711     first.offeredDraw = second.offeredDraw = 0;
11712
11713     if (gameMode == PlayFromGameFile) {
11714         whiteTimeRemaining = timeRemaining[0][currentMove];
11715         blackTimeRemaining = timeRemaining[1][currentMove];
11716         DisplayTitle("");
11717     }
11718
11719     if (gameMode == MachinePlaysWhite ||
11720         gameMode == MachinePlaysBlack ||
11721         gameMode == TwoMachinesPlay ||
11722         gameMode == EndOfGame) {
11723         i = forwardMostMove;
11724         while (i > currentMove) {
11725             SendToProgram("undo\n", &first);
11726             i--;
11727         }
11728         whiteTimeRemaining = timeRemaining[0][currentMove];
11729         blackTimeRemaining = timeRemaining[1][currentMove];
11730         DisplayBothClocks();
11731         if (whiteFlag || blackFlag) {
11732             whiteFlag = blackFlag = 0;
11733         }
11734         DisplayTitle("");
11735     }           
11736     
11737     gameMode = EditGame;
11738     ModeHighlight();
11739     SetGameInfo();
11740 }
11741
11742
11743 void
11744 EditPositionEvent()
11745 {
11746     if (gameMode == EditPosition) {
11747         EditGameEvent();
11748         return;
11749     }
11750     
11751     EditGameEvent();
11752     if (gameMode != EditGame) return;
11753     
11754     gameMode = EditPosition;
11755     ModeHighlight();
11756     SetGameInfo();
11757     if (currentMove > 0)
11758       CopyBoard(boards[0], boards[currentMove]);
11759     
11760     blackPlaysFirst = !WhiteOnMove(currentMove);
11761     ResetClocks();
11762     currentMove = forwardMostMove = backwardMostMove = 0;
11763     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11764     DisplayMove(-1);
11765 }
11766
11767 void
11768 ExitAnalyzeMode()
11769 {
11770     /* [DM] icsEngineAnalyze - possible call from other functions */
11771     if (appData.icsEngineAnalyze) {
11772         appData.icsEngineAnalyze = FALSE;
11773
11774         DisplayMessage("",_("Close ICS engine analyze..."));
11775     }
11776     if (first.analysisSupport && first.analyzing) {
11777       SendToProgram("exit\n", &first);
11778       first.analyzing = FALSE;
11779     }
11780     thinkOutput[0] = NULLCHAR;
11781 }
11782
11783 void
11784 EditPositionDone(Boolean fakeRights)
11785 {
11786     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11787
11788     startedFromSetupPosition = TRUE;
11789     InitChessProgram(&first, FALSE);
11790     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11791       boards[0][EP_STATUS] = EP_NONE;
11792       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11793     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11794         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11795         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11796       } else boards[0][CASTLING][2] = NoRights;
11797     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11798         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11799         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11800       } else boards[0][CASTLING][5] = NoRights;
11801     }
11802     SendToProgram("force\n", &first);
11803     if (blackPlaysFirst) {
11804         strcpy(moveList[0], "");
11805         strcpy(parseList[0], "");
11806         currentMove = forwardMostMove = backwardMostMove = 1;
11807         CopyBoard(boards[1], boards[0]);
11808     } else {
11809         currentMove = forwardMostMove = backwardMostMove = 0;
11810     }
11811     SendBoard(&first, forwardMostMove);
11812     if (appData.debugMode) {
11813         fprintf(debugFP, "EditPosDone\n");
11814     }
11815     DisplayTitle("");
11816     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11817     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11818     gameMode = EditGame;
11819     ModeHighlight();
11820     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11821     ClearHighlights(); /* [AS] */
11822 }
11823
11824 /* Pause for `ms' milliseconds */
11825 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11826 void
11827 TimeDelay(ms)
11828      long ms;
11829 {
11830     TimeMark m1, m2;
11831
11832     GetTimeMark(&m1);
11833     do {
11834         GetTimeMark(&m2);
11835     } while (SubtractTimeMarks(&m2, &m1) < ms);
11836 }
11837
11838 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11839 void
11840 SendMultiLineToICS(buf)
11841      char *buf;
11842 {
11843     char temp[MSG_SIZ+1], *p;
11844     int len;
11845
11846     len = strlen(buf);
11847     if (len > MSG_SIZ)
11848       len = MSG_SIZ;
11849   
11850     strncpy(temp, buf, len);
11851     temp[len] = 0;
11852
11853     p = temp;
11854     while (*p) {
11855         if (*p == '\n' || *p == '\r')
11856           *p = ' ';
11857         ++p;
11858     }
11859
11860     strcat(temp, "\n");
11861     SendToICS(temp);
11862     SendToPlayer(temp, strlen(temp));
11863 }
11864
11865 void
11866 SetWhiteToPlayEvent()
11867 {
11868     if (gameMode == EditPosition) {
11869         blackPlaysFirst = FALSE;
11870         DisplayBothClocks();    /* works because currentMove is 0 */
11871     } else if (gameMode == IcsExamining) {
11872         SendToICS(ics_prefix);
11873         SendToICS("tomove white\n");
11874     }
11875 }
11876
11877 void
11878 SetBlackToPlayEvent()
11879 {
11880     if (gameMode == EditPosition) {
11881         blackPlaysFirst = TRUE;
11882         currentMove = 1;        /* kludge */
11883         DisplayBothClocks();
11884         currentMove = 0;
11885     } else if (gameMode == IcsExamining) {
11886         SendToICS(ics_prefix);
11887         SendToICS("tomove black\n");
11888     }
11889 }
11890
11891 void
11892 EditPositionMenuEvent(selection, x, y)
11893      ChessSquare selection;
11894      int x, y;
11895 {
11896     char buf[MSG_SIZ];
11897     ChessSquare piece = boards[0][y][x];
11898
11899     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11900
11901     switch (selection) {
11902       case ClearBoard:
11903         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11904             SendToICS(ics_prefix);
11905             SendToICS("bsetup clear\n");
11906         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11907             SendToICS(ics_prefix);
11908             SendToICS("clearboard\n");
11909         } else {
11910             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11911                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11912                 for (y = 0; y < BOARD_HEIGHT; y++) {
11913                     if (gameMode == IcsExamining) {
11914                         if (boards[currentMove][y][x] != EmptySquare) {
11915                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11916                                     AAA + x, ONE + y);
11917                             SendToICS(buf);
11918                         }
11919                     } else {
11920                         boards[0][y][x] = p;
11921                     }
11922                 }
11923             }
11924         }
11925         if (gameMode == EditPosition) {
11926             DrawPosition(FALSE, boards[0]);
11927         }
11928         break;
11929
11930       case WhitePlay:
11931         SetWhiteToPlayEvent();
11932         break;
11933
11934       case BlackPlay:
11935         SetBlackToPlayEvent();
11936         break;
11937
11938       case EmptySquare:
11939         if (gameMode == IcsExamining) {
11940             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11941             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11942             SendToICS(buf);
11943         } else {
11944             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11945                 if(x == BOARD_LEFT-2) {
11946                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11947                     boards[0][y][1] = 0;
11948                 } else
11949                 if(x == BOARD_RGHT+1) {
11950                     if(y >= gameInfo.holdingsSize) break;
11951                     boards[0][y][BOARD_WIDTH-2] = 0;
11952                 } else break;
11953             }
11954             boards[0][y][x] = EmptySquare;
11955             DrawPosition(FALSE, boards[0]);
11956         }
11957         break;
11958
11959       case PromotePiece:
11960         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11961            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11962             selection = (ChessSquare) (PROMOTED piece);
11963         } else if(piece == EmptySquare) selection = WhiteSilver;
11964         else selection = (ChessSquare)((int)piece - 1);
11965         goto defaultlabel;
11966
11967       case DemotePiece:
11968         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11969            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11970             selection = (ChessSquare) (DEMOTED piece);
11971         } else if(piece == EmptySquare) selection = BlackSilver;
11972         else selection = (ChessSquare)((int)piece + 1);       
11973         goto defaultlabel;
11974
11975       case WhiteQueen:
11976       case BlackQueen:
11977         if(gameInfo.variant == VariantShatranj ||
11978            gameInfo.variant == VariantXiangqi  ||
11979            gameInfo.variant == VariantCourier  ||
11980            gameInfo.variant == VariantMakruk     )
11981             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11982         goto defaultlabel;
11983
11984       case WhiteKing:
11985       case BlackKing:
11986         if(gameInfo.variant == VariantXiangqi)
11987             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11988         if(gameInfo.variant == VariantKnightmate)
11989             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11990       default:
11991         defaultlabel:
11992         if (gameMode == IcsExamining) {
11993             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11994             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11995                     PieceToChar(selection), AAA + x, ONE + y);
11996             SendToICS(buf);
11997         } else {
11998             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11999                 int n;
12000                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12001                     n = PieceToNumber(selection - BlackPawn);
12002                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12003                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12004                     boards[0][BOARD_HEIGHT-1-n][1]++;
12005                 } else
12006                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12007                     n = PieceToNumber(selection);
12008                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12009                     boards[0][n][BOARD_WIDTH-1] = selection;
12010                     boards[0][n][BOARD_WIDTH-2]++;
12011                 }
12012             } else
12013             boards[0][y][x] = selection;
12014             DrawPosition(TRUE, boards[0]);
12015         }
12016         break;
12017     }
12018 }
12019
12020
12021 void
12022 DropMenuEvent(selection, x, y)
12023      ChessSquare selection;
12024      int x, y;
12025 {
12026     ChessMove moveType;
12027
12028     switch (gameMode) {
12029       case IcsPlayingWhite:
12030       case MachinePlaysBlack:
12031         if (!WhiteOnMove(currentMove)) {
12032             DisplayMoveError(_("It is Black's turn"));
12033             return;
12034         }
12035         moveType = WhiteDrop;
12036         break;
12037       case IcsPlayingBlack:
12038       case MachinePlaysWhite:
12039         if (WhiteOnMove(currentMove)) {
12040             DisplayMoveError(_("It is White's turn"));
12041             return;
12042         }
12043         moveType = BlackDrop;
12044         break;
12045       case EditGame:
12046         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12047         break;
12048       default:
12049         return;
12050     }
12051
12052     if (moveType == BlackDrop && selection < BlackPawn) {
12053       selection = (ChessSquare) ((int) selection
12054                                  + (int) BlackPawn - (int) WhitePawn);
12055     }
12056     if (boards[currentMove][y][x] != EmptySquare) {
12057         DisplayMoveError(_("That square is occupied"));
12058         return;
12059     }
12060
12061     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12062 }
12063
12064 void
12065 AcceptEvent()
12066 {
12067     /* Accept a pending offer of any kind from opponent */
12068     
12069     if (appData.icsActive) {
12070         SendToICS(ics_prefix);
12071         SendToICS("accept\n");
12072     } else if (cmailMsgLoaded) {
12073         if (currentMove == cmailOldMove &&
12074             commentList[cmailOldMove] != NULL &&
12075             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12076                    "Black offers a draw" : "White offers a draw")) {
12077             TruncateGame();
12078             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12079             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12080         } else {
12081             DisplayError(_("There is no pending offer on this move"), 0);
12082             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12083         }
12084     } else {
12085         /* Not used for offers from chess program */
12086     }
12087 }
12088
12089 void
12090 DeclineEvent()
12091 {
12092     /* Decline a pending offer of any kind from opponent */
12093     
12094     if (appData.icsActive) {
12095         SendToICS(ics_prefix);
12096         SendToICS("decline\n");
12097     } else if (cmailMsgLoaded) {
12098         if (currentMove == cmailOldMove &&
12099             commentList[cmailOldMove] != NULL &&
12100             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12101                    "Black offers a draw" : "White offers a draw")) {
12102 #ifdef NOTDEF
12103             AppendComment(cmailOldMove, "Draw declined", TRUE);
12104             DisplayComment(cmailOldMove - 1, "Draw declined");
12105 #endif /*NOTDEF*/
12106         } else {
12107             DisplayError(_("There is no pending offer on this move"), 0);
12108         }
12109     } else {
12110         /* Not used for offers from chess program */
12111     }
12112 }
12113
12114 void
12115 RematchEvent()
12116 {
12117     /* Issue ICS rematch command */
12118     if (appData.icsActive) {
12119         SendToICS(ics_prefix);
12120         SendToICS("rematch\n");
12121     }
12122 }
12123
12124 void
12125 CallFlagEvent()
12126 {
12127     /* Call your opponent's flag (claim a win on time) */
12128     if (appData.icsActive) {
12129         SendToICS(ics_prefix);
12130         SendToICS("flag\n");
12131     } else {
12132         switch (gameMode) {
12133           default:
12134             return;
12135           case MachinePlaysWhite:
12136             if (whiteFlag) {
12137                 if (blackFlag)
12138                   GameEnds(GameIsDrawn, "Both players ran out of time",
12139                            GE_PLAYER);
12140                 else
12141                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12142             } else {
12143                 DisplayError(_("Your opponent is not out of time"), 0);
12144             }
12145             break;
12146           case MachinePlaysBlack:
12147             if (blackFlag) {
12148                 if (whiteFlag)
12149                   GameEnds(GameIsDrawn, "Both players ran out of time",
12150                            GE_PLAYER);
12151                 else
12152                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12153             } else {
12154                 DisplayError(_("Your opponent is not out of time"), 0);
12155             }
12156             break;
12157         }
12158     }
12159 }
12160
12161 void
12162 DrawEvent()
12163 {
12164     /* Offer draw or accept pending draw offer from opponent */
12165     
12166     if (appData.icsActive) {
12167         /* Note: tournament rules require draw offers to be
12168            made after you make your move but before you punch
12169            your clock.  Currently ICS doesn't let you do that;
12170            instead, you immediately punch your clock after making
12171            a move, but you can offer a draw at any time. */
12172         
12173         SendToICS(ics_prefix);
12174         SendToICS("draw\n");
12175         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12176     } else if (cmailMsgLoaded) {
12177         if (currentMove == cmailOldMove &&
12178             commentList[cmailOldMove] != NULL &&
12179             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12180                    "Black offers a draw" : "White offers a draw")) {
12181             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12182             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12183         } else if (currentMove == cmailOldMove + 1) {
12184             char *offer = WhiteOnMove(cmailOldMove) ?
12185               "White offers a draw" : "Black offers a draw";
12186             AppendComment(currentMove, offer, TRUE);
12187             DisplayComment(currentMove - 1, offer);
12188             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12189         } else {
12190             DisplayError(_("You must make your move before offering a draw"), 0);
12191             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12192         }
12193     } else if (first.offeredDraw) {
12194         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12195     } else {
12196         if (first.sendDrawOffers) {
12197             SendToProgram("draw\n", &first);
12198             userOfferedDraw = TRUE;
12199         }
12200     }
12201 }
12202
12203 void
12204 AdjournEvent()
12205 {
12206     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12207     
12208     if (appData.icsActive) {
12209         SendToICS(ics_prefix);
12210         SendToICS("adjourn\n");
12211     } else {
12212         /* Currently GNU Chess doesn't offer or accept Adjourns */
12213     }
12214 }
12215
12216
12217 void
12218 AbortEvent()
12219 {
12220     /* Offer Abort or accept pending Abort offer from opponent */
12221     
12222     if (appData.icsActive) {
12223         SendToICS(ics_prefix);
12224         SendToICS("abort\n");
12225     } else {
12226         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12227     }
12228 }
12229
12230 void
12231 ResignEvent()
12232 {
12233     /* Resign.  You can do this even if it's not your turn. */
12234     
12235     if (appData.icsActive) {
12236         SendToICS(ics_prefix);
12237         SendToICS("resign\n");
12238     } else {
12239         switch (gameMode) {
12240           case MachinePlaysWhite:
12241             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12242             break;
12243           case MachinePlaysBlack:
12244             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12245             break;
12246           case EditGame:
12247             if (cmailMsgLoaded) {
12248                 TruncateGame();
12249                 if (WhiteOnMove(cmailOldMove)) {
12250                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12251                 } else {
12252                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12253                 }
12254                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12255             }
12256             break;
12257           default:
12258             break;
12259         }
12260     }
12261 }
12262
12263
12264 void
12265 StopObservingEvent()
12266 {
12267     /* Stop observing current games */
12268     SendToICS(ics_prefix);
12269     SendToICS("unobserve\n");
12270 }
12271
12272 void
12273 StopExaminingEvent()
12274 {
12275     /* Stop observing current game */
12276     SendToICS(ics_prefix);
12277     SendToICS("unexamine\n");
12278 }
12279
12280 void
12281 ForwardInner(target)
12282      int target;
12283 {
12284     int limit;
12285
12286     if (appData.debugMode)
12287         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12288                 target, currentMove, forwardMostMove);
12289
12290     if (gameMode == EditPosition)
12291       return;
12292
12293     if (gameMode == PlayFromGameFile && !pausing)
12294       PauseEvent();
12295     
12296     if (gameMode == IcsExamining && pausing)
12297       limit = pauseExamForwardMostMove;
12298     else
12299       limit = forwardMostMove;
12300     
12301     if (target > limit) target = limit;
12302
12303     if (target > 0 && moveList[target - 1][0]) {
12304         int fromX, fromY, toX, toY;
12305         toX = moveList[target - 1][2] - AAA;
12306         toY = moveList[target - 1][3] - ONE;
12307         if (moveList[target - 1][1] == '@') {
12308             if (appData.highlightLastMove) {
12309                 SetHighlights(-1, -1, toX, toY);
12310             }
12311         } else {
12312             fromX = moveList[target - 1][0] - AAA;
12313             fromY = moveList[target - 1][1] - ONE;
12314             if (target == currentMove + 1) {
12315                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12316             }
12317             if (appData.highlightLastMove) {
12318                 SetHighlights(fromX, fromY, toX, toY);
12319             }
12320         }
12321     }
12322     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12323         gameMode == Training || gameMode == PlayFromGameFile || 
12324         gameMode == AnalyzeFile) {
12325         while (currentMove < target) {
12326             SendMoveToProgram(currentMove++, &first);
12327         }
12328     } else {
12329         currentMove = target;
12330     }
12331     
12332     if (gameMode == EditGame || gameMode == EndOfGame) {
12333         whiteTimeRemaining = timeRemaining[0][currentMove];
12334         blackTimeRemaining = timeRemaining[1][currentMove];
12335     }
12336     DisplayBothClocks();
12337     DisplayMove(currentMove - 1);
12338     DrawPosition(FALSE, boards[currentMove]);
12339     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12340     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12341         DisplayComment(currentMove - 1, commentList[currentMove]);
12342     }
12343 }
12344
12345
12346 void
12347 ForwardEvent()
12348 {
12349     if (gameMode == IcsExamining && !pausing) {
12350         SendToICS(ics_prefix);
12351         SendToICS("forward\n");
12352     } else {
12353         ForwardInner(currentMove + 1);
12354     }
12355 }
12356
12357 void
12358 ToEndEvent()
12359 {
12360     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12361         /* to optimze, we temporarily turn off analysis mode while we feed
12362          * the remaining moves to the engine. Otherwise we get analysis output
12363          * after each move.
12364          */ 
12365         if (first.analysisSupport) {
12366           SendToProgram("exit\nforce\n", &first);
12367           first.analyzing = FALSE;
12368         }
12369     }
12370         
12371     if (gameMode == IcsExamining && !pausing) {
12372         SendToICS(ics_prefix);
12373         SendToICS("forward 999999\n");
12374     } else {
12375         ForwardInner(forwardMostMove);
12376     }
12377
12378     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12379         /* we have fed all the moves, so reactivate analysis mode */
12380         SendToProgram("analyze\n", &first);
12381         first.analyzing = TRUE;
12382         /*first.maybeThinking = TRUE;*/
12383         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12384     }
12385 }
12386
12387 void
12388 BackwardInner(target)
12389      int target;
12390 {
12391     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12392
12393     if (appData.debugMode)
12394         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12395                 target, currentMove, forwardMostMove);
12396
12397     if (gameMode == EditPosition) return;
12398     if (currentMove <= backwardMostMove) {
12399         ClearHighlights();
12400         DrawPosition(full_redraw, boards[currentMove]);
12401         return;
12402     }
12403     if (gameMode == PlayFromGameFile && !pausing)
12404       PauseEvent();
12405     
12406     if (moveList[target][0]) {
12407         int fromX, fromY, toX, toY;
12408         toX = moveList[target][2] - AAA;
12409         toY = moveList[target][3] - ONE;
12410         if (moveList[target][1] == '@') {
12411             if (appData.highlightLastMove) {
12412                 SetHighlights(-1, -1, toX, toY);
12413             }
12414         } else {
12415             fromX = moveList[target][0] - AAA;
12416             fromY = moveList[target][1] - ONE;
12417             if (target == currentMove - 1) {
12418                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12419             }
12420             if (appData.highlightLastMove) {
12421                 SetHighlights(fromX, fromY, toX, toY);
12422             }
12423         }
12424     }
12425     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12426         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12427         while (currentMove > target) {
12428             SendToProgram("undo\n", &first);
12429             currentMove--;
12430         }
12431     } else {
12432         currentMove = target;
12433     }
12434     
12435     if (gameMode == EditGame || gameMode == EndOfGame) {
12436         whiteTimeRemaining = timeRemaining[0][currentMove];
12437         blackTimeRemaining = timeRemaining[1][currentMove];
12438     }
12439     DisplayBothClocks();
12440     DisplayMove(currentMove - 1);
12441     DrawPosition(full_redraw, boards[currentMove]);
12442     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12443     // [HGM] PV info: routine tests if comment empty
12444     DisplayComment(currentMove - 1, commentList[currentMove]);
12445 }
12446
12447 void
12448 BackwardEvent()
12449 {
12450     if (gameMode == IcsExamining && !pausing) {
12451         SendToICS(ics_prefix);
12452         SendToICS("backward\n");
12453     } else {
12454         BackwardInner(currentMove - 1);
12455     }
12456 }
12457
12458 void
12459 ToStartEvent()
12460 {
12461     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12462         /* to optimize, we temporarily turn off analysis mode while we undo
12463          * all the moves. Otherwise we get analysis output after each undo.
12464          */ 
12465         if (first.analysisSupport) {
12466           SendToProgram("exit\nforce\n", &first);
12467           first.analyzing = FALSE;
12468         }
12469     }
12470
12471     if (gameMode == IcsExamining && !pausing) {
12472         SendToICS(ics_prefix);
12473         SendToICS("backward 999999\n");
12474     } else {
12475         BackwardInner(backwardMostMove);
12476     }
12477
12478     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12479         /* we have fed all the moves, so reactivate analysis mode */
12480         SendToProgram("analyze\n", &first);
12481         first.analyzing = TRUE;
12482         /*first.maybeThinking = TRUE;*/
12483         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12484     }
12485 }
12486
12487 void
12488 ToNrEvent(int to)
12489 {
12490   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12491   if (to >= forwardMostMove) to = forwardMostMove;
12492   if (to <= backwardMostMove) to = backwardMostMove;
12493   if (to < currentMove) {
12494     BackwardInner(to);
12495   } else {
12496     ForwardInner(to);
12497   }
12498 }
12499
12500 void
12501 RevertEvent()
12502 {
12503     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12504         return;
12505     }
12506     if (gameMode != IcsExamining) {
12507         DisplayError(_("You are not examining a game"), 0);
12508         return;
12509     }
12510     if (pausing) {
12511         DisplayError(_("You can't revert while pausing"), 0);
12512         return;
12513     }
12514     SendToICS(ics_prefix);
12515     SendToICS("revert\n");
12516 }
12517
12518 void
12519 RetractMoveEvent()
12520 {
12521     switch (gameMode) {
12522       case MachinePlaysWhite:
12523       case MachinePlaysBlack:
12524         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12525             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12526             return;
12527         }
12528         if (forwardMostMove < 2) return;
12529         currentMove = forwardMostMove = forwardMostMove - 2;
12530         whiteTimeRemaining = timeRemaining[0][currentMove];
12531         blackTimeRemaining = timeRemaining[1][currentMove];
12532         DisplayBothClocks();
12533         DisplayMove(currentMove - 1);
12534         ClearHighlights();/*!! could figure this out*/
12535         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12536         SendToProgram("remove\n", &first);
12537         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12538         break;
12539
12540       case BeginningOfGame:
12541       default:
12542         break;
12543
12544       case IcsPlayingWhite:
12545       case IcsPlayingBlack:
12546         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12547             SendToICS(ics_prefix);
12548             SendToICS("takeback 2\n");
12549         } else {
12550             SendToICS(ics_prefix);
12551             SendToICS("takeback 1\n");
12552         }
12553         break;
12554     }
12555 }
12556
12557 void
12558 MoveNowEvent()
12559 {
12560     ChessProgramState *cps;
12561
12562     switch (gameMode) {
12563       case MachinePlaysWhite:
12564         if (!WhiteOnMove(forwardMostMove)) {
12565             DisplayError(_("It is your turn"), 0);
12566             return;
12567         }
12568         cps = &first;
12569         break;
12570       case MachinePlaysBlack:
12571         if (WhiteOnMove(forwardMostMove)) {
12572             DisplayError(_("It is your turn"), 0);
12573             return;
12574         }
12575         cps = &first;
12576         break;
12577       case TwoMachinesPlay:
12578         if (WhiteOnMove(forwardMostMove) ==
12579             (first.twoMachinesColor[0] == 'w')) {
12580             cps = &first;
12581         } else {
12582             cps = &second;
12583         }
12584         break;
12585       case BeginningOfGame:
12586       default:
12587         return;
12588     }
12589     SendToProgram("?\n", cps);
12590 }
12591
12592 void
12593 TruncateGameEvent()
12594 {
12595     EditGameEvent();
12596     if (gameMode != EditGame) return;
12597     TruncateGame();
12598 }
12599
12600 void
12601 TruncateGame()
12602 {
12603     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12604     if (forwardMostMove > currentMove) {
12605         if (gameInfo.resultDetails != NULL) {
12606             free(gameInfo.resultDetails);
12607             gameInfo.resultDetails = NULL;
12608             gameInfo.result = GameUnfinished;
12609         }
12610         forwardMostMove = currentMove;
12611         HistorySet(parseList, backwardMostMove, forwardMostMove,
12612                    currentMove-1);
12613     }
12614 }
12615
12616 void
12617 HintEvent()
12618 {
12619     if (appData.noChessProgram) return;
12620     switch (gameMode) {
12621       case MachinePlaysWhite:
12622         if (WhiteOnMove(forwardMostMove)) {
12623             DisplayError(_("Wait until your turn"), 0);
12624             return;
12625         }
12626         break;
12627       case BeginningOfGame:
12628       case MachinePlaysBlack:
12629         if (!WhiteOnMove(forwardMostMove)) {
12630             DisplayError(_("Wait until your turn"), 0);
12631             return;
12632         }
12633         break;
12634       default:
12635         DisplayError(_("No hint available"), 0);
12636         return;
12637     }
12638     SendToProgram("hint\n", &first);
12639     hintRequested = TRUE;
12640 }
12641
12642 void
12643 BookEvent()
12644 {
12645     if (appData.noChessProgram) return;
12646     switch (gameMode) {
12647       case MachinePlaysWhite:
12648         if (WhiteOnMove(forwardMostMove)) {
12649             DisplayError(_("Wait until your turn"), 0);
12650             return;
12651         }
12652         break;
12653       case BeginningOfGame:
12654       case MachinePlaysBlack:
12655         if (!WhiteOnMove(forwardMostMove)) {
12656             DisplayError(_("Wait until your turn"), 0);
12657             return;
12658         }
12659         break;
12660       case EditPosition:
12661         EditPositionDone(TRUE);
12662         break;
12663       case TwoMachinesPlay:
12664         return;
12665       default:
12666         break;
12667     }
12668     SendToProgram("bk\n", &first);
12669     bookOutput[0] = NULLCHAR;
12670     bookRequested = TRUE;
12671 }
12672
12673 void
12674 AboutGameEvent()
12675 {
12676     char *tags = PGNTags(&gameInfo);
12677     TagsPopUp(tags, CmailMsg());
12678     free(tags);
12679 }
12680
12681 /* end button procedures */
12682
12683 void
12684 PrintPosition(fp, move)
12685      FILE *fp;
12686      int move;
12687 {
12688     int i, j;
12689     
12690     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12691         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12692             char c = PieceToChar(boards[move][i][j]);
12693             fputc(c == 'x' ? '.' : c, fp);
12694             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12695         }
12696     }
12697     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12698       fprintf(fp, "white to play\n");
12699     else
12700       fprintf(fp, "black to play\n");
12701 }
12702
12703 void
12704 PrintOpponents(fp)
12705      FILE *fp;
12706 {
12707     if (gameInfo.white != NULL) {
12708         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12709     } else {
12710         fprintf(fp, "\n");
12711     }
12712 }
12713
12714 /* Find last component of program's own name, using some heuristics */
12715 void
12716 TidyProgramName(prog, host, buf)
12717      char *prog, *host, buf[MSG_SIZ];
12718 {
12719     char *p, *q;
12720     int local = (strcmp(host, "localhost") == 0);
12721     while (!local && (p = strchr(prog, ';')) != NULL) {
12722         p++;
12723         while (*p == ' ') p++;
12724         prog = p;
12725     }
12726     if (*prog == '"' || *prog == '\'') {
12727         q = strchr(prog + 1, *prog);
12728     } else {
12729         q = strchr(prog, ' ');
12730     }
12731     if (q == NULL) q = prog + strlen(prog);
12732     p = q;
12733     while (p >= prog && *p != '/' && *p != '\\') p--;
12734     p++;
12735     if(p == prog && *p == '"') p++;
12736     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12737     memcpy(buf, p, q - p);
12738     buf[q - p] = NULLCHAR;
12739     if (!local) {
12740         strcat(buf, "@");
12741         strcat(buf, host);
12742     }
12743 }
12744
12745 char *
12746 TimeControlTagValue()
12747 {
12748     char buf[MSG_SIZ];
12749     if (!appData.clockMode) {
12750         strcpy(buf, "-");
12751     } else if (movesPerSession > 0) {
12752         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12753     } else if (timeIncrement == 0) {
12754         sprintf(buf, "%ld", timeControl/1000);
12755     } else {
12756         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12757     }
12758     return StrSave(buf);
12759 }
12760
12761 void
12762 SetGameInfo()
12763 {
12764     /* This routine is used only for certain modes */
12765     VariantClass v = gameInfo.variant;
12766     ChessMove r = GameUnfinished;
12767     char *p = NULL;
12768
12769     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12770         r = gameInfo.result; 
12771         p = gameInfo.resultDetails; 
12772         gameInfo.resultDetails = NULL;
12773     }
12774     ClearGameInfo(&gameInfo);
12775     gameInfo.variant = v;
12776
12777     switch (gameMode) {
12778       case MachinePlaysWhite:
12779         gameInfo.event = StrSave( appData.pgnEventHeader );
12780         gameInfo.site = StrSave(HostName());
12781         gameInfo.date = PGNDate();
12782         gameInfo.round = StrSave("-");
12783         gameInfo.white = StrSave(first.tidy);
12784         gameInfo.black = StrSave(UserName());
12785         gameInfo.timeControl = TimeControlTagValue();
12786         break;
12787
12788       case MachinePlaysBlack:
12789         gameInfo.event = StrSave( appData.pgnEventHeader );
12790         gameInfo.site = StrSave(HostName());
12791         gameInfo.date = PGNDate();
12792         gameInfo.round = StrSave("-");
12793         gameInfo.white = StrSave(UserName());
12794         gameInfo.black = StrSave(first.tidy);
12795         gameInfo.timeControl = TimeControlTagValue();
12796         break;
12797
12798       case TwoMachinesPlay:
12799         gameInfo.event = StrSave( appData.pgnEventHeader );
12800         gameInfo.site = StrSave(HostName());
12801         gameInfo.date = PGNDate();
12802         if (matchGame > 0) {
12803             char buf[MSG_SIZ];
12804             sprintf(buf, "%d", matchGame);
12805             gameInfo.round = StrSave(buf);
12806         } else {
12807             gameInfo.round = StrSave("-");
12808         }
12809         if (first.twoMachinesColor[0] == 'w') {
12810             gameInfo.white = StrSave(first.tidy);
12811             gameInfo.black = StrSave(second.tidy);
12812         } else {
12813             gameInfo.white = StrSave(second.tidy);
12814             gameInfo.black = StrSave(first.tidy);
12815         }
12816         gameInfo.timeControl = TimeControlTagValue();
12817         break;
12818
12819       case EditGame:
12820         gameInfo.event = StrSave("Edited game");
12821         gameInfo.site = StrSave(HostName());
12822         gameInfo.date = PGNDate();
12823         gameInfo.round = StrSave("-");
12824         gameInfo.white = StrSave("-");
12825         gameInfo.black = StrSave("-");
12826         gameInfo.result = r;
12827         gameInfo.resultDetails = p;
12828         break;
12829
12830       case EditPosition:
12831         gameInfo.event = StrSave("Edited position");
12832         gameInfo.site = StrSave(HostName());
12833         gameInfo.date = PGNDate();
12834         gameInfo.round = StrSave("-");
12835         gameInfo.white = StrSave("-");
12836         gameInfo.black = StrSave("-");
12837         break;
12838
12839       case IcsPlayingWhite:
12840       case IcsPlayingBlack:
12841       case IcsObserving:
12842       case IcsExamining:
12843         break;
12844
12845       case PlayFromGameFile:
12846         gameInfo.event = StrSave("Game from non-PGN file");
12847         gameInfo.site = StrSave(HostName());
12848         gameInfo.date = PGNDate();
12849         gameInfo.round = StrSave("-");
12850         gameInfo.white = StrSave("?");
12851         gameInfo.black = StrSave("?");
12852         break;
12853
12854       default:
12855         break;
12856     }
12857 }
12858
12859 void
12860 ReplaceComment(index, text)
12861      int index;
12862      char *text;
12863 {
12864     int len;
12865
12866     while (*text == '\n') text++;
12867     len = strlen(text);
12868     while (len > 0 && text[len - 1] == '\n') len--;
12869
12870     if (commentList[index] != NULL)
12871       free(commentList[index]);
12872
12873     if (len == 0) {
12874         commentList[index] = NULL;
12875         return;
12876     }
12877   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12878       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12879       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12880     commentList[index] = (char *) malloc(len + 2);
12881     strncpy(commentList[index], text, len);
12882     commentList[index][len] = '\n';
12883     commentList[index][len + 1] = NULLCHAR;
12884   } else { 
12885     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12886     char *p;
12887     commentList[index] = (char *) malloc(len + 6);
12888     strcpy(commentList[index], "{\n");
12889     strncpy(commentList[index]+2, text, len);
12890     commentList[index][len+2] = NULLCHAR;
12891     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12892     strcat(commentList[index], "\n}\n");
12893   }
12894 }
12895
12896 void
12897 CrushCRs(text)
12898      char *text;
12899 {
12900   char *p = text;
12901   char *q = text;
12902   char ch;
12903
12904   do {
12905     ch = *p++;
12906     if (ch == '\r') continue;
12907     *q++ = ch;
12908   } while (ch != '\0');
12909 }
12910
12911 void
12912 AppendComment(index, text, addBraces)
12913      int index;
12914      char *text;
12915      Boolean addBraces; // [HGM] braces: tells if we should add {}
12916 {
12917     int oldlen, len;
12918     char *old;
12919
12920 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12921     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12922
12923     CrushCRs(text);
12924     while (*text == '\n') text++;
12925     len = strlen(text);
12926     while (len > 0 && text[len - 1] == '\n') len--;
12927
12928     if (len == 0) return;
12929
12930     if (commentList[index] != NULL) {
12931         old = commentList[index];
12932         oldlen = strlen(old);
12933         while(commentList[index][oldlen-1] ==  '\n')
12934           commentList[index][--oldlen] = NULLCHAR;
12935         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12936         strcpy(commentList[index], old);
12937         free(old);
12938         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12939         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12940           if(addBraces) addBraces = FALSE; else { text++; len--; }
12941           while (*text == '\n') { text++; len--; }
12942           commentList[index][--oldlen] = NULLCHAR;
12943       }
12944         if(addBraces) strcat(commentList[index], "\n{\n");
12945         else          strcat(commentList[index], "\n");
12946         strcat(commentList[index], text);
12947         if(addBraces) strcat(commentList[index], "\n}\n");
12948         else          strcat(commentList[index], "\n");
12949     } else {
12950         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12951         if(addBraces)
12952              strcpy(commentList[index], "{\n");
12953         else commentList[index][0] = NULLCHAR;
12954         strcat(commentList[index], text);
12955         strcat(commentList[index], "\n");
12956         if(addBraces) strcat(commentList[index], "}\n");
12957     }
12958 }
12959
12960 static char * FindStr( char * text, char * sub_text )
12961 {
12962     char * result = strstr( text, sub_text );
12963
12964     if( result != NULL ) {
12965         result += strlen( sub_text );
12966     }
12967
12968     return result;
12969 }
12970
12971 /* [AS] Try to extract PV info from PGN comment */
12972 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12973 char *GetInfoFromComment( int index, char * text )
12974 {
12975     char * sep = text;
12976
12977     if( text != NULL && index > 0 ) {
12978         int score = 0;
12979         int depth = 0;
12980         int time = -1, sec = 0, deci;
12981         char * s_eval = FindStr( text, "[%eval " );
12982         char * s_emt = FindStr( text, "[%emt " );
12983
12984         if( s_eval != NULL || s_emt != NULL ) {
12985             /* New style */
12986             char delim;
12987
12988             if( s_eval != NULL ) {
12989                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12990                     return text;
12991                 }
12992
12993                 if( delim != ']' ) {
12994                     return text;
12995                 }
12996             }
12997
12998             if( s_emt != NULL ) {
12999             }
13000                 return text;
13001         }
13002         else {
13003             /* We expect something like: [+|-]nnn.nn/dd */
13004             int score_lo = 0;
13005
13006             if(*text != '{') return text; // [HGM] braces: must be normal comment
13007
13008             sep = strchr( text, '/' );
13009             if( sep == NULL || sep < (text+4) ) {
13010                 return text;
13011             }
13012
13013             time = -1; sec = -1; deci = -1;
13014             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13015                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13016                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13017                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13018                 return text;
13019             }
13020
13021             if( score_lo < 0 || score_lo >= 100 ) {
13022                 return text;
13023             }
13024
13025             if(sec >= 0) time = 600*time + 10*sec; else
13026             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13027
13028             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13029
13030             /* [HGM] PV time: now locate end of PV info */
13031             while( *++sep >= '0' && *sep <= '9'); // strip depth
13032             if(time >= 0)
13033             while( *++sep >= '0' && *sep <= '9'); // strip time
13034             if(sec >= 0)
13035             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13036             if(deci >= 0)
13037             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13038             while(*sep == ' ') sep++;
13039         }
13040
13041         if( depth <= 0 ) {
13042             return text;
13043         }
13044
13045         if( time < 0 ) {
13046             time = -1;
13047         }
13048
13049         pvInfoList[index-1].depth = depth;
13050         pvInfoList[index-1].score = score;
13051         pvInfoList[index-1].time  = 10*time; // centi-sec
13052         if(*sep == '}') *sep = 0; else *--sep = '{';
13053     }
13054     return sep;
13055 }
13056
13057 void
13058 SendToProgram(message, cps)
13059      char *message;
13060      ChessProgramState *cps;
13061 {
13062     int count, outCount, error;
13063     char buf[MSG_SIZ];
13064
13065     if (cps->pr == NULL) return;
13066     Attention(cps);
13067     
13068     if (appData.debugMode) {
13069         TimeMark now;
13070         GetTimeMark(&now);
13071         fprintf(debugFP, "%ld >%-6s: %s", 
13072                 SubtractTimeMarks(&now, &programStartTime),
13073                 cps->which, message);
13074     }
13075     
13076     count = strlen(message);
13077     outCount = OutputToProcess(cps->pr, message, count, &error);
13078     if (outCount < count && !exiting 
13079                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13080         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13081         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13082             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13083                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13084                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13085             } else {
13086                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13087             }
13088             gameInfo.resultDetails = StrSave(buf);
13089         }
13090         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13091     }
13092 }
13093
13094 void
13095 ReceiveFromProgram(isr, closure, message, count, error)
13096      InputSourceRef isr;
13097      VOIDSTAR closure;
13098      char *message;
13099      int count;
13100      int error;
13101 {
13102     char *end_str;
13103     char buf[MSG_SIZ];
13104     ChessProgramState *cps = (ChessProgramState *)closure;
13105
13106     if (isr != cps->isr) return; /* Killed intentionally */
13107     if (count <= 0) {
13108         if (count == 0) {
13109             sprintf(buf,
13110                     _("Error: %s chess program (%s) exited unexpectedly"),
13111                     cps->which, cps->program);
13112         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13113                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13114                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13115                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13116                 } else {
13117                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13118                 }
13119                 gameInfo.resultDetails = StrSave(buf);
13120             }
13121             RemoveInputSource(cps->isr);
13122             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13123         } else {
13124             sprintf(buf,
13125                     _("Error reading from %s chess program (%s)"),
13126                     cps->which, cps->program);
13127             RemoveInputSource(cps->isr);
13128
13129             /* [AS] Program is misbehaving badly... kill it */
13130             if( count == -2 ) {
13131                 DestroyChildProcess( cps->pr, 9 );
13132                 cps->pr = NoProc;
13133             }
13134
13135             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13136         }
13137         return;
13138     }
13139     
13140     if ((end_str = strchr(message, '\r')) != NULL)
13141       *end_str = NULLCHAR;
13142     if ((end_str = strchr(message, '\n')) != NULL)
13143       *end_str = NULLCHAR;
13144     
13145     if (appData.debugMode) {
13146         TimeMark now; int print = 1;
13147         char *quote = ""; char c; int i;
13148
13149         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13150                 char start = message[0];
13151                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13152                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13153                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13154                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13155                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13156                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13157                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13158                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13159                         { quote = "# "; print = (appData.engineComments == 2); }
13160                 message[0] = start; // restore original message
13161         }
13162         if(print) {
13163                 GetTimeMark(&now);
13164                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13165                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13166                         quote,
13167                         message);
13168         }
13169     }
13170
13171     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13172     if (appData.icsEngineAnalyze) {
13173         if (strstr(message, "whisper") != NULL ||
13174              strstr(message, "kibitz") != NULL || 
13175             strstr(message, "tellics") != NULL) return;
13176     }
13177
13178     HandleMachineMove(message, cps);
13179 }
13180
13181
13182 void
13183 SendTimeControl(cps, mps, tc, inc, sd, st)
13184      ChessProgramState *cps;
13185      int mps, inc, sd, st;
13186      long tc;
13187 {
13188     char buf[MSG_SIZ];
13189     int seconds;
13190
13191     if( timeControl_2 > 0 ) {
13192         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13193             tc = timeControl_2;
13194         }
13195     }
13196     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13197     inc /= cps->timeOdds;
13198     st  /= cps->timeOdds;
13199
13200     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13201
13202     if (st > 0) {
13203       /* Set exact time per move, normally using st command */
13204       if (cps->stKludge) {
13205         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13206         seconds = st % 60;
13207         if (seconds == 0) {
13208           sprintf(buf, "level 1 %d\n", st/60);
13209         } else {
13210           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13211         }
13212       } else {
13213         sprintf(buf, "st %d\n", st);
13214       }
13215     } else {
13216       /* Set conventional or incremental time control, using level command */
13217       if (seconds == 0) {
13218         /* Note old gnuchess bug -- minutes:seconds used to not work.
13219            Fixed in later versions, but still avoid :seconds
13220            when seconds is 0. */
13221         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13222       } else {
13223         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13224                 seconds, inc/1000);
13225       }
13226     }
13227     SendToProgram(buf, cps);
13228
13229     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13230     /* Orthogonally, limit search to given depth */
13231     if (sd > 0) {
13232       if (cps->sdKludge) {
13233         sprintf(buf, "depth\n%d\n", sd);
13234       } else {
13235         sprintf(buf, "sd %d\n", sd);
13236       }
13237       SendToProgram(buf, cps);
13238     }
13239
13240     if(cps->nps > 0) { /* [HGM] nps */
13241         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13242         else {
13243                 sprintf(buf, "nps %d\n", cps->nps);
13244               SendToProgram(buf, cps);
13245         }
13246     }
13247 }
13248
13249 ChessProgramState *WhitePlayer()
13250 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13251 {
13252     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13253        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13254         return &second;
13255     return &first;
13256 }
13257
13258 void
13259 SendTimeRemaining(cps, machineWhite)
13260      ChessProgramState *cps;
13261      int /*boolean*/ machineWhite;
13262 {
13263     char message[MSG_SIZ];
13264     long time, otime;
13265
13266     /* Note: this routine must be called when the clocks are stopped
13267        or when they have *just* been set or switched; otherwise
13268        it will be off by the time since the current tick started.
13269     */
13270     if (machineWhite) {
13271         time = whiteTimeRemaining / 10;
13272         otime = blackTimeRemaining / 10;
13273     } else {
13274         time = blackTimeRemaining / 10;
13275         otime = whiteTimeRemaining / 10;
13276     }
13277     /* [HGM] translate opponent's time by time-odds factor */
13278     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13279     if (appData.debugMode) {
13280         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13281     }
13282
13283     if (time <= 0) time = 1;
13284     if (otime <= 0) otime = 1;
13285     
13286     sprintf(message, "time %ld\n", time);
13287     SendToProgram(message, cps);
13288
13289     sprintf(message, "otim %ld\n", otime);
13290     SendToProgram(message, cps);
13291 }
13292
13293 int
13294 BoolFeature(p, name, loc, cps)
13295      char **p;
13296      char *name;
13297      int *loc;
13298      ChessProgramState *cps;
13299 {
13300   char buf[MSG_SIZ];
13301   int len = strlen(name);
13302   int val;
13303   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13304     (*p) += len + 1;
13305     sscanf(*p, "%d", &val);
13306     *loc = (val != 0);
13307     while (**p && **p != ' ') (*p)++;
13308     sprintf(buf, "accepted %s\n", name);
13309     SendToProgram(buf, cps);
13310     return TRUE;
13311   }
13312   return FALSE;
13313 }
13314
13315 int
13316 IntFeature(p, name, loc, cps)
13317      char **p;
13318      char *name;
13319      int *loc;
13320      ChessProgramState *cps;
13321 {
13322   char buf[MSG_SIZ];
13323   int len = strlen(name);
13324   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13325     (*p) += len + 1;
13326     sscanf(*p, "%d", loc);
13327     while (**p && **p != ' ') (*p)++;
13328     sprintf(buf, "accepted %s\n", name);
13329     SendToProgram(buf, cps);
13330     return TRUE;
13331   }
13332   return FALSE;
13333 }
13334
13335 int
13336 StringFeature(p, name, loc, cps)
13337      char **p;
13338      char *name;
13339      char loc[];
13340      ChessProgramState *cps;
13341 {
13342   char buf[MSG_SIZ];
13343   int len = strlen(name);
13344   if (strncmp((*p), name, len) == 0
13345       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13346     (*p) += len + 2;
13347     sscanf(*p, "%[^\"]", loc);
13348     while (**p && **p != '\"') (*p)++;
13349     if (**p == '\"') (*p)++;
13350     sprintf(buf, "accepted %s\n", name);
13351     SendToProgram(buf, cps);
13352     return TRUE;
13353   }
13354   return FALSE;
13355 }
13356
13357 int 
13358 ParseOption(Option *opt, ChessProgramState *cps)
13359 // [HGM] options: process the string that defines an engine option, and determine
13360 // name, type, default value, and allowed value range
13361 {
13362         char *p, *q, buf[MSG_SIZ];
13363         int n, min = (-1)<<31, max = 1<<31, def;
13364
13365         if(p = strstr(opt->name, " -spin ")) {
13366             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13367             if(max < min) max = min; // enforce consistency
13368             if(def < min) def = min;
13369             if(def > max) def = max;
13370             opt->value = def;
13371             opt->min = min;
13372             opt->max = max;
13373             opt->type = Spin;
13374         } else if((p = strstr(opt->name, " -slider "))) {
13375             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13376             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13377             if(max < min) max = min; // enforce consistency
13378             if(def < min) def = min;
13379             if(def > max) def = max;
13380             opt->value = def;
13381             opt->min = min;
13382             opt->max = max;
13383             opt->type = Spin; // Slider;
13384         } else if((p = strstr(opt->name, " -string "))) {
13385             opt->textValue = p+9;
13386             opt->type = TextBox;
13387         } else if((p = strstr(opt->name, " -file "))) {
13388             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13389             opt->textValue = p+7;
13390             opt->type = TextBox; // FileName;
13391         } else if((p = strstr(opt->name, " -path "))) {
13392             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13393             opt->textValue = p+7;
13394             opt->type = TextBox; // PathName;
13395         } else if(p = strstr(opt->name, " -check ")) {
13396             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13397             opt->value = (def != 0);
13398             opt->type = CheckBox;
13399         } else if(p = strstr(opt->name, " -combo ")) {
13400             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13401             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13402             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13403             opt->value = n = 0;
13404             while(q = StrStr(q, " /// ")) {
13405                 n++; *q = 0;    // count choices, and null-terminate each of them
13406                 q += 5;
13407                 if(*q == '*') { // remember default, which is marked with * prefix
13408                     q++;
13409                     opt->value = n;
13410                 }
13411                 cps->comboList[cps->comboCnt++] = q;
13412             }
13413             cps->comboList[cps->comboCnt++] = NULL;
13414             opt->max = n + 1;
13415             opt->type = ComboBox;
13416         } else if(p = strstr(opt->name, " -button")) {
13417             opt->type = Button;
13418         } else if(p = strstr(opt->name, " -save")) {
13419             opt->type = SaveButton;
13420         } else return FALSE;
13421         *p = 0; // terminate option name
13422         // now look if the command-line options define a setting for this engine option.
13423         if(cps->optionSettings && cps->optionSettings[0])
13424             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13425         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13426                 sprintf(buf, "option %s", p);
13427                 if(p = strstr(buf, ",")) *p = 0;
13428                 strcat(buf, "\n");
13429                 SendToProgram(buf, cps);
13430         }
13431         return TRUE;
13432 }
13433
13434 void
13435 FeatureDone(cps, val)
13436      ChessProgramState* cps;
13437      int val;
13438 {
13439   DelayedEventCallback cb = GetDelayedEvent();
13440   if ((cb == InitBackEnd3 && cps == &first) ||
13441       (cb == TwoMachinesEventIfReady && cps == &second)) {
13442     CancelDelayedEvent();
13443     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13444   }
13445   cps->initDone = val;
13446 }
13447
13448 /* Parse feature command from engine */
13449 void
13450 ParseFeatures(args, cps)
13451      char* args;
13452      ChessProgramState *cps;  
13453 {
13454   char *p = args;
13455   char *q;
13456   int val;
13457   char buf[MSG_SIZ];
13458
13459   for (;;) {
13460     while (*p == ' ') p++;
13461     if (*p == NULLCHAR) return;
13462
13463     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13464     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13465     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13466     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13467     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13468     if (BoolFeature(&p, "reuse", &val, cps)) {
13469       /* Engine can disable reuse, but can't enable it if user said no */
13470       if (!val) cps->reuse = FALSE;
13471       continue;
13472     }
13473     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13474     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13475       if (gameMode == TwoMachinesPlay) {
13476         DisplayTwoMachinesTitle();
13477       } else {
13478         DisplayTitle("");
13479       }
13480       continue;
13481     }
13482     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13483     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13484     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13485     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13486     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13487     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13488     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13489     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13490     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13491     if (IntFeature(&p, "done", &val, cps)) {
13492       FeatureDone(cps, val);
13493       continue;
13494     }
13495     /* Added by Tord: */
13496     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13497     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13498     /* End of additions by Tord */
13499
13500     /* [HGM] added features: */
13501     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13502     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13503     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13504     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13505     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13506     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13507     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13508         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13509             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13510             SendToProgram(buf, cps);
13511             continue;
13512         }
13513         if(cps->nrOptions >= MAX_OPTIONS) {
13514             cps->nrOptions--;
13515             sprintf(buf, "%s engine has too many options\n", cps->which);
13516             DisplayError(buf, 0);
13517         }
13518         continue;
13519     }
13520     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13521     /* End of additions by HGM */
13522
13523     /* unknown feature: complain and skip */
13524     q = p;
13525     while (*q && *q != '=') q++;
13526     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13527     SendToProgram(buf, cps);
13528     p = q;
13529     if (*p == '=') {
13530       p++;
13531       if (*p == '\"') {
13532         p++;
13533         while (*p && *p != '\"') p++;
13534         if (*p == '\"') p++;
13535       } else {
13536         while (*p && *p != ' ') p++;
13537       }
13538     }
13539   }
13540
13541 }
13542
13543 void
13544 PeriodicUpdatesEvent(newState)
13545      int newState;
13546 {
13547     if (newState == appData.periodicUpdates)
13548       return;
13549
13550     appData.periodicUpdates=newState;
13551
13552     /* Display type changes, so update it now */
13553 //    DisplayAnalysis();
13554
13555     /* Get the ball rolling again... */
13556     if (newState) {
13557         AnalysisPeriodicEvent(1);
13558         StartAnalysisClock();
13559     }
13560 }
13561
13562 void
13563 PonderNextMoveEvent(newState)
13564      int newState;
13565 {
13566     if (newState == appData.ponderNextMove) return;
13567     if (gameMode == EditPosition) EditPositionDone(TRUE);
13568     if (newState) {
13569         SendToProgram("hard\n", &first);
13570         if (gameMode == TwoMachinesPlay) {
13571             SendToProgram("hard\n", &second);
13572         }
13573     } else {
13574         SendToProgram("easy\n", &first);
13575         thinkOutput[0] = NULLCHAR;
13576         if (gameMode == TwoMachinesPlay) {
13577             SendToProgram("easy\n", &second);
13578         }
13579     }
13580     appData.ponderNextMove = newState;
13581 }
13582
13583 void
13584 NewSettingEvent(option, command, value)
13585      char *command;
13586      int option, value;
13587 {
13588     char buf[MSG_SIZ];
13589
13590     if (gameMode == EditPosition) EditPositionDone(TRUE);
13591     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13592     SendToProgram(buf, &first);
13593     if (gameMode == TwoMachinesPlay) {
13594         SendToProgram(buf, &second);
13595     }
13596 }
13597
13598 void
13599 ShowThinkingEvent()
13600 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13601 {
13602     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13603     int newState = appData.showThinking
13604         // [HGM] thinking: other features now need thinking output as well
13605         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13606     
13607     if (oldState == newState) return;
13608     oldState = newState;
13609     if (gameMode == EditPosition) EditPositionDone(TRUE);
13610     if (oldState) {
13611         SendToProgram("post\n", &first);
13612         if (gameMode == TwoMachinesPlay) {
13613             SendToProgram("post\n", &second);
13614         }
13615     } else {
13616         SendToProgram("nopost\n", &first);
13617         thinkOutput[0] = NULLCHAR;
13618         if (gameMode == TwoMachinesPlay) {
13619             SendToProgram("nopost\n", &second);
13620         }
13621     }
13622 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13623 }
13624
13625 void
13626 AskQuestionEvent(title, question, replyPrefix, which)
13627      char *title; char *question; char *replyPrefix; char *which;
13628 {
13629   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13630   if (pr == NoProc) return;
13631   AskQuestion(title, question, replyPrefix, pr);
13632 }
13633
13634 void
13635 DisplayMove(moveNumber)
13636      int moveNumber;
13637 {
13638     char message[MSG_SIZ];
13639     char res[MSG_SIZ];
13640     char cpThinkOutput[MSG_SIZ];
13641
13642     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13643     
13644     if (moveNumber == forwardMostMove - 1 || 
13645         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13646
13647         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13648
13649         if (strchr(cpThinkOutput, '\n')) {
13650             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13651         }
13652     } else {
13653         *cpThinkOutput = NULLCHAR;
13654     }
13655
13656     /* [AS] Hide thinking from human user */
13657     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13658         *cpThinkOutput = NULLCHAR;
13659         if( thinkOutput[0] != NULLCHAR ) {
13660             int i;
13661
13662             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13663                 cpThinkOutput[i] = '.';
13664             }
13665             cpThinkOutput[i] = NULLCHAR;
13666             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13667         }
13668     }
13669
13670     if (moveNumber == forwardMostMove - 1 &&
13671         gameInfo.resultDetails != NULL) {
13672         if (gameInfo.resultDetails[0] == NULLCHAR) {
13673             sprintf(res, " %s", PGNResult(gameInfo.result));
13674         } else {
13675             sprintf(res, " {%s} %s",
13676                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13677         }
13678     } else {
13679         res[0] = NULLCHAR;
13680     }
13681
13682     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13683         DisplayMessage(res, cpThinkOutput);
13684     } else {
13685         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13686                 WhiteOnMove(moveNumber) ? " " : ".. ",
13687                 parseList[moveNumber], res);
13688         DisplayMessage(message, cpThinkOutput);
13689     }
13690 }
13691
13692 void
13693 DisplayComment(moveNumber, text)
13694      int moveNumber;
13695      char *text;
13696 {
13697     char title[MSG_SIZ];
13698     char buf[8000]; // comment can be long!
13699     int score, depth;
13700     
13701     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13702       strcpy(title, "Comment");
13703     } else {
13704       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13705               WhiteOnMove(moveNumber) ? " " : ".. ",
13706               parseList[moveNumber]);
13707     }
13708     // [HGM] PV info: display PV info together with (or as) comment
13709     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13710       if(text == NULL) text = "";                                           
13711       score = pvInfoList[moveNumber].score;
13712       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13713               depth, (pvInfoList[moveNumber].time+50)/100, text);
13714       text = buf;
13715     }
13716     if (text != NULL && (appData.autoDisplayComment || commentUp))
13717         CommentPopUp(title, text);
13718 }
13719
13720 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13721  * might be busy thinking or pondering.  It can be omitted if your
13722  * gnuchess is configured to stop thinking immediately on any user
13723  * input.  However, that gnuchess feature depends on the FIONREAD
13724  * ioctl, which does not work properly on some flavors of Unix.
13725  */
13726 void
13727 Attention(cps)
13728      ChessProgramState *cps;
13729 {
13730 #if ATTENTION
13731     if (!cps->useSigint) return;
13732     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13733     switch (gameMode) {
13734       case MachinePlaysWhite:
13735       case MachinePlaysBlack:
13736       case TwoMachinesPlay:
13737       case IcsPlayingWhite:
13738       case IcsPlayingBlack:
13739       case AnalyzeMode:
13740       case AnalyzeFile:
13741         /* Skip if we know it isn't thinking */
13742         if (!cps->maybeThinking) return;
13743         if (appData.debugMode)
13744           fprintf(debugFP, "Interrupting %s\n", cps->which);
13745         InterruptChildProcess(cps->pr);
13746         cps->maybeThinking = FALSE;
13747         break;
13748       default:
13749         break;
13750     }
13751 #endif /*ATTENTION*/
13752 }
13753
13754 int
13755 CheckFlags()
13756 {
13757     if (whiteTimeRemaining <= 0) {
13758         if (!whiteFlag) {
13759             whiteFlag = TRUE;
13760             if (appData.icsActive) {
13761                 if (appData.autoCallFlag &&
13762                     gameMode == IcsPlayingBlack && !blackFlag) {
13763                   SendToICS(ics_prefix);
13764                   SendToICS("flag\n");
13765                 }
13766             } else {
13767                 if (blackFlag) {
13768                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13769                 } else {
13770                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13771                     if (appData.autoCallFlag) {
13772                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13773                         return TRUE;
13774                     }
13775                 }
13776             }
13777         }
13778     }
13779     if (blackTimeRemaining <= 0) {
13780         if (!blackFlag) {
13781             blackFlag = TRUE;
13782             if (appData.icsActive) {
13783                 if (appData.autoCallFlag &&
13784                     gameMode == IcsPlayingWhite && !whiteFlag) {
13785                   SendToICS(ics_prefix);
13786                   SendToICS("flag\n");
13787                 }
13788             } else {
13789                 if (whiteFlag) {
13790                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13791                 } else {
13792                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13793                     if (appData.autoCallFlag) {
13794                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13795                         return TRUE;
13796                     }
13797                 }
13798             }
13799         }
13800     }
13801     return FALSE;
13802 }
13803
13804 void
13805 CheckTimeControl()
13806 {
13807     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13808         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13809
13810     /*
13811      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13812      */
13813     if ( !WhiteOnMove(forwardMostMove) )
13814         /* White made time control */
13815         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13816         /* [HGM] time odds: correct new time quota for time odds! */
13817                                             / WhitePlayer()->timeOdds;
13818       else
13819         /* Black made time control */
13820         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13821                                             / WhitePlayer()->other->timeOdds;
13822 }
13823
13824 void
13825 DisplayBothClocks()
13826 {
13827     int wom = gameMode == EditPosition ?
13828       !blackPlaysFirst : WhiteOnMove(currentMove);
13829     DisplayWhiteClock(whiteTimeRemaining, wom);
13830     DisplayBlackClock(blackTimeRemaining, !wom);
13831 }
13832
13833
13834 /* Timekeeping seems to be a portability nightmare.  I think everyone
13835    has ftime(), but I'm really not sure, so I'm including some ifdefs
13836    to use other calls if you don't.  Clocks will be less accurate if
13837    you have neither ftime nor gettimeofday.
13838 */
13839
13840 /* VS 2008 requires the #include outside of the function */
13841 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13842 #include <sys/timeb.h>
13843 #endif
13844
13845 /* Get the current time as a TimeMark */
13846 void
13847 GetTimeMark(tm)
13848      TimeMark *tm;
13849 {
13850 #if HAVE_GETTIMEOFDAY
13851
13852     struct timeval timeVal;
13853     struct timezone timeZone;
13854
13855     gettimeofday(&timeVal, &timeZone);
13856     tm->sec = (long) timeVal.tv_sec; 
13857     tm->ms = (int) (timeVal.tv_usec / 1000L);
13858
13859 #else /*!HAVE_GETTIMEOFDAY*/
13860 #if HAVE_FTIME
13861
13862 // include <sys/timeb.h> / moved to just above start of function
13863     struct timeb timeB;
13864
13865     ftime(&timeB);
13866     tm->sec = (long) timeB.time;
13867     tm->ms = (int) timeB.millitm;
13868
13869 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13870     tm->sec = (long) time(NULL);
13871     tm->ms = 0;
13872 #endif
13873 #endif
13874 }
13875
13876 /* Return the difference in milliseconds between two
13877    time marks.  We assume the difference will fit in a long!
13878 */
13879 long
13880 SubtractTimeMarks(tm2, tm1)
13881      TimeMark *tm2, *tm1;
13882 {
13883     return 1000L*(tm2->sec - tm1->sec) +
13884            (long) (tm2->ms - tm1->ms);
13885 }
13886
13887
13888 /*
13889  * Code to manage the game clocks.
13890  *
13891  * In tournament play, black starts the clock and then white makes a move.
13892  * We give the human user a slight advantage if he is playing white---the
13893  * clocks don't run until he makes his first move, so it takes zero time.
13894  * Also, we don't account for network lag, so we could get out of sync
13895  * with GNU Chess's clock -- but then, referees are always right.  
13896  */
13897
13898 static TimeMark tickStartTM;
13899 static long intendedTickLength;
13900
13901 long
13902 NextTickLength(timeRemaining)
13903      long timeRemaining;
13904 {
13905     long nominalTickLength, nextTickLength;
13906
13907     if (timeRemaining > 0L && timeRemaining <= 10000L)
13908       nominalTickLength = 100L;
13909     else
13910       nominalTickLength = 1000L;
13911     nextTickLength = timeRemaining % nominalTickLength;
13912     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13913
13914     return nextTickLength;
13915 }
13916
13917 /* Adjust clock one minute up or down */
13918 void
13919 AdjustClock(Boolean which, int dir)
13920 {
13921     if(which) blackTimeRemaining += 60000*dir;
13922     else      whiteTimeRemaining += 60000*dir;
13923     DisplayBothClocks();
13924 }
13925
13926 /* Stop clocks and reset to a fresh time control */
13927 void
13928 ResetClocks() 
13929 {
13930     (void) StopClockTimer();
13931     if (appData.icsActive) {
13932         whiteTimeRemaining = blackTimeRemaining = 0;
13933     } else if (searchTime) {
13934         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13935         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13936     } else { /* [HGM] correct new time quote for time odds */
13937         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13938         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13939     }
13940     if (whiteFlag || blackFlag) {
13941         DisplayTitle("");
13942         whiteFlag = blackFlag = FALSE;
13943     }
13944     DisplayBothClocks();
13945 }
13946
13947 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13948
13949 /* Decrement running clock by amount of time that has passed */
13950 void
13951 DecrementClocks()
13952 {
13953     long timeRemaining;
13954     long lastTickLength, fudge;
13955     TimeMark now;
13956
13957     if (!appData.clockMode) return;
13958     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13959         
13960     GetTimeMark(&now);
13961
13962     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13963
13964     /* Fudge if we woke up a little too soon */
13965     fudge = intendedTickLength - lastTickLength;
13966     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13967
13968     if (WhiteOnMove(forwardMostMove)) {
13969         if(whiteNPS >= 0) lastTickLength = 0;
13970         timeRemaining = whiteTimeRemaining -= lastTickLength;
13971         DisplayWhiteClock(whiteTimeRemaining - fudge,
13972                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13973     } else {
13974         if(blackNPS >= 0) lastTickLength = 0;
13975         timeRemaining = blackTimeRemaining -= lastTickLength;
13976         DisplayBlackClock(blackTimeRemaining - fudge,
13977                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13978     }
13979
13980     if (CheckFlags()) return;
13981         
13982     tickStartTM = now;
13983     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13984     StartClockTimer(intendedTickLength);
13985
13986     /* if the time remaining has fallen below the alarm threshold, sound the
13987      * alarm. if the alarm has sounded and (due to a takeback or time control
13988      * with increment) the time remaining has increased to a level above the
13989      * threshold, reset the alarm so it can sound again. 
13990      */
13991     
13992     if (appData.icsActive && appData.icsAlarm) {
13993
13994         /* make sure we are dealing with the user's clock */
13995         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13996                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13997            )) return;
13998
13999         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14000             alarmSounded = FALSE;
14001         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14002             PlayAlarmSound();
14003             alarmSounded = TRUE;
14004         }
14005     }
14006 }
14007
14008
14009 /* A player has just moved, so stop the previously running
14010    clock and (if in clock mode) start the other one.
14011    We redisplay both clocks in case we're in ICS mode, because
14012    ICS gives us an update to both clocks after every move.
14013    Note that this routine is called *after* forwardMostMove
14014    is updated, so the last fractional tick must be subtracted
14015    from the color that is *not* on move now.
14016 */
14017 void
14018 SwitchClocks()
14019 {
14020     long lastTickLength;
14021     TimeMark now;
14022     int flagged = FALSE;
14023
14024     GetTimeMark(&now);
14025
14026     if (StopClockTimer() && appData.clockMode) {
14027         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14028         if (WhiteOnMove(forwardMostMove)) {
14029             if(blackNPS >= 0) lastTickLength = 0;
14030             blackTimeRemaining -= lastTickLength;
14031            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14032 //         if(pvInfoList[forwardMostMove-1].time == -1)
14033                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14034                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14035         } else {
14036            if(whiteNPS >= 0) lastTickLength = 0;
14037            whiteTimeRemaining -= lastTickLength;
14038            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14039 //         if(pvInfoList[forwardMostMove-1].time == -1)
14040                  pvInfoList[forwardMostMove-1].time = 
14041                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14042         }
14043         flagged = CheckFlags();
14044     }
14045     CheckTimeControl();
14046
14047     if (flagged || !appData.clockMode) return;
14048
14049     switch (gameMode) {
14050       case MachinePlaysBlack:
14051       case MachinePlaysWhite:
14052       case BeginningOfGame:
14053         if (pausing) return;
14054         break;
14055
14056       case EditGame:
14057       case PlayFromGameFile:
14058       case IcsExamining:
14059         return;
14060
14061       default:
14062         break;
14063     }
14064
14065     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14066         if(WhiteOnMove(forwardMostMove))
14067              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14068         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14069     }
14070
14071     tickStartTM = now;
14072     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14073       whiteTimeRemaining : blackTimeRemaining);
14074     StartClockTimer(intendedTickLength);
14075 }
14076         
14077
14078 /* Stop both clocks */
14079 void
14080 StopClocks()
14081 {       
14082     long lastTickLength;
14083     TimeMark now;
14084
14085     if (!StopClockTimer()) return;
14086     if (!appData.clockMode) return;
14087
14088     GetTimeMark(&now);
14089
14090     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14091     if (WhiteOnMove(forwardMostMove)) {
14092         if(whiteNPS >= 0) lastTickLength = 0;
14093         whiteTimeRemaining -= lastTickLength;
14094         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14095     } else {
14096         if(blackNPS >= 0) lastTickLength = 0;
14097         blackTimeRemaining -= lastTickLength;
14098         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14099     }
14100     CheckFlags();
14101 }
14102         
14103 /* Start clock of player on move.  Time may have been reset, so
14104    if clock is already running, stop and restart it. */
14105 void
14106 StartClocks()
14107 {
14108     (void) StopClockTimer(); /* in case it was running already */
14109     DisplayBothClocks();
14110     if (CheckFlags()) return;
14111
14112     if (!appData.clockMode) return;
14113     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14114
14115     GetTimeMark(&tickStartTM);
14116     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14117       whiteTimeRemaining : blackTimeRemaining);
14118
14119    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14120     whiteNPS = blackNPS = -1; 
14121     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14122        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14123         whiteNPS = first.nps;
14124     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14125        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14126         blackNPS = first.nps;
14127     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14128         whiteNPS = second.nps;
14129     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14130         blackNPS = second.nps;
14131     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14132
14133     StartClockTimer(intendedTickLength);
14134 }
14135
14136 char *
14137 TimeString(ms)
14138      long ms;
14139 {
14140     long second, minute, hour, day;
14141     char *sign = "";
14142     static char buf[32];
14143     
14144     if (ms > 0 && ms <= 9900) {
14145       /* convert milliseconds to tenths, rounding up */
14146       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14147
14148       sprintf(buf, " %03.1f ", tenths/10.0);
14149       return buf;
14150     }
14151
14152     /* convert milliseconds to seconds, rounding up */
14153     /* use floating point to avoid strangeness of integer division
14154        with negative dividends on many machines */
14155     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14156
14157     if (second < 0) {
14158         sign = "-";
14159         second = -second;
14160     }
14161     
14162     day = second / (60 * 60 * 24);
14163     second = second % (60 * 60 * 24);
14164     hour = second / (60 * 60);
14165     second = second % (60 * 60);
14166     minute = second / 60;
14167     second = second % 60;
14168     
14169     if (day > 0)
14170       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14171               sign, day, hour, minute, second);
14172     else if (hour > 0)
14173       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14174     else
14175       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14176     
14177     return buf;
14178 }
14179
14180
14181 /*
14182  * This is necessary because some C libraries aren't ANSI C compliant yet.
14183  */
14184 char *
14185 StrStr(string, match)
14186      char *string, *match;
14187 {
14188     int i, length;
14189     
14190     length = strlen(match);
14191     
14192     for (i = strlen(string) - length; i >= 0; i--, string++)
14193       if (!strncmp(match, string, length))
14194         return string;
14195     
14196     return NULL;
14197 }
14198
14199 char *
14200 StrCaseStr(string, match)
14201      char *string, *match;
14202 {
14203     int i, j, length;
14204     
14205     length = strlen(match);
14206     
14207     for (i = strlen(string) - length; i >= 0; i--, string++) {
14208         for (j = 0; j < length; j++) {
14209             if (ToLower(match[j]) != ToLower(string[j]))
14210               break;
14211         }
14212         if (j == length) return string;
14213     }
14214
14215     return NULL;
14216 }
14217
14218 #ifndef _amigados
14219 int
14220 StrCaseCmp(s1, s2)
14221      char *s1, *s2;
14222 {
14223     char c1, c2;
14224     
14225     for (;;) {
14226         c1 = ToLower(*s1++);
14227         c2 = ToLower(*s2++);
14228         if (c1 > c2) return 1;
14229         if (c1 < c2) return -1;
14230         if (c1 == NULLCHAR) return 0;
14231     }
14232 }
14233
14234
14235 int
14236 ToLower(c)
14237      int c;
14238 {
14239     return isupper(c) ? tolower(c) : c;
14240 }
14241
14242
14243 int
14244 ToUpper(c)
14245      int c;
14246 {
14247     return islower(c) ? toupper(c) : c;
14248 }
14249 #endif /* !_amigados    */
14250
14251 char *
14252 StrSave(s)
14253      char *s;
14254 {
14255     char *ret;
14256
14257     if ((ret = (char *) malloc(strlen(s) + 1))) {
14258         strcpy(ret, s);
14259     }
14260     return ret;
14261 }
14262
14263 char *
14264 StrSavePtr(s, savePtr)
14265      char *s, **savePtr;
14266 {
14267     if (*savePtr) {
14268         free(*savePtr);
14269     }
14270     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14271         strcpy(*savePtr, s);
14272     }
14273     return(*savePtr);
14274 }
14275
14276 char *
14277 PGNDate()
14278 {
14279     time_t clock;
14280     struct tm *tm;
14281     char buf[MSG_SIZ];
14282
14283     clock = time((time_t *)NULL);
14284     tm = localtime(&clock);
14285     sprintf(buf, "%04d.%02d.%02d",
14286             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14287     return StrSave(buf);
14288 }
14289
14290
14291 char *
14292 PositionToFEN(move, overrideCastling)
14293      int move;
14294      char *overrideCastling;
14295 {
14296     int i, j, fromX, fromY, toX, toY;
14297     int whiteToPlay;
14298     char buf[128];
14299     char *p, *q;
14300     int emptycount;
14301     ChessSquare piece;
14302
14303     whiteToPlay = (gameMode == EditPosition) ?
14304       !blackPlaysFirst : (move % 2 == 0);
14305     p = buf;
14306
14307     /* Piece placement data */
14308     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14309         emptycount = 0;
14310         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14311             if (boards[move][i][j] == EmptySquare) {
14312                 emptycount++;
14313             } else { ChessSquare piece = boards[move][i][j];
14314                 if (emptycount > 0) {
14315                     if(emptycount<10) /* [HGM] can be >= 10 */
14316                         *p++ = '0' + emptycount;
14317                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14318                     emptycount = 0;
14319                 }
14320                 if(PieceToChar(piece) == '+') {
14321                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14322                     *p++ = '+';
14323                     piece = (ChessSquare)(DEMOTED piece);
14324                 } 
14325                 *p++ = PieceToChar(piece);
14326                 if(p[-1] == '~') {
14327                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14328                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14329                     *p++ = '~';
14330                 }
14331             }
14332         }
14333         if (emptycount > 0) {
14334             if(emptycount<10) /* [HGM] can be >= 10 */
14335                 *p++ = '0' + emptycount;
14336             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14337             emptycount = 0;
14338         }
14339         *p++ = '/';
14340     }
14341     *(p - 1) = ' ';
14342
14343     /* [HGM] print Crazyhouse or Shogi holdings */
14344     if( gameInfo.holdingsWidth ) {
14345         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14346         q = p;
14347         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14348             piece = boards[move][i][BOARD_WIDTH-1];
14349             if( piece != EmptySquare )
14350               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14351                   *p++ = PieceToChar(piece);
14352         }
14353         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14354             piece = boards[move][BOARD_HEIGHT-i-1][0];
14355             if( piece != EmptySquare )
14356               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14357                   *p++ = PieceToChar(piece);
14358         }
14359
14360         if( q == p ) *p++ = '-';
14361         *p++ = ']';
14362         *p++ = ' ';
14363     }
14364
14365     /* Active color */
14366     *p++ = whiteToPlay ? 'w' : 'b';
14367     *p++ = ' ';
14368
14369   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14370     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14371   } else {
14372   if(nrCastlingRights) {
14373      q = p;
14374      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14375        /* [HGM] write directly from rights */
14376            if(boards[move][CASTLING][2] != NoRights &&
14377               boards[move][CASTLING][0] != NoRights   )
14378                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14379            if(boards[move][CASTLING][2] != NoRights &&
14380               boards[move][CASTLING][1] != NoRights   )
14381                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14382            if(boards[move][CASTLING][5] != NoRights &&
14383               boards[move][CASTLING][3] != NoRights   )
14384                 *p++ = boards[move][CASTLING][3] + AAA;
14385            if(boards[move][CASTLING][5] != NoRights &&
14386               boards[move][CASTLING][4] != NoRights   )
14387                 *p++ = boards[move][CASTLING][4] + AAA;
14388      } else {
14389
14390         /* [HGM] write true castling rights */
14391         if( nrCastlingRights == 6 ) {
14392             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14393                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14394             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14395                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14396             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14397                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14398             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14399                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14400         }
14401      }
14402      if (q == p) *p++ = '-'; /* No castling rights */
14403      *p++ = ' ';
14404   }
14405
14406   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14407      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14408     /* En passant target square */
14409     if (move > backwardMostMove) {
14410         fromX = moveList[move - 1][0] - AAA;
14411         fromY = moveList[move - 1][1] - ONE;
14412         toX = moveList[move - 1][2] - AAA;
14413         toY = moveList[move - 1][3] - ONE;
14414         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14415             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14416             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14417             fromX == toX) {
14418             /* 2-square pawn move just happened */
14419             *p++ = toX + AAA;
14420             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14421         } else {
14422             *p++ = '-';
14423         }
14424     } else if(move == backwardMostMove) {
14425         // [HGM] perhaps we should always do it like this, and forget the above?
14426         if((signed char)boards[move][EP_STATUS] >= 0) {
14427             *p++ = boards[move][EP_STATUS] + AAA;
14428             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14429         } else {
14430             *p++ = '-';
14431         }
14432     } else {
14433         *p++ = '-';
14434     }
14435     *p++ = ' ';
14436   }
14437   }
14438
14439     /* [HGM] find reversible plies */
14440     {   int i = 0, j=move;
14441
14442         if (appData.debugMode) { int k;
14443             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14444             for(k=backwardMostMove; k<=forwardMostMove; k++)
14445                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14446
14447         }
14448
14449         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14450         if( j == backwardMostMove ) i += initialRulePlies;
14451         sprintf(p, "%d ", i);
14452         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14453     }
14454     /* Fullmove number */
14455     sprintf(p, "%d", (move / 2) + 1);
14456     
14457     return StrSave(buf);
14458 }
14459
14460 Boolean
14461 ParseFEN(board, blackPlaysFirst, fen)
14462     Board board;
14463      int *blackPlaysFirst;
14464      char *fen;
14465 {
14466     int i, j;
14467     char *p;
14468     int emptycount;
14469     ChessSquare piece;
14470
14471     p = fen;
14472
14473     /* [HGM] by default clear Crazyhouse holdings, if present */
14474     if(gameInfo.holdingsWidth) {
14475        for(i=0; i<BOARD_HEIGHT; i++) {
14476            board[i][0]             = EmptySquare; /* black holdings */
14477            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14478            board[i][1]             = (ChessSquare) 0; /* black counts */
14479            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14480        }
14481     }
14482
14483     /* Piece placement data */
14484     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14485         j = 0;
14486         for (;;) {
14487             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14488                 if (*p == '/') p++;
14489                 emptycount = gameInfo.boardWidth - j;
14490                 while (emptycount--)
14491                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14492                 break;
14493 #if(BOARD_FILES >= 10)
14494             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14495                 p++; emptycount=10;
14496                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14497                 while (emptycount--)
14498                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14499 #endif
14500             } else if (isdigit(*p)) {
14501                 emptycount = *p++ - '0';
14502                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14503                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14504                 while (emptycount--)
14505                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14506             } else if (*p == '+' || isalpha(*p)) {
14507                 if (j >= gameInfo.boardWidth) return FALSE;
14508                 if(*p=='+') {
14509                     piece = CharToPiece(*++p);
14510                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14511                     piece = (ChessSquare) (PROMOTED piece ); p++;
14512                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14513                 } else piece = CharToPiece(*p++);
14514
14515                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14516                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14517                     piece = (ChessSquare) (PROMOTED piece);
14518                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14519                     p++;
14520                 }
14521                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14522             } else {
14523                 return FALSE;
14524             }
14525         }
14526     }
14527     while (*p == '/' || *p == ' ') p++;
14528
14529     /* [HGM] look for Crazyhouse holdings here */
14530     while(*p==' ') p++;
14531     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14532         if(*p == '[') p++;
14533         if(*p == '-' ) *p++; /* empty holdings */ else {
14534             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14535             /* if we would allow FEN reading to set board size, we would   */
14536             /* have to add holdings and shift the board read so far here   */
14537             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14538                 *p++;
14539                 if((int) piece >= (int) BlackPawn ) {
14540                     i = (int)piece - (int)BlackPawn;
14541                     i = PieceToNumber((ChessSquare)i);
14542                     if( i >= gameInfo.holdingsSize ) return FALSE;
14543                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14544                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14545                 } else {
14546                     i = (int)piece - (int)WhitePawn;
14547                     i = PieceToNumber((ChessSquare)i);
14548                     if( i >= gameInfo.holdingsSize ) return FALSE;
14549                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14550                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14551                 }
14552             }
14553         }
14554         if(*p == ']') *p++;
14555     }
14556
14557     while(*p == ' ') p++;
14558
14559     /* Active color */
14560     switch (*p++) {
14561       case 'w':
14562         *blackPlaysFirst = FALSE;
14563         break;
14564       case 'b': 
14565         *blackPlaysFirst = TRUE;
14566         break;
14567       default:
14568         return FALSE;
14569     }
14570
14571     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14572     /* return the extra info in global variiables             */
14573
14574     /* set defaults in case FEN is incomplete */
14575     board[EP_STATUS] = EP_UNKNOWN;
14576     for(i=0; i<nrCastlingRights; i++ ) {
14577         board[CASTLING][i] =
14578             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14579     }   /* assume possible unless obviously impossible */
14580     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14581     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14582     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14583                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14584     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14585     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14586     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14587                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14588     FENrulePlies = 0;
14589
14590     while(*p==' ') p++;
14591     if(nrCastlingRights) {
14592       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14593           /* castling indicator present, so default becomes no castlings */
14594           for(i=0; i<nrCastlingRights; i++ ) {
14595                  board[CASTLING][i] = NoRights;
14596           }
14597       }
14598       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14599              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14600              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14601              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14602         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14603
14604         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14605             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14606             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14607         }
14608         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14609             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14610         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14611                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14612         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14613                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14614         switch(c) {
14615           case'K':
14616               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14617               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14618               board[CASTLING][2] = whiteKingFile;
14619               break;
14620           case'Q':
14621               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14622               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14623               board[CASTLING][2] = whiteKingFile;
14624               break;
14625           case'k':
14626               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14627               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14628               board[CASTLING][5] = blackKingFile;
14629               break;
14630           case'q':
14631               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14632               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14633               board[CASTLING][5] = blackKingFile;
14634           case '-':
14635               break;
14636           default: /* FRC castlings */
14637               if(c >= 'a') { /* black rights */
14638                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14639                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14640                   if(i == BOARD_RGHT) break;
14641                   board[CASTLING][5] = i;
14642                   c -= AAA;
14643                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14644                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14645                   if(c > i)
14646                       board[CASTLING][3] = c;
14647                   else
14648                       board[CASTLING][4] = c;
14649               } else { /* white rights */
14650                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14651                     if(board[0][i] == WhiteKing) break;
14652                   if(i == BOARD_RGHT) break;
14653                   board[CASTLING][2] = i;
14654                   c -= AAA - 'a' + 'A';
14655                   if(board[0][c] >= WhiteKing) break;
14656                   if(c > i)
14657                       board[CASTLING][0] = c;
14658                   else
14659                       board[CASTLING][1] = c;
14660               }
14661         }
14662       }
14663       for(i=0; i<nrCastlingRights; i++)
14664         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14665     if (appData.debugMode) {
14666         fprintf(debugFP, "FEN castling rights:");
14667         for(i=0; i<nrCastlingRights; i++)
14668         fprintf(debugFP, " %d", board[CASTLING][i]);
14669         fprintf(debugFP, "\n");
14670     }
14671
14672       while(*p==' ') p++;
14673     }
14674
14675     /* read e.p. field in games that know e.p. capture */
14676     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14677        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14678       if(*p=='-') {
14679         p++; board[EP_STATUS] = EP_NONE;
14680       } else {
14681          char c = *p++ - AAA;
14682
14683          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14684          if(*p >= '0' && *p <='9') *p++;
14685          board[EP_STATUS] = c;
14686       }
14687     }
14688
14689
14690     if(sscanf(p, "%d", &i) == 1) {
14691         FENrulePlies = i; /* 50-move ply counter */
14692         /* (The move number is still ignored)    */
14693     }
14694
14695     return TRUE;
14696 }
14697       
14698 void
14699 EditPositionPasteFEN(char *fen)
14700 {
14701   if (fen != NULL) {
14702     Board initial_position;
14703
14704     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14705       DisplayError(_("Bad FEN position in clipboard"), 0);
14706       return ;
14707     } else {
14708       int savedBlackPlaysFirst = blackPlaysFirst;
14709       EditPositionEvent();
14710       blackPlaysFirst = savedBlackPlaysFirst;
14711       CopyBoard(boards[0], initial_position);
14712       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14713       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14714       DisplayBothClocks();
14715       DrawPosition(FALSE, boards[currentMove]);
14716     }
14717   }
14718 }
14719
14720 static char cseq[12] = "\\   ";
14721
14722 Boolean set_cont_sequence(char *new_seq)
14723 {
14724     int len;
14725     Boolean ret;
14726
14727     // handle bad attempts to set the sequence
14728         if (!new_seq)
14729                 return 0; // acceptable error - no debug
14730
14731     len = strlen(new_seq);
14732     ret = (len > 0) && (len < sizeof(cseq));
14733     if (ret)
14734         strcpy(cseq, new_seq);
14735     else if (appData.debugMode)
14736         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14737     return ret;
14738 }
14739
14740 /*
14741     reformat a source message so words don't cross the width boundary.  internal
14742     newlines are not removed.  returns the wrapped size (no null character unless
14743     included in source message).  If dest is NULL, only calculate the size required
14744     for the dest buffer.  lp argument indicats line position upon entry, and it's
14745     passed back upon exit.
14746 */
14747 int wrap(char *dest, char *src, int count, int width, int *lp)
14748 {
14749     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14750
14751     cseq_len = strlen(cseq);
14752     old_line = line = *lp;
14753     ansi = len = clen = 0;
14754
14755     for (i=0; i < count; i++)
14756     {
14757         if (src[i] == '\033')
14758             ansi = 1;
14759
14760         // if we hit the width, back up
14761         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14762         {
14763             // store i & len in case the word is too long
14764             old_i = i, old_len = len;
14765
14766             // find the end of the last word
14767             while (i && src[i] != ' ' && src[i] != '\n')
14768             {
14769                 i--;
14770                 len--;
14771             }
14772
14773             // word too long?  restore i & len before splitting it
14774             if ((old_i-i+clen) >= width)
14775             {
14776                 i = old_i;
14777                 len = old_len;
14778             }
14779
14780             // extra space?
14781             if (i && src[i-1] == ' ')
14782                 len--;
14783
14784             if (src[i] != ' ' && src[i] != '\n')
14785             {
14786                 i--;
14787                 if (len)
14788                     len--;
14789             }
14790
14791             // now append the newline and continuation sequence
14792             if (dest)
14793                 dest[len] = '\n';
14794             len++;
14795             if (dest)
14796                 strncpy(dest+len, cseq, cseq_len);
14797             len += cseq_len;
14798             line = cseq_len;
14799             clen = cseq_len;
14800             continue;
14801         }
14802
14803         if (dest)
14804             dest[len] = src[i];
14805         len++;
14806         if (!ansi)
14807             line++;
14808         if (src[i] == '\n')
14809             line = 0;
14810         if (src[i] == 'm')
14811             ansi = 0;
14812     }
14813     if (dest && appData.debugMode)
14814     {
14815         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14816             count, width, line, len, *lp);
14817         show_bytes(debugFP, src, count);
14818         fprintf(debugFP, "\ndest: ");
14819         show_bytes(debugFP, dest, len);
14820         fprintf(debugFP, "\n");
14821     }
14822     *lp = dest ? line : old_line;
14823
14824     return len;
14825 }
14826
14827 // [HGM] vari: routines for shelving variations
14828
14829 void 
14830 PushTail(int firstMove, int lastMove)
14831 {
14832         int i, j, nrMoves = lastMove - firstMove;
14833
14834         if(appData.icsActive) { // only in local mode
14835                 forwardMostMove = currentMove; // mimic old ICS behavior
14836                 return;
14837         }
14838         if(storedGames >= MAX_VARIATIONS-1) return;
14839
14840         // push current tail of game on stack
14841         savedResult[storedGames] = gameInfo.result;
14842         savedDetails[storedGames] = gameInfo.resultDetails;
14843         gameInfo.resultDetails = NULL;
14844         savedFirst[storedGames] = firstMove;
14845         savedLast [storedGames] = lastMove;
14846         savedFramePtr[storedGames] = framePtr;
14847         framePtr -= nrMoves; // reserve space for the boards
14848         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14849             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14850             for(j=0; j<MOVE_LEN; j++)
14851                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14852             for(j=0; j<2*MOVE_LEN; j++)
14853                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14854             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14855             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14856             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14857             pvInfoList[firstMove+i-1].depth = 0;
14858             commentList[framePtr+i] = commentList[firstMove+i];
14859             commentList[firstMove+i] = NULL;
14860         }
14861
14862         storedGames++;
14863         forwardMostMove = currentMove; // truncte game so we can start variation
14864         if(storedGames == 1) GreyRevert(FALSE);
14865 }
14866
14867 Boolean
14868 PopTail(Boolean annotate)
14869 {
14870         int i, j, nrMoves;
14871         char buf[8000], moveBuf[20];
14872
14873         if(appData.icsActive) return FALSE; // only in local mode
14874         if(!storedGames) return FALSE; // sanity
14875
14876         storedGames--;
14877         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14878         nrMoves = savedLast[storedGames] - currentMove;
14879         if(annotate) {
14880                 int cnt = 10;
14881                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14882                 else strcpy(buf, "(");
14883                 for(i=currentMove; i<forwardMostMove; i++) {
14884                         if(WhiteOnMove(i))
14885                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14886                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14887                         strcat(buf, moveBuf);
14888                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14889                 }
14890                 strcat(buf, ")");
14891         }
14892         for(i=1; i<nrMoves; i++) { // copy last variation back
14893             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14894             for(j=0; j<MOVE_LEN; j++)
14895                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14896             for(j=0; j<2*MOVE_LEN; j++)
14897                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14898             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14899             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14900             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14901             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14902             commentList[currentMove+i] = commentList[framePtr+i];
14903             commentList[framePtr+i] = NULL;
14904         }
14905         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14906         framePtr = savedFramePtr[storedGames];
14907         gameInfo.result = savedResult[storedGames];
14908         if(gameInfo.resultDetails != NULL) {
14909             free(gameInfo.resultDetails);
14910       }
14911         gameInfo.resultDetails = savedDetails[storedGames];
14912         forwardMostMove = currentMove + nrMoves;
14913         if(storedGames == 0) GreyRevert(TRUE);
14914         return TRUE;
14915 }
14916
14917 void 
14918 CleanupTail()
14919 {       // remove all shelved variations
14920         int i;
14921         for(i=0; i<storedGames; i++) {
14922             if(savedDetails[i])
14923                 free(savedDetails[i]);
14924             savedDetails[i] = NULL;
14925         }
14926         for(i=framePtr; i<MAX_MOVES; i++) {
14927                 if(commentList[i]) free(commentList[i]);
14928                 commentList[i] = NULL;
14929         }
14930         framePtr = MAX_MOVES-1;
14931         storedGames = 0;
14932 }