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