Add promotions and e.p. to oneClickMove
[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     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8721         /* If we are playing on ICS, the server decides when the
8722            game is over, but the engine can offer to draw, claim 
8723            a draw, or resign. 
8724          */
8725 #if ZIPPY
8726         if (appData.zippyPlay && first.initDone) {
8727             if (result == GameIsDrawn) {
8728                 /* In case draw still needs to be claimed */
8729                 SendToICS(ics_prefix);
8730                 SendToICS("draw\n");
8731             } else if (StrCaseStr(resultDetails, "resign")) {
8732                 SendToICS(ics_prefix);
8733                 SendToICS("resign\n");
8734             }
8735         }
8736 #endif
8737         endingGame = 0; /* [HGM] crash */
8738         return;
8739     }
8740
8741     /* If we're loading the game from a file, stop */
8742     if (whosays == GE_FILE) {
8743       (void) StopLoadGameTimer();
8744       gameFileFP = NULL;
8745     }
8746
8747     /* Cancel draw offers */
8748     first.offeredDraw = second.offeredDraw = 0;
8749
8750     /* If this is an ICS game, only ICS can really say it's done;
8751        if not, anyone can. */
8752     isIcsGame = (gameMode == IcsPlayingWhite || 
8753                  gameMode == IcsPlayingBlack || 
8754                  gameMode == IcsObserving    || 
8755                  gameMode == IcsExamining);
8756
8757     if (!isIcsGame || whosays == GE_ICS) {
8758         /* OK -- not an ICS game, or ICS said it was done */
8759         StopClocks();
8760         if (!isIcsGame && !appData.noChessProgram) 
8761           SetUserThinkingEnables();
8762     
8763         /* [HGM] if a machine claims the game end we verify this claim */
8764         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8765             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8766                 char claimer;
8767                 ChessMove trueResult = (ChessMove) -1;
8768
8769                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8770                                             first.twoMachinesColor[0] :
8771                                             second.twoMachinesColor[0] ;
8772
8773                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8774                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8775                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8776                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8777                 } else
8778                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8779                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8780                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8781                 } else
8782                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8783                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8784                 }
8785
8786                 // now verify win claims, but not in drop games, as we don't understand those yet
8787                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8788                                                  || gameInfo.variant == VariantGreat) &&
8789                     (result == WhiteWins && claimer == 'w' ||
8790                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8791                       if (appData.debugMode) {
8792                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8793                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8794                       }
8795                       if(result != trueResult) {
8796                               sprintf(buf, "False win claim: '%s'", resultDetails);
8797                               result = claimer == 'w' ? BlackWins : WhiteWins;
8798                               resultDetails = buf;
8799                       }
8800                 } else
8801                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8802                     && (forwardMostMove <= backwardMostMove ||
8803                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8804                         (claimer=='b')==(forwardMostMove&1))
8805                                                                                   ) {
8806                       /* [HGM] verify: draws that were not flagged are false claims */
8807                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8808                       result = claimer == 'w' ? BlackWins : WhiteWins;
8809                       resultDetails = buf;
8810                 }
8811                 /* (Claiming a loss is accepted no questions asked!) */
8812             }
8813             /* [HGM] bare: don't allow bare King to win */
8814             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8815                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8816                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8817                && result != GameIsDrawn)
8818             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8819                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8820                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8821                         if(p >= 0 && p <= (int)WhiteKing) k++;
8822                 }
8823                 if (appData.debugMode) {
8824                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8825                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8826                 }
8827                 if(k <= 1) {
8828                         result = GameIsDrawn;
8829                         sprintf(buf, "%s but bare king", resultDetails);
8830                         resultDetails = buf;
8831                 }
8832             }
8833         }
8834
8835
8836         if(serverMoves != NULL && !loadFlag) { char c = '=';
8837             if(result==WhiteWins) c = '+';
8838             if(result==BlackWins) c = '-';
8839             if(resultDetails != NULL)
8840                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8841         }
8842         if (resultDetails != NULL) {
8843             gameInfo.result = result;
8844             gameInfo.resultDetails = StrSave(resultDetails);
8845
8846             /* display last move only if game was not loaded from file */
8847             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8848                 DisplayMove(currentMove - 1);
8849     
8850             if (forwardMostMove != 0) {
8851                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8852                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8853                                                                 ) {
8854                     if (*appData.saveGameFile != NULLCHAR) {
8855                         SaveGameToFile(appData.saveGameFile, TRUE);
8856                     } else if (appData.autoSaveGames) {
8857                         AutoSaveGame();
8858                     }
8859                     if (*appData.savePositionFile != NULLCHAR) {
8860                         SavePositionToFile(appData.savePositionFile);
8861                     }
8862                 }
8863             }
8864
8865             /* Tell program how game ended in case it is learning */
8866             /* [HGM] Moved this to after saving the PGN, just in case */
8867             /* engine died and we got here through time loss. In that */
8868             /* case we will get a fatal error writing the pipe, which */
8869             /* would otherwise lose us the PGN.                       */
8870             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8871             /* output during GameEnds should never be fatal anymore   */
8872             if (gameMode == MachinePlaysWhite ||
8873                 gameMode == MachinePlaysBlack ||
8874                 gameMode == TwoMachinesPlay ||
8875                 gameMode == IcsPlayingWhite ||
8876                 gameMode == IcsPlayingBlack ||
8877                 gameMode == BeginningOfGame) {
8878                 char buf[MSG_SIZ];
8879                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8880                         resultDetails);
8881                 if (first.pr != NoProc) {
8882                     SendToProgram(buf, &first);
8883                 }
8884                 if (second.pr != NoProc &&
8885                     gameMode == TwoMachinesPlay) {
8886                     SendToProgram(buf, &second);
8887                 }
8888             }
8889         }
8890
8891         if (appData.icsActive) {
8892             if (appData.quietPlay &&
8893                 (gameMode == IcsPlayingWhite ||
8894                  gameMode == IcsPlayingBlack)) {
8895                 SendToICS(ics_prefix);
8896                 SendToICS("set shout 1\n");
8897             }
8898             nextGameMode = IcsIdle;
8899             ics_user_moved = FALSE;
8900             /* clean up premove.  It's ugly when the game has ended and the
8901              * premove highlights are still on the board.
8902              */
8903             if (gotPremove) {
8904               gotPremove = FALSE;
8905               ClearPremoveHighlights();
8906               DrawPosition(FALSE, boards[currentMove]);
8907             }
8908             if (whosays == GE_ICS) {
8909                 switch (result) {
8910                 case WhiteWins:
8911                     if (gameMode == IcsPlayingWhite)
8912                         PlayIcsWinSound();
8913                     else if(gameMode == IcsPlayingBlack)
8914                         PlayIcsLossSound();
8915                     break;
8916                 case BlackWins:
8917                     if (gameMode == IcsPlayingBlack)
8918                         PlayIcsWinSound();
8919                     else if(gameMode == IcsPlayingWhite)
8920                         PlayIcsLossSound();
8921                     break;
8922                 case GameIsDrawn:
8923                     PlayIcsDrawSound();
8924                     break;
8925                 default:
8926                     PlayIcsUnfinishedSound();
8927                 }
8928             }
8929         } else if (gameMode == EditGame ||
8930                    gameMode == PlayFromGameFile || 
8931                    gameMode == AnalyzeMode || 
8932                    gameMode == AnalyzeFile) {
8933             nextGameMode = gameMode;
8934         } else {
8935             nextGameMode = EndOfGame;
8936         }
8937         pausing = FALSE;
8938         ModeHighlight();
8939     } else {
8940         nextGameMode = gameMode;
8941     }
8942
8943     if (appData.noChessProgram) {
8944         gameMode = nextGameMode;
8945         ModeHighlight();
8946         endingGame = 0; /* [HGM] crash */
8947         return;
8948     }
8949
8950     if (first.reuse) {
8951         /* Put first chess program into idle state */
8952         if (first.pr != NoProc &&
8953             (gameMode == MachinePlaysWhite ||
8954              gameMode == MachinePlaysBlack ||
8955              gameMode == TwoMachinesPlay ||
8956              gameMode == IcsPlayingWhite ||
8957              gameMode == IcsPlayingBlack ||
8958              gameMode == BeginningOfGame)) {
8959             SendToProgram("force\n", &first);
8960             if (first.usePing) {
8961               char buf[MSG_SIZ];
8962               sprintf(buf, "ping %d\n", ++first.lastPing);
8963               SendToProgram(buf, &first);
8964             }
8965         }
8966     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8967         /* Kill off first chess program */
8968         if (first.isr != NULL)
8969           RemoveInputSource(first.isr);
8970         first.isr = NULL;
8971     
8972         if (first.pr != NoProc) {
8973             ExitAnalyzeMode();
8974             DoSleep( appData.delayBeforeQuit );
8975             SendToProgram("quit\n", &first);
8976             DoSleep( appData.delayAfterQuit );
8977             DestroyChildProcess(first.pr, first.useSigterm);
8978         }
8979         first.pr = NoProc;
8980     }
8981     if (second.reuse) {
8982         /* Put second chess program into idle state */
8983         if (second.pr != NoProc &&
8984             gameMode == TwoMachinesPlay) {
8985             SendToProgram("force\n", &second);
8986             if (second.usePing) {
8987               char buf[MSG_SIZ];
8988               sprintf(buf, "ping %d\n", ++second.lastPing);
8989               SendToProgram(buf, &second);
8990             }
8991         }
8992     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8993         /* Kill off second chess program */
8994         if (second.isr != NULL)
8995           RemoveInputSource(second.isr);
8996         second.isr = NULL;
8997     
8998         if (second.pr != NoProc) {
8999             DoSleep( appData.delayBeforeQuit );
9000             SendToProgram("quit\n", &second);
9001             DoSleep( appData.delayAfterQuit );
9002             DestroyChildProcess(second.pr, second.useSigterm);
9003         }
9004         second.pr = NoProc;
9005     }
9006
9007     if (matchMode && gameMode == TwoMachinesPlay) {
9008         switch (result) {
9009         case WhiteWins:
9010           if (first.twoMachinesColor[0] == 'w') {
9011             first.matchWins++;
9012           } else {
9013             second.matchWins++;
9014           }
9015           break;
9016         case BlackWins:
9017           if (first.twoMachinesColor[0] == 'b') {
9018             first.matchWins++;
9019           } else {
9020             second.matchWins++;
9021           }
9022           break;
9023         default:
9024           break;
9025         }
9026         if (matchGame < appData.matchGames) {
9027             char *tmp;
9028             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9029                 tmp = first.twoMachinesColor;
9030                 first.twoMachinesColor = second.twoMachinesColor;
9031                 second.twoMachinesColor = tmp;
9032             }
9033             gameMode = nextGameMode;
9034             matchGame++;
9035             if(appData.matchPause>10000 || appData.matchPause<10)
9036                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9037             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9038             endingGame = 0; /* [HGM] crash */
9039             return;
9040         } else {
9041             char buf[MSG_SIZ];
9042             gameMode = nextGameMode;
9043             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9044                     first.tidy, second.tidy,
9045                     first.matchWins, second.matchWins,
9046                     appData.matchGames - (first.matchWins + second.matchWins));
9047             DisplayFatalError(buf, 0, 0);
9048         }
9049     }
9050     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9051         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9052       ExitAnalyzeMode();
9053     gameMode = nextGameMode;
9054     ModeHighlight();
9055     endingGame = 0;  /* [HGM] crash */
9056 }
9057
9058 /* Assumes program was just initialized (initString sent).
9059    Leaves program in force mode. */
9060 void
9061 FeedMovesToProgram(cps, upto) 
9062      ChessProgramState *cps;
9063      int upto;
9064 {
9065     int i;
9066     
9067     if (appData.debugMode)
9068       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9069               startedFromSetupPosition ? "position and " : "",
9070               backwardMostMove, upto, cps->which);
9071     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9072         // [HGM] variantswitch: make engine aware of new variant
9073         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9074                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9075         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9076         SendToProgram(buf, cps);
9077         currentlyInitializedVariant = gameInfo.variant;
9078     }
9079     SendToProgram("force\n", cps);
9080     if (startedFromSetupPosition) {
9081         SendBoard(cps, backwardMostMove);
9082     if (appData.debugMode) {
9083         fprintf(debugFP, "feedMoves\n");
9084     }
9085     }
9086     for (i = backwardMostMove; i < upto; i++) {
9087         SendMoveToProgram(i, cps);
9088     }
9089 }
9090
9091
9092 void
9093 ResurrectChessProgram()
9094 {
9095      /* The chess program may have exited.
9096         If so, restart it and feed it all the moves made so far. */
9097
9098     if (appData.noChessProgram || first.pr != NoProc) return;
9099     
9100     StartChessProgram(&first);
9101     InitChessProgram(&first, FALSE);
9102     FeedMovesToProgram(&first, currentMove);
9103
9104     if (!first.sendTime) {
9105         /* can't tell gnuchess what its clock should read,
9106            so we bow to its notion. */
9107         ResetClocks();
9108         timeRemaining[0][currentMove] = whiteTimeRemaining;
9109         timeRemaining[1][currentMove] = blackTimeRemaining;
9110     }
9111
9112     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9113                 appData.icsEngineAnalyze) && first.analysisSupport) {
9114       SendToProgram("analyze\n", &first);
9115       first.analyzing = TRUE;
9116     }
9117 }
9118
9119 /*
9120  * Button procedures
9121  */
9122 void
9123 Reset(redraw, init)
9124      int redraw, init;
9125 {
9126     int i;
9127
9128     if (appData.debugMode) {
9129         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9130                 redraw, init, gameMode);
9131     }
9132     CleanupTail(); // [HGM] vari: delete any stored variations
9133     pausing = pauseExamInvalid = FALSE;
9134     startedFromSetupPosition = blackPlaysFirst = FALSE;
9135     firstMove = TRUE;
9136     whiteFlag = blackFlag = FALSE;
9137     userOfferedDraw = FALSE;
9138     hintRequested = bookRequested = FALSE;
9139     first.maybeThinking = FALSE;
9140     second.maybeThinking = FALSE;
9141     first.bookSuspend = FALSE; // [HGM] book
9142     second.bookSuspend = FALSE;
9143     thinkOutput[0] = NULLCHAR;
9144     lastHint[0] = NULLCHAR;
9145     ClearGameInfo(&gameInfo);
9146     gameInfo.variant = StringToVariant(appData.variant);
9147     ics_user_moved = ics_clock_paused = FALSE;
9148     ics_getting_history = H_FALSE;
9149     ics_gamenum = -1;
9150     white_holding[0] = black_holding[0] = NULLCHAR;
9151     ClearProgramStats();
9152     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9153     
9154     ResetFrontEnd();
9155     ClearHighlights();
9156     flipView = appData.flipView;
9157     ClearPremoveHighlights();
9158     gotPremove = FALSE;
9159     alarmSounded = FALSE;
9160
9161     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9162     if(appData.serverMovesName != NULL) {
9163         /* [HGM] prepare to make moves file for broadcasting */
9164         clock_t t = clock();
9165         if(serverMoves != NULL) fclose(serverMoves);
9166         serverMoves = fopen(appData.serverMovesName, "r");
9167         if(serverMoves != NULL) {
9168             fclose(serverMoves);
9169             /* delay 15 sec before overwriting, so all clients can see end */
9170             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9171         }
9172         serverMoves = fopen(appData.serverMovesName, "w");
9173     }
9174
9175     ExitAnalyzeMode();
9176     gameMode = BeginningOfGame;
9177     ModeHighlight();
9178     if(appData.icsActive) gameInfo.variant = VariantNormal;
9179     currentMove = forwardMostMove = backwardMostMove = 0;
9180     InitPosition(redraw);
9181     for (i = 0; i < MAX_MOVES; i++) {
9182         if (commentList[i] != NULL) {
9183             free(commentList[i]);
9184             commentList[i] = NULL;
9185         }
9186     }
9187     ResetClocks();
9188     timeRemaining[0][0] = whiteTimeRemaining;
9189     timeRemaining[1][0] = blackTimeRemaining;
9190     if (first.pr == NULL) {
9191         StartChessProgram(&first);
9192     }
9193     if (init) {
9194             InitChessProgram(&first, startedFromSetupPosition);
9195     }
9196     DisplayTitle("");
9197     DisplayMessage("", "");
9198     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9199     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9200 }
9201
9202 void
9203 AutoPlayGameLoop()
9204 {
9205     for (;;) {
9206         if (!AutoPlayOneMove())
9207           return;
9208         if (matchMode || appData.timeDelay == 0)
9209           continue;
9210         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9211           return;
9212         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9213         break;
9214     }
9215 }
9216
9217
9218 int
9219 AutoPlayOneMove()
9220 {
9221     int fromX, fromY, toX, toY;
9222
9223     if (appData.debugMode) {
9224       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9225     }
9226
9227     if (gameMode != PlayFromGameFile)
9228       return FALSE;
9229
9230     if (currentMove >= forwardMostMove) {
9231       gameMode = EditGame;
9232       ModeHighlight();
9233
9234       /* [AS] Clear current move marker at the end of a game */
9235       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9236
9237       return FALSE;
9238     }
9239     
9240     toX = moveList[currentMove][2] - AAA;
9241     toY = moveList[currentMove][3] - ONE;
9242
9243     if (moveList[currentMove][1] == '@') {
9244         if (appData.highlightLastMove) {
9245             SetHighlights(-1, -1, toX, toY);
9246         }
9247     } else {
9248         fromX = moveList[currentMove][0] - AAA;
9249         fromY = moveList[currentMove][1] - ONE;
9250
9251         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9252
9253         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9254
9255         if (appData.highlightLastMove) {
9256             SetHighlights(fromX, fromY, toX, toY);
9257         }
9258     }
9259     DisplayMove(currentMove);
9260     SendMoveToProgram(currentMove++, &first);
9261     DisplayBothClocks();
9262     DrawPosition(FALSE, boards[currentMove]);
9263     // [HGM] PV info: always display, routine tests if empty
9264     DisplayComment(currentMove - 1, commentList[currentMove]);
9265     return TRUE;
9266 }
9267
9268
9269 int
9270 LoadGameOneMove(readAhead)
9271      ChessMove readAhead;
9272 {
9273     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9274     char promoChar = NULLCHAR;
9275     ChessMove moveType;
9276     char move[MSG_SIZ];
9277     char *p, *q;
9278     
9279     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9280         gameMode != AnalyzeMode && gameMode != Training) {
9281         gameFileFP = NULL;
9282         return FALSE;
9283     }
9284     
9285     yyboardindex = forwardMostMove;
9286     if (readAhead != (ChessMove)0) {
9287       moveType = readAhead;
9288     } else {
9289       if (gameFileFP == NULL)
9290           return FALSE;
9291       moveType = (ChessMove) yylex();
9292     }
9293     
9294     done = FALSE;
9295     switch (moveType) {
9296       case Comment:
9297         if (appData.debugMode) 
9298           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9299         p = yy_text;
9300
9301         /* append the comment but don't display it */
9302         AppendComment(currentMove, p, FALSE);
9303         return TRUE;
9304
9305       case WhiteCapturesEnPassant:
9306       case BlackCapturesEnPassant:
9307       case WhitePromotionChancellor:
9308       case BlackPromotionChancellor:
9309       case WhitePromotionArchbishop:
9310       case BlackPromotionArchbishop:
9311       case WhitePromotionCentaur:
9312       case BlackPromotionCentaur:
9313       case WhitePromotionQueen:
9314       case BlackPromotionQueen:
9315       case WhitePromotionRook:
9316       case BlackPromotionRook:
9317       case WhitePromotionBishop:
9318       case BlackPromotionBishop:
9319       case WhitePromotionKnight:
9320       case BlackPromotionKnight:
9321       case WhitePromotionKing:
9322       case BlackPromotionKing:
9323       case NormalMove:
9324       case WhiteKingSideCastle:
9325       case WhiteQueenSideCastle:
9326       case BlackKingSideCastle:
9327       case BlackQueenSideCastle:
9328       case WhiteKingSideCastleWild:
9329       case WhiteQueenSideCastleWild:
9330       case BlackKingSideCastleWild:
9331       case BlackQueenSideCastleWild:
9332       /* PUSH Fabien */
9333       case WhiteHSideCastleFR:
9334       case WhiteASideCastleFR:
9335       case BlackHSideCastleFR:
9336       case BlackASideCastleFR:
9337       /* POP Fabien */
9338         if (appData.debugMode)
9339           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9340         fromX = currentMoveString[0] - AAA;
9341         fromY = currentMoveString[1] - ONE;
9342         toX = currentMoveString[2] - AAA;
9343         toY = currentMoveString[3] - ONE;
9344         promoChar = currentMoveString[4];
9345         break;
9346
9347       case WhiteDrop:
9348       case BlackDrop:
9349         if (appData.debugMode)
9350           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9351         fromX = moveType == WhiteDrop ?
9352           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9353         (int) CharToPiece(ToLower(currentMoveString[0]));
9354         fromY = DROP_RANK;
9355         toX = currentMoveString[2] - AAA;
9356         toY = currentMoveString[3] - ONE;
9357         break;
9358
9359       case WhiteWins:
9360       case BlackWins:
9361       case GameIsDrawn:
9362       case GameUnfinished:
9363         if (appData.debugMode)
9364           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9365         p = strchr(yy_text, '{');
9366         if (p == NULL) p = strchr(yy_text, '(');
9367         if (p == NULL) {
9368             p = yy_text;
9369             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9370         } else {
9371             q = strchr(p, *p == '{' ? '}' : ')');
9372             if (q != NULL) *q = NULLCHAR;
9373             p++;
9374         }
9375         GameEnds(moveType, p, GE_FILE);
9376         done = TRUE;
9377         if (cmailMsgLoaded) {
9378             ClearHighlights();
9379             flipView = WhiteOnMove(currentMove);
9380             if (moveType == GameUnfinished) flipView = !flipView;
9381             if (appData.debugMode)
9382               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9383         }
9384         break;
9385
9386       case (ChessMove) 0:       /* end of file */
9387         if (appData.debugMode)
9388           fprintf(debugFP, "Parser hit end of file\n");
9389         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9390           case MT_NONE:
9391           case MT_CHECK:
9392             break;
9393           case MT_CHECKMATE:
9394           case MT_STAINMATE:
9395             if (WhiteOnMove(currentMove)) {
9396                 GameEnds(BlackWins, "Black mates", GE_FILE);
9397             } else {
9398                 GameEnds(WhiteWins, "White mates", GE_FILE);
9399             }
9400             break;
9401           case MT_STALEMATE:
9402             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9403             break;
9404         }
9405         done = TRUE;
9406         break;
9407
9408       case MoveNumberOne:
9409         if (lastLoadGameStart == GNUChessGame) {
9410             /* GNUChessGames have numbers, but they aren't move numbers */
9411             if (appData.debugMode)
9412               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9413                       yy_text, (int) moveType);
9414             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9415         }
9416         /* else fall thru */
9417
9418       case XBoardGame:
9419       case GNUChessGame:
9420       case PGNTag:
9421         /* Reached start of next game in file */
9422         if (appData.debugMode)
9423           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9424         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9425           case MT_NONE:
9426           case MT_CHECK:
9427             break;
9428           case MT_CHECKMATE:
9429           case MT_STAINMATE:
9430             if (WhiteOnMove(currentMove)) {
9431                 GameEnds(BlackWins, "Black mates", GE_FILE);
9432             } else {
9433                 GameEnds(WhiteWins, "White mates", GE_FILE);
9434             }
9435             break;
9436           case MT_STALEMATE:
9437             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9438             break;
9439         }
9440         done = TRUE;
9441         break;
9442
9443       case PositionDiagram:     /* should not happen; ignore */
9444       case ElapsedTime:         /* ignore */
9445       case NAG:                 /* ignore */
9446         if (appData.debugMode)
9447           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9448                   yy_text, (int) moveType);
9449         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9450
9451       case IllegalMove:
9452         if (appData.testLegality) {
9453             if (appData.debugMode)
9454               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9455             sprintf(move, _("Illegal move: %d.%s%s"),
9456                     (forwardMostMove / 2) + 1,
9457                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9458             DisplayError(move, 0);
9459             done = TRUE;
9460         } else {
9461             if (appData.debugMode)
9462               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9463                       yy_text, currentMoveString);
9464             fromX = currentMoveString[0] - AAA;
9465             fromY = currentMoveString[1] - ONE;
9466             toX = currentMoveString[2] - AAA;
9467             toY = currentMoveString[3] - ONE;
9468             promoChar = currentMoveString[4];
9469         }
9470         break;
9471
9472       case AmbiguousMove:
9473         if (appData.debugMode)
9474           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9475         sprintf(move, _("Ambiguous move: %d.%s%s"),
9476                 (forwardMostMove / 2) + 1,
9477                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9478         DisplayError(move, 0);
9479         done = TRUE;
9480         break;
9481
9482       default:
9483       case ImpossibleMove:
9484         if (appData.debugMode)
9485           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9486         sprintf(move, _("Illegal move: %d.%s%s"),
9487                 (forwardMostMove / 2) + 1,
9488                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9489         DisplayError(move, 0);
9490         done = TRUE;
9491         break;
9492     }
9493
9494     if (done) {
9495         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9496             DrawPosition(FALSE, boards[currentMove]);
9497             DisplayBothClocks();
9498             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9499               DisplayComment(currentMove - 1, commentList[currentMove]);
9500         }
9501         (void) StopLoadGameTimer();
9502         gameFileFP = NULL;
9503         cmailOldMove = forwardMostMove;
9504         return FALSE;
9505     } else {
9506         /* currentMoveString is set as a side-effect of yylex */
9507         strcat(currentMoveString, "\n");
9508         strcpy(moveList[forwardMostMove], currentMoveString);
9509         
9510         thinkOutput[0] = NULLCHAR;
9511         MakeMove(fromX, fromY, toX, toY, promoChar);
9512         currentMove = forwardMostMove;
9513         return TRUE;
9514     }
9515 }
9516
9517 /* Load the nth game from the given file */
9518 int
9519 LoadGameFromFile(filename, n, title, useList)
9520      char *filename;
9521      int n;
9522      char *title;
9523      /*Boolean*/ int useList;
9524 {
9525     FILE *f;
9526     char buf[MSG_SIZ];
9527
9528     if (strcmp(filename, "-") == 0) {
9529         f = stdin;
9530         title = "stdin";
9531     } else {
9532         f = fopen(filename, "rb");
9533         if (f == NULL) {
9534           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9535             DisplayError(buf, errno);
9536             return FALSE;
9537         }
9538     }
9539     if (fseek(f, 0, 0) == -1) {
9540         /* f is not seekable; probably a pipe */
9541         useList = FALSE;
9542     }
9543     if (useList && n == 0) {
9544         int error = GameListBuild(f);
9545         if (error) {
9546             DisplayError(_("Cannot build game list"), error);
9547         } else if (!ListEmpty(&gameList) &&
9548                    ((ListGame *) gameList.tailPred)->number > 1) {
9549             GameListPopUp(f, title);
9550             return TRUE;
9551         }
9552         GameListDestroy();
9553         n = 1;
9554     }
9555     if (n == 0) n = 1;
9556     return LoadGame(f, n, title, FALSE);
9557 }
9558
9559
9560 void
9561 MakeRegisteredMove()
9562 {
9563     int fromX, fromY, toX, toY;
9564     char promoChar;
9565     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9566         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9567           case CMAIL_MOVE:
9568           case CMAIL_DRAW:
9569             if (appData.debugMode)
9570               fprintf(debugFP, "Restoring %s for game %d\n",
9571                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9572     
9573             thinkOutput[0] = NULLCHAR;
9574             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9575             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9576             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9577             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9578             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9579             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9580             MakeMove(fromX, fromY, toX, toY, promoChar);
9581             ShowMove(fromX, fromY, toX, toY);
9582               
9583             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9584               case MT_NONE:
9585               case MT_CHECK:
9586                 break;
9587                 
9588               case MT_CHECKMATE:
9589               case MT_STAINMATE:
9590                 if (WhiteOnMove(currentMove)) {
9591                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9592                 } else {
9593                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9594                 }
9595                 break;
9596                 
9597               case MT_STALEMATE:
9598                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9599                 break;
9600             }
9601
9602             break;
9603             
9604           case CMAIL_RESIGN:
9605             if (WhiteOnMove(currentMove)) {
9606                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9607             } else {
9608                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9609             }
9610             break;
9611             
9612           case CMAIL_ACCEPT:
9613             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9614             break;
9615               
9616           default:
9617             break;
9618         }
9619     }
9620
9621     return;
9622 }
9623
9624 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9625 int
9626 CmailLoadGame(f, gameNumber, title, useList)
9627      FILE *f;
9628      int gameNumber;
9629      char *title;
9630      int useList;
9631 {
9632     int retVal;
9633
9634     if (gameNumber > nCmailGames) {
9635         DisplayError(_("No more games in this message"), 0);
9636         return FALSE;
9637     }
9638     if (f == lastLoadGameFP) {
9639         int offset = gameNumber - lastLoadGameNumber;
9640         if (offset == 0) {
9641             cmailMsg[0] = NULLCHAR;
9642             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9643                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9644                 nCmailMovesRegistered--;
9645             }
9646             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9647             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9648                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9649             }
9650         } else {
9651             if (! RegisterMove()) return FALSE;
9652         }
9653     }
9654
9655     retVal = LoadGame(f, gameNumber, title, useList);
9656
9657     /* Make move registered during previous look at this game, if any */
9658     MakeRegisteredMove();
9659
9660     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9661         commentList[currentMove]
9662           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9663         DisplayComment(currentMove - 1, commentList[currentMove]);
9664     }
9665
9666     return retVal;
9667 }
9668
9669 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9670 int
9671 ReloadGame(offset)
9672      int offset;
9673 {
9674     int gameNumber = lastLoadGameNumber + offset;
9675     if (lastLoadGameFP == NULL) {
9676         DisplayError(_("No game has been loaded yet"), 0);
9677         return FALSE;
9678     }
9679     if (gameNumber <= 0) {
9680         DisplayError(_("Can't back up any further"), 0);
9681         return FALSE;
9682     }
9683     if (cmailMsgLoaded) {
9684         return CmailLoadGame(lastLoadGameFP, gameNumber,
9685                              lastLoadGameTitle, lastLoadGameUseList);
9686     } else {
9687         return LoadGame(lastLoadGameFP, gameNumber,
9688                         lastLoadGameTitle, lastLoadGameUseList);
9689     }
9690 }
9691
9692
9693
9694 /* Load the nth game from open file f */
9695 int
9696 LoadGame(f, gameNumber, title, useList)
9697      FILE *f;
9698      int gameNumber;
9699      char *title;
9700      int useList;
9701 {
9702     ChessMove cm;
9703     char buf[MSG_SIZ];
9704     int gn = gameNumber;
9705     ListGame *lg = NULL;
9706     int numPGNTags = 0;
9707     int err;
9708     GameMode oldGameMode;
9709     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9710
9711     if (appData.debugMode) 
9712         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9713
9714     if (gameMode == Training )
9715         SetTrainingModeOff();
9716
9717     oldGameMode = gameMode;
9718     if (gameMode != BeginningOfGame) {
9719       Reset(FALSE, TRUE);
9720     }
9721
9722     gameFileFP = f;
9723     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9724         fclose(lastLoadGameFP);
9725     }
9726
9727     if (useList) {
9728         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9729         
9730         if (lg) {
9731             fseek(f, lg->offset, 0);
9732             GameListHighlight(gameNumber);
9733             gn = 1;
9734         }
9735         else {
9736             DisplayError(_("Game number out of range"), 0);
9737             return FALSE;
9738         }
9739     } else {
9740         GameListDestroy();
9741         if (fseek(f, 0, 0) == -1) {
9742             if (f == lastLoadGameFP ?
9743                 gameNumber == lastLoadGameNumber + 1 :
9744                 gameNumber == 1) {
9745                 gn = 1;
9746             } else {
9747                 DisplayError(_("Can't seek on game file"), 0);
9748                 return FALSE;
9749             }
9750         }
9751     }
9752     lastLoadGameFP = f;
9753     lastLoadGameNumber = gameNumber;
9754     strcpy(lastLoadGameTitle, title);
9755     lastLoadGameUseList = useList;
9756
9757     yynewfile(f);
9758
9759     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9760       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9761                 lg->gameInfo.black);
9762             DisplayTitle(buf);
9763     } else if (*title != NULLCHAR) {
9764         if (gameNumber > 1) {
9765             sprintf(buf, "%s %d", title, gameNumber);
9766             DisplayTitle(buf);
9767         } else {
9768             DisplayTitle(title);
9769         }
9770     }
9771
9772     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9773         gameMode = PlayFromGameFile;
9774         ModeHighlight();
9775     }
9776
9777     currentMove = forwardMostMove = backwardMostMove = 0;
9778     CopyBoard(boards[0], initialPosition);
9779     StopClocks();
9780
9781     /*
9782      * Skip the first gn-1 games in the file.
9783      * Also skip over anything that precedes an identifiable 
9784      * start of game marker, to avoid being confused by 
9785      * garbage at the start of the file.  Currently 
9786      * recognized start of game markers are the move number "1",
9787      * the pattern "gnuchess .* game", the pattern
9788      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9789      * A game that starts with one of the latter two patterns
9790      * will also have a move number 1, possibly
9791      * following a position diagram.
9792      * 5-4-02: Let's try being more lenient and allowing a game to
9793      * start with an unnumbered move.  Does that break anything?
9794      */
9795     cm = lastLoadGameStart = (ChessMove) 0;
9796     while (gn > 0) {
9797         yyboardindex = forwardMostMove;
9798         cm = (ChessMove) yylex();
9799         switch (cm) {
9800           case (ChessMove) 0:
9801             if (cmailMsgLoaded) {
9802                 nCmailGames = CMAIL_MAX_GAMES - gn;
9803             } else {
9804                 Reset(TRUE, TRUE);
9805                 DisplayError(_("Game not found in file"), 0);
9806             }
9807             return FALSE;
9808
9809           case GNUChessGame:
9810           case XBoardGame:
9811             gn--;
9812             lastLoadGameStart = cm;
9813             break;
9814             
9815           case MoveNumberOne:
9816             switch (lastLoadGameStart) {
9817               case GNUChessGame:
9818               case XBoardGame:
9819               case PGNTag:
9820                 break;
9821               case MoveNumberOne:
9822               case (ChessMove) 0:
9823                 gn--;           /* count this game */
9824                 lastLoadGameStart = cm;
9825                 break;
9826               default:
9827                 /* impossible */
9828                 break;
9829             }
9830             break;
9831
9832           case PGNTag:
9833             switch (lastLoadGameStart) {
9834               case GNUChessGame:
9835               case PGNTag:
9836               case MoveNumberOne:
9837               case (ChessMove) 0:
9838                 gn--;           /* count this game */
9839                 lastLoadGameStart = cm;
9840                 break;
9841               case XBoardGame:
9842                 lastLoadGameStart = cm; /* game counted already */
9843                 break;
9844               default:
9845                 /* impossible */
9846                 break;
9847             }
9848             if (gn > 0) {
9849                 do {
9850                     yyboardindex = forwardMostMove;
9851                     cm = (ChessMove) yylex();
9852                 } while (cm == PGNTag || cm == Comment);
9853             }
9854             break;
9855
9856           case WhiteWins:
9857           case BlackWins:
9858           case GameIsDrawn:
9859             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9860                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9861                     != CMAIL_OLD_RESULT) {
9862                     nCmailResults ++ ;
9863                     cmailResult[  CMAIL_MAX_GAMES
9864                                 - gn - 1] = CMAIL_OLD_RESULT;
9865                 }
9866             }
9867             break;
9868
9869           case NormalMove:
9870             /* Only a NormalMove can be at the start of a game
9871              * without a position diagram. */
9872             if (lastLoadGameStart == (ChessMove) 0) {
9873               gn--;
9874               lastLoadGameStart = MoveNumberOne;
9875             }
9876             break;
9877
9878           default:
9879             break;
9880         }
9881     }
9882     
9883     if (appData.debugMode)
9884       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9885
9886     if (cm == XBoardGame) {
9887         /* Skip any header junk before position diagram and/or move 1 */
9888         for (;;) {
9889             yyboardindex = forwardMostMove;
9890             cm = (ChessMove) yylex();
9891
9892             if (cm == (ChessMove) 0 ||
9893                 cm == GNUChessGame || cm == XBoardGame) {
9894                 /* Empty game; pretend end-of-file and handle later */
9895                 cm = (ChessMove) 0;
9896                 break;
9897             }
9898
9899             if (cm == MoveNumberOne || cm == PositionDiagram ||
9900                 cm == PGNTag || cm == Comment)
9901               break;
9902         }
9903     } else if (cm == GNUChessGame) {
9904         if (gameInfo.event != NULL) {
9905             free(gameInfo.event);
9906         }
9907         gameInfo.event = StrSave(yy_text);
9908     }   
9909
9910     startedFromSetupPosition = FALSE;
9911     while (cm == PGNTag) {
9912         if (appData.debugMode) 
9913           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9914         err = ParsePGNTag(yy_text, &gameInfo);
9915         if (!err) numPGNTags++;
9916
9917         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9918         if(gameInfo.variant != oldVariant) {
9919             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9920             InitPosition(TRUE);
9921             oldVariant = gameInfo.variant;
9922             if (appData.debugMode) 
9923               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9924         }
9925
9926
9927         if (gameInfo.fen != NULL) {
9928           Board initial_position;
9929           startedFromSetupPosition = TRUE;
9930           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9931             Reset(TRUE, TRUE);
9932             DisplayError(_("Bad FEN position in file"), 0);
9933             return FALSE;
9934           }
9935           CopyBoard(boards[0], initial_position);
9936           if (blackPlaysFirst) {
9937             currentMove = forwardMostMove = backwardMostMove = 1;
9938             CopyBoard(boards[1], initial_position);
9939             strcpy(moveList[0], "");
9940             strcpy(parseList[0], "");
9941             timeRemaining[0][1] = whiteTimeRemaining;
9942             timeRemaining[1][1] = blackTimeRemaining;
9943             if (commentList[0] != NULL) {
9944               commentList[1] = commentList[0];
9945               commentList[0] = NULL;
9946             }
9947           } else {
9948             currentMove = forwardMostMove = backwardMostMove = 0;
9949           }
9950           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9951           {   int i;
9952               initialRulePlies = FENrulePlies;
9953               for( i=0; i< nrCastlingRights; i++ )
9954                   initialRights[i] = initial_position[CASTLING][i];
9955           }
9956           yyboardindex = forwardMostMove;
9957           free(gameInfo.fen);
9958           gameInfo.fen = NULL;
9959         }
9960
9961         yyboardindex = forwardMostMove;
9962         cm = (ChessMove) yylex();
9963
9964         /* Handle comments interspersed among the tags */
9965         while (cm == Comment) {
9966             char *p;
9967             if (appData.debugMode) 
9968               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9969             p = yy_text;
9970             AppendComment(currentMove, p, FALSE);
9971             yyboardindex = forwardMostMove;
9972             cm = (ChessMove) yylex();
9973         }
9974     }
9975
9976     /* don't rely on existence of Event tag since if game was
9977      * pasted from clipboard the Event tag may not exist
9978      */
9979     if (numPGNTags > 0){
9980         char *tags;
9981         if (gameInfo.variant == VariantNormal) {
9982           gameInfo.variant = StringToVariant(gameInfo.event);
9983         }
9984         if (!matchMode) {
9985           if( appData.autoDisplayTags ) {
9986             tags = PGNTags(&gameInfo);
9987             TagsPopUp(tags, CmailMsg());
9988             free(tags);
9989           }
9990         }
9991     } else {
9992         /* Make something up, but don't display it now */
9993         SetGameInfo();
9994         TagsPopDown();
9995     }
9996
9997     if (cm == PositionDiagram) {
9998         int i, j;
9999         char *p;
10000         Board initial_position;
10001
10002         if (appData.debugMode)
10003           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10004
10005         if (!startedFromSetupPosition) {
10006             p = yy_text;
10007             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10008               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10009                 switch (*p) {
10010                   case '[':
10011                   case '-':
10012                   case ' ':
10013                   case '\t':
10014                   case '\n':
10015                   case '\r':
10016                     break;
10017                   default:
10018                     initial_position[i][j++] = CharToPiece(*p);
10019                     break;
10020                 }
10021             while (*p == ' ' || *p == '\t' ||
10022                    *p == '\n' || *p == '\r') p++;
10023         
10024             if (strncmp(p, "black", strlen("black"))==0)
10025               blackPlaysFirst = TRUE;
10026             else
10027               blackPlaysFirst = FALSE;
10028             startedFromSetupPosition = TRUE;
10029         
10030             CopyBoard(boards[0], initial_position);
10031             if (blackPlaysFirst) {
10032                 currentMove = forwardMostMove = backwardMostMove = 1;
10033                 CopyBoard(boards[1], initial_position);
10034                 strcpy(moveList[0], "");
10035                 strcpy(parseList[0], "");
10036                 timeRemaining[0][1] = whiteTimeRemaining;
10037                 timeRemaining[1][1] = blackTimeRemaining;
10038                 if (commentList[0] != NULL) {
10039                     commentList[1] = commentList[0];
10040                     commentList[0] = NULL;
10041                 }
10042             } else {
10043                 currentMove = forwardMostMove = backwardMostMove = 0;
10044             }
10045         }
10046         yyboardindex = forwardMostMove;
10047         cm = (ChessMove) yylex();
10048     }
10049
10050     if (first.pr == NoProc) {
10051         StartChessProgram(&first);
10052     }
10053     InitChessProgram(&first, FALSE);
10054     SendToProgram("force\n", &first);
10055     if (startedFromSetupPosition) {
10056         SendBoard(&first, forwardMostMove);
10057     if (appData.debugMode) {
10058         fprintf(debugFP, "Load Game\n");
10059     }
10060         DisplayBothClocks();
10061     }      
10062
10063     /* [HGM] server: flag to write setup moves in broadcast file as one */
10064     loadFlag = appData.suppressLoadMoves;
10065
10066     while (cm == Comment) {
10067         char *p;
10068         if (appData.debugMode) 
10069           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10070         p = yy_text;
10071         AppendComment(currentMove, p, FALSE);
10072         yyboardindex = forwardMostMove;
10073         cm = (ChessMove) yylex();
10074     }
10075
10076     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10077         cm == WhiteWins || cm == BlackWins ||
10078         cm == GameIsDrawn || cm == GameUnfinished) {
10079         DisplayMessage("", _("No moves in game"));
10080         if (cmailMsgLoaded) {
10081             if (appData.debugMode)
10082               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10083             ClearHighlights();
10084             flipView = FALSE;
10085         }
10086         DrawPosition(FALSE, boards[currentMove]);
10087         DisplayBothClocks();
10088         gameMode = EditGame;
10089         ModeHighlight();
10090         gameFileFP = NULL;
10091         cmailOldMove = 0;
10092         return TRUE;
10093     }
10094
10095     // [HGM] PV info: routine tests if comment empty
10096     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10097         DisplayComment(currentMove - 1, commentList[currentMove]);
10098     }
10099     if (!matchMode && appData.timeDelay != 0) 
10100       DrawPosition(FALSE, boards[currentMove]);
10101
10102     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10103       programStats.ok_to_send = 1;
10104     }
10105
10106     /* if the first token after the PGN tags is a move
10107      * and not move number 1, retrieve it from the parser 
10108      */
10109     if (cm != MoveNumberOne)
10110         LoadGameOneMove(cm);
10111
10112     /* load the remaining moves from the file */
10113     while (LoadGameOneMove((ChessMove)0)) {
10114       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10115       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10116     }
10117
10118     /* rewind to the start of the game */
10119     currentMove = backwardMostMove;
10120
10121     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10122
10123     if (oldGameMode == AnalyzeFile ||
10124         oldGameMode == AnalyzeMode) {
10125       AnalyzeFileEvent();
10126     }
10127
10128     if (matchMode || appData.timeDelay == 0) {
10129       ToEndEvent();
10130       gameMode = EditGame;
10131       ModeHighlight();
10132     } else if (appData.timeDelay > 0) {
10133       AutoPlayGameLoop();
10134     }
10135
10136     if (appData.debugMode) 
10137         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10138
10139     loadFlag = 0; /* [HGM] true game starts */
10140     return TRUE;
10141 }
10142
10143 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10144 int
10145 ReloadPosition(offset)
10146      int offset;
10147 {
10148     int positionNumber = lastLoadPositionNumber + offset;
10149     if (lastLoadPositionFP == NULL) {
10150         DisplayError(_("No position has been loaded yet"), 0);
10151         return FALSE;
10152     }
10153     if (positionNumber <= 0) {
10154         DisplayError(_("Can't back up any further"), 0);
10155         return FALSE;
10156     }
10157     return LoadPosition(lastLoadPositionFP, positionNumber,
10158                         lastLoadPositionTitle);
10159 }
10160
10161 /* Load the nth position from the given file */
10162 int
10163 LoadPositionFromFile(filename, n, title)
10164      char *filename;
10165      int n;
10166      char *title;
10167 {
10168     FILE *f;
10169     char buf[MSG_SIZ];
10170
10171     if (strcmp(filename, "-") == 0) {
10172         return LoadPosition(stdin, n, "stdin");
10173     } else {
10174         f = fopen(filename, "rb");
10175         if (f == NULL) {
10176             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10177             DisplayError(buf, errno);
10178             return FALSE;
10179         } else {
10180             return LoadPosition(f, n, title);
10181         }
10182     }
10183 }
10184
10185 /* Load the nth position from the given open file, and close it */
10186 int
10187 LoadPosition(f, positionNumber, title)
10188      FILE *f;
10189      int positionNumber;
10190      char *title;
10191 {
10192     char *p, line[MSG_SIZ];
10193     Board initial_position;
10194     int i, j, fenMode, pn;
10195     
10196     if (gameMode == Training )
10197         SetTrainingModeOff();
10198
10199     if (gameMode != BeginningOfGame) {
10200         Reset(FALSE, TRUE);
10201     }
10202     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10203         fclose(lastLoadPositionFP);
10204     }
10205     if (positionNumber == 0) positionNumber = 1;
10206     lastLoadPositionFP = f;
10207     lastLoadPositionNumber = positionNumber;
10208     strcpy(lastLoadPositionTitle, title);
10209     if (first.pr == NoProc) {
10210       StartChessProgram(&first);
10211       InitChessProgram(&first, FALSE);
10212     }    
10213     pn = positionNumber;
10214     if (positionNumber < 0) {
10215         /* Negative position number means to seek to that byte offset */
10216         if (fseek(f, -positionNumber, 0) == -1) {
10217             DisplayError(_("Can't seek on position file"), 0);
10218             return FALSE;
10219         };
10220         pn = 1;
10221     } else {
10222         if (fseek(f, 0, 0) == -1) {
10223             if (f == lastLoadPositionFP ?
10224                 positionNumber == lastLoadPositionNumber + 1 :
10225                 positionNumber == 1) {
10226                 pn = 1;
10227             } else {
10228                 DisplayError(_("Can't seek on position file"), 0);
10229                 return FALSE;
10230             }
10231         }
10232     }
10233     /* See if this file is FEN or old-style xboard */
10234     if (fgets(line, MSG_SIZ, f) == NULL) {
10235         DisplayError(_("Position not found in file"), 0);
10236         return FALSE;
10237     }
10238     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10239     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10240
10241     if (pn >= 2) {
10242         if (fenMode || line[0] == '#') pn--;
10243         while (pn > 0) {
10244             /* skip positions before number pn */
10245             if (fgets(line, MSG_SIZ, f) == NULL) {
10246                 Reset(TRUE, TRUE);
10247                 DisplayError(_("Position not found in file"), 0);
10248                 return FALSE;
10249             }
10250             if (fenMode || line[0] == '#') pn--;
10251         }
10252     }
10253
10254     if (fenMode) {
10255         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10256             DisplayError(_("Bad FEN position in file"), 0);
10257             return FALSE;
10258         }
10259     } else {
10260         (void) fgets(line, MSG_SIZ, f);
10261         (void) fgets(line, MSG_SIZ, f);
10262     
10263         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10264             (void) fgets(line, MSG_SIZ, f);
10265             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10266                 if (*p == ' ')
10267                   continue;
10268                 initial_position[i][j++] = CharToPiece(*p);
10269             }
10270         }
10271     
10272         blackPlaysFirst = FALSE;
10273         if (!feof(f)) {
10274             (void) fgets(line, MSG_SIZ, f);
10275             if (strncmp(line, "black", strlen("black"))==0)
10276               blackPlaysFirst = TRUE;
10277         }
10278     }
10279     startedFromSetupPosition = TRUE;
10280     
10281     SendToProgram("force\n", &first);
10282     CopyBoard(boards[0], initial_position);
10283     if (blackPlaysFirst) {
10284         currentMove = forwardMostMove = backwardMostMove = 1;
10285         strcpy(moveList[0], "");
10286         strcpy(parseList[0], "");
10287         CopyBoard(boards[1], initial_position);
10288         DisplayMessage("", _("Black to play"));
10289     } else {
10290         currentMove = forwardMostMove = backwardMostMove = 0;
10291         DisplayMessage("", _("White to play"));
10292     }
10293     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10294     SendBoard(&first, forwardMostMove);
10295     if (appData.debugMode) {
10296 int i, j;
10297   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10298   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10299         fprintf(debugFP, "Load Position\n");
10300     }
10301
10302     if (positionNumber > 1) {
10303         sprintf(line, "%s %d", title, positionNumber);
10304         DisplayTitle(line);
10305     } else {
10306         DisplayTitle(title);
10307     }
10308     gameMode = EditGame;
10309     ModeHighlight();
10310     ResetClocks();
10311     timeRemaining[0][1] = whiteTimeRemaining;
10312     timeRemaining[1][1] = blackTimeRemaining;
10313     DrawPosition(FALSE, boards[currentMove]);
10314    
10315     return TRUE;
10316 }
10317
10318
10319 void
10320 CopyPlayerNameIntoFileName(dest, src)
10321      char **dest, *src;
10322 {
10323     while (*src != NULLCHAR && *src != ',') {
10324         if (*src == ' ') {
10325             *(*dest)++ = '_';
10326             src++;
10327         } else {
10328             *(*dest)++ = *src++;
10329         }
10330     }
10331 }
10332
10333 char *DefaultFileName(ext)
10334      char *ext;
10335 {
10336     static char def[MSG_SIZ];
10337     char *p;
10338
10339     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10340         p = def;
10341         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10342         *p++ = '-';
10343         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10344         *p++ = '.';
10345         strcpy(p, ext);
10346     } else {
10347         def[0] = NULLCHAR;
10348     }
10349     return def;
10350 }
10351
10352 /* Save the current game to the given file */
10353 int
10354 SaveGameToFile(filename, append)
10355      char *filename;
10356      int append;
10357 {
10358     FILE *f;
10359     char buf[MSG_SIZ];
10360
10361     if (strcmp(filename, "-") == 0) {
10362         return SaveGame(stdout, 0, NULL);
10363     } else {
10364         f = fopen(filename, append ? "a" : "w");
10365         if (f == NULL) {
10366             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10367             DisplayError(buf, errno);
10368             return FALSE;
10369         } else {
10370             return SaveGame(f, 0, NULL);
10371         }
10372     }
10373 }
10374
10375 char *
10376 SavePart(str)
10377      char *str;
10378 {
10379     static char buf[MSG_SIZ];
10380     char *p;
10381     
10382     p = strchr(str, ' ');
10383     if (p == NULL) return str;
10384     strncpy(buf, str, p - str);
10385     buf[p - str] = NULLCHAR;
10386     return buf;
10387 }
10388
10389 #define PGN_MAX_LINE 75
10390
10391 #define PGN_SIDE_WHITE  0
10392 #define PGN_SIDE_BLACK  1
10393
10394 /* [AS] */
10395 static int FindFirstMoveOutOfBook( int side )
10396 {
10397     int result = -1;
10398
10399     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10400         int index = backwardMostMove;
10401         int has_book_hit = 0;
10402
10403         if( (index % 2) != side ) {
10404             index++;
10405         }
10406
10407         while( index < forwardMostMove ) {
10408             /* Check to see if engine is in book */
10409             int depth = pvInfoList[index].depth;
10410             int score = pvInfoList[index].score;
10411             int in_book = 0;
10412
10413             if( depth <= 2 ) {
10414                 in_book = 1;
10415             }
10416             else if( score == 0 && depth == 63 ) {
10417                 in_book = 1; /* Zappa */
10418             }
10419             else if( score == 2 && depth == 99 ) {
10420                 in_book = 1; /* Abrok */
10421             }
10422
10423             has_book_hit += in_book;
10424
10425             if( ! in_book ) {
10426                 result = index;
10427
10428                 break;
10429             }
10430
10431             index += 2;
10432         }
10433     }
10434
10435     return result;
10436 }
10437
10438 /* [AS] */
10439 void GetOutOfBookInfo( char * buf )
10440 {
10441     int oob[2];
10442     int i;
10443     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10444
10445     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10446     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10447
10448     *buf = '\0';
10449
10450     if( oob[0] >= 0 || oob[1] >= 0 ) {
10451         for( i=0; i<2; i++ ) {
10452             int idx = oob[i];
10453
10454             if( idx >= 0 ) {
10455                 if( i > 0 && oob[0] >= 0 ) {
10456                     strcat( buf, "   " );
10457                 }
10458
10459                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10460                 sprintf( buf+strlen(buf), "%s%.2f", 
10461                     pvInfoList[idx].score >= 0 ? "+" : "",
10462                     pvInfoList[idx].score / 100.0 );
10463             }
10464         }
10465     }
10466 }
10467
10468 /* Save game in PGN style and close the file */
10469 int
10470 SaveGamePGN(f)
10471      FILE *f;
10472 {
10473     int i, offset, linelen, newblock;
10474     time_t tm;
10475 //    char *movetext;
10476     char numtext[32];
10477     int movelen, numlen, blank;
10478     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10479
10480     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10481     
10482     tm = time((time_t *) NULL);
10483     
10484     PrintPGNTags(f, &gameInfo);
10485     
10486     if (backwardMostMove > 0 || startedFromSetupPosition) {
10487         char *fen = PositionToFEN(backwardMostMove, NULL);
10488         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10489         fprintf(f, "\n{--------------\n");
10490         PrintPosition(f, backwardMostMove);
10491         fprintf(f, "--------------}\n");
10492         free(fen);
10493     }
10494     else {
10495         /* [AS] Out of book annotation */
10496         if( appData.saveOutOfBookInfo ) {
10497             char buf[64];
10498
10499             GetOutOfBookInfo( buf );
10500
10501             if( buf[0] != '\0' ) {
10502                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10503             }
10504         }
10505
10506         fprintf(f, "\n");
10507     }
10508
10509     i = backwardMostMove;
10510     linelen = 0;
10511     newblock = TRUE;
10512
10513     while (i < forwardMostMove) {
10514         /* Print comments preceding this move */
10515         if (commentList[i] != NULL) {
10516             if (linelen > 0) fprintf(f, "\n");
10517             fprintf(f, "%s", commentList[i]);
10518             linelen = 0;
10519             newblock = TRUE;
10520         }
10521
10522         /* Format move number */
10523         if ((i % 2) == 0) {
10524             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10525         } else {
10526             if (newblock) {
10527                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10528             } else {
10529                 numtext[0] = NULLCHAR;
10530             }
10531         }
10532         numlen = strlen(numtext);
10533         newblock = FALSE;
10534
10535         /* Print move number */
10536         blank = linelen > 0 && numlen > 0;
10537         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10538             fprintf(f, "\n");
10539             linelen = 0;
10540             blank = 0;
10541         }
10542         if (blank) {
10543             fprintf(f, " ");
10544             linelen++;
10545         }
10546         fprintf(f, "%s", numtext);
10547         linelen += numlen;
10548
10549         /* Get move */
10550         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10551         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10552
10553         /* Print move */
10554         blank = linelen > 0 && movelen > 0;
10555         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10556             fprintf(f, "\n");
10557             linelen = 0;
10558             blank = 0;
10559         }
10560         if (blank) {
10561             fprintf(f, " ");
10562             linelen++;
10563         }
10564         fprintf(f, "%s", move_buffer);
10565         linelen += movelen;
10566
10567         /* [AS] Add PV info if present */
10568         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10569             /* [HGM] add time */
10570             char buf[MSG_SIZ]; int seconds;
10571
10572             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10573
10574             if( seconds <= 0) buf[0] = 0; else
10575             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10576                 seconds = (seconds + 4)/10; // round to full seconds
10577                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10578                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10579             }
10580
10581             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10582                 pvInfoList[i].score >= 0 ? "+" : "",
10583                 pvInfoList[i].score / 100.0,
10584                 pvInfoList[i].depth,
10585                 buf );
10586
10587             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10588
10589             /* Print score/depth */
10590             blank = linelen > 0 && movelen > 0;
10591             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10592                 fprintf(f, "\n");
10593                 linelen = 0;
10594                 blank = 0;
10595             }
10596             if (blank) {
10597                 fprintf(f, " ");
10598                 linelen++;
10599             }
10600             fprintf(f, "%s", move_buffer);
10601             linelen += movelen;
10602         }
10603
10604         i++;
10605     }
10606     
10607     /* Start a new line */
10608     if (linelen > 0) fprintf(f, "\n");
10609
10610     /* Print comments after last move */
10611     if (commentList[i] != NULL) {
10612         fprintf(f, "%s\n", commentList[i]);
10613     }
10614
10615     /* Print result */
10616     if (gameInfo.resultDetails != NULL &&
10617         gameInfo.resultDetails[0] != NULLCHAR) {
10618         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10619                 PGNResult(gameInfo.result));
10620     } else {
10621         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10622     }
10623
10624     fclose(f);
10625     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10626     return TRUE;
10627 }
10628
10629 /* Save game in old style and close the file */
10630 int
10631 SaveGameOldStyle(f)
10632      FILE *f;
10633 {
10634     int i, offset;
10635     time_t tm;
10636     
10637     tm = time((time_t *) NULL);
10638     
10639     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10640     PrintOpponents(f);
10641     
10642     if (backwardMostMove > 0 || startedFromSetupPosition) {
10643         fprintf(f, "\n[--------------\n");
10644         PrintPosition(f, backwardMostMove);
10645         fprintf(f, "--------------]\n");
10646     } else {
10647         fprintf(f, "\n");
10648     }
10649
10650     i = backwardMostMove;
10651     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10652
10653     while (i < forwardMostMove) {
10654         if (commentList[i] != NULL) {
10655             fprintf(f, "[%s]\n", commentList[i]);
10656         }
10657
10658         if ((i % 2) == 1) {
10659             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10660             i++;
10661         } else {
10662             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10663             i++;
10664             if (commentList[i] != NULL) {
10665                 fprintf(f, "\n");
10666                 continue;
10667             }
10668             if (i >= forwardMostMove) {
10669                 fprintf(f, "\n");
10670                 break;
10671             }
10672             fprintf(f, "%s\n", parseList[i]);
10673             i++;
10674         }
10675     }
10676     
10677     if (commentList[i] != NULL) {
10678         fprintf(f, "[%s]\n", commentList[i]);
10679     }
10680
10681     /* This isn't really the old style, but it's close enough */
10682     if (gameInfo.resultDetails != NULL &&
10683         gameInfo.resultDetails[0] != NULLCHAR) {
10684         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10685                 gameInfo.resultDetails);
10686     } else {
10687         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10688     }
10689
10690     fclose(f);
10691     return TRUE;
10692 }
10693
10694 /* Save the current game to open file f and close the file */
10695 int
10696 SaveGame(f, dummy, dummy2)
10697      FILE *f;
10698      int dummy;
10699      char *dummy2;
10700 {
10701     if (gameMode == EditPosition) EditPositionDone(TRUE);
10702     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10703     if (appData.oldSaveStyle)
10704       return SaveGameOldStyle(f);
10705     else
10706       return SaveGamePGN(f);
10707 }
10708
10709 /* Save the current position to the given file */
10710 int
10711 SavePositionToFile(filename)
10712      char *filename;
10713 {
10714     FILE *f;
10715     char buf[MSG_SIZ];
10716
10717     if (strcmp(filename, "-") == 0) {
10718         return SavePosition(stdout, 0, NULL);
10719     } else {
10720         f = fopen(filename, "a");
10721         if (f == NULL) {
10722             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10723             DisplayError(buf, errno);
10724             return FALSE;
10725         } else {
10726             SavePosition(f, 0, NULL);
10727             return TRUE;
10728         }
10729     }
10730 }
10731
10732 /* Save the current position to the given open file and close the file */
10733 int
10734 SavePosition(f, dummy, dummy2)
10735      FILE *f;
10736      int dummy;
10737      char *dummy2;
10738 {
10739     time_t tm;
10740     char *fen;
10741     
10742     if (gameMode == EditPosition) EditPositionDone(TRUE);
10743     if (appData.oldSaveStyle) {
10744         tm = time((time_t *) NULL);
10745     
10746         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10747         PrintOpponents(f);
10748         fprintf(f, "[--------------\n");
10749         PrintPosition(f, currentMove);
10750         fprintf(f, "--------------]\n");
10751     } else {
10752         fen = PositionToFEN(currentMove, NULL);
10753         fprintf(f, "%s\n", fen);
10754         free(fen);
10755     }
10756     fclose(f);
10757     return TRUE;
10758 }
10759
10760 void
10761 ReloadCmailMsgEvent(unregister)
10762      int unregister;
10763 {
10764 #if !WIN32
10765     static char *inFilename = NULL;
10766     static char *outFilename;
10767     int i;
10768     struct stat inbuf, outbuf;
10769     int status;
10770     
10771     /* Any registered moves are unregistered if unregister is set, */
10772     /* i.e. invoked by the signal handler */
10773     if (unregister) {
10774         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10775             cmailMoveRegistered[i] = FALSE;
10776             if (cmailCommentList[i] != NULL) {
10777                 free(cmailCommentList[i]);
10778                 cmailCommentList[i] = NULL;
10779             }
10780         }
10781         nCmailMovesRegistered = 0;
10782     }
10783
10784     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10785         cmailResult[i] = CMAIL_NOT_RESULT;
10786     }
10787     nCmailResults = 0;
10788
10789     if (inFilename == NULL) {
10790         /* Because the filenames are static they only get malloced once  */
10791         /* and they never get freed                                      */
10792         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10793         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10794
10795         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10796         sprintf(outFilename, "%s.out", appData.cmailGameName);
10797     }
10798     
10799     status = stat(outFilename, &outbuf);
10800     if (status < 0) {
10801         cmailMailedMove = FALSE;
10802     } else {
10803         status = stat(inFilename, &inbuf);
10804         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10805     }
10806     
10807     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10808        counts the games, notes how each one terminated, etc.
10809        
10810        It would be nice to remove this kludge and instead gather all
10811        the information while building the game list.  (And to keep it
10812        in the game list nodes instead of having a bunch of fixed-size
10813        parallel arrays.)  Note this will require getting each game's
10814        termination from the PGN tags, as the game list builder does
10815        not process the game moves.  --mann
10816        */
10817     cmailMsgLoaded = TRUE;
10818     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10819     
10820     /* Load first game in the file or popup game menu */
10821     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10822
10823 #endif /* !WIN32 */
10824     return;
10825 }
10826
10827 int
10828 RegisterMove()
10829 {
10830     FILE *f;
10831     char string[MSG_SIZ];
10832
10833     if (   cmailMailedMove
10834         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10835         return TRUE;            /* Allow free viewing  */
10836     }
10837
10838     /* Unregister move to ensure that we don't leave RegisterMove        */
10839     /* with the move registered when the conditions for registering no   */
10840     /* longer hold                                                       */
10841     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10842         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10843         nCmailMovesRegistered --;
10844
10845         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10846           {
10847               free(cmailCommentList[lastLoadGameNumber - 1]);
10848               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10849           }
10850     }
10851
10852     if (cmailOldMove == -1) {
10853         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10854         return FALSE;
10855     }
10856
10857     if (currentMove > cmailOldMove + 1) {
10858         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10859         return FALSE;
10860     }
10861
10862     if (currentMove < cmailOldMove) {
10863         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10864         return FALSE;
10865     }
10866
10867     if (forwardMostMove > currentMove) {
10868         /* Silently truncate extra moves */
10869         TruncateGame();
10870     }
10871
10872     if (   (currentMove == cmailOldMove + 1)
10873         || (   (currentMove == cmailOldMove)
10874             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10875                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10876         if (gameInfo.result != GameUnfinished) {
10877             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10878         }
10879
10880         if (commentList[currentMove] != NULL) {
10881             cmailCommentList[lastLoadGameNumber - 1]
10882               = StrSave(commentList[currentMove]);
10883         }
10884         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10885
10886         if (appData.debugMode)
10887           fprintf(debugFP, "Saving %s for game %d\n",
10888                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10889
10890         sprintf(string,
10891                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10892         
10893         f = fopen(string, "w");
10894         if (appData.oldSaveStyle) {
10895             SaveGameOldStyle(f); /* also closes the file */
10896             
10897             sprintf(string, "%s.pos.out", appData.cmailGameName);
10898             f = fopen(string, "w");
10899             SavePosition(f, 0, NULL); /* also closes the file */
10900         } else {
10901             fprintf(f, "{--------------\n");
10902             PrintPosition(f, currentMove);
10903             fprintf(f, "--------------}\n\n");
10904             
10905             SaveGame(f, 0, NULL); /* also closes the file*/
10906         }
10907         
10908         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10909         nCmailMovesRegistered ++;
10910     } else if (nCmailGames == 1) {
10911         DisplayError(_("You have not made a move yet"), 0);
10912         return FALSE;
10913     }
10914
10915     return TRUE;
10916 }
10917
10918 void
10919 MailMoveEvent()
10920 {
10921 #if !WIN32
10922     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10923     FILE *commandOutput;
10924     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10925     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10926     int nBuffers;
10927     int i;
10928     int archived;
10929     char *arcDir;
10930
10931     if (! cmailMsgLoaded) {
10932         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10933         return;
10934     }
10935
10936     if (nCmailGames == nCmailResults) {
10937         DisplayError(_("No unfinished games"), 0);
10938         return;
10939     }
10940
10941 #if CMAIL_PROHIBIT_REMAIL
10942     if (cmailMailedMove) {
10943         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);
10944         DisplayError(msg, 0);
10945         return;
10946     }
10947 #endif
10948
10949     if (! (cmailMailedMove || RegisterMove())) return;
10950     
10951     if (   cmailMailedMove
10952         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10953         sprintf(string, partCommandString,
10954                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10955         commandOutput = popen(string, "r");
10956
10957         if (commandOutput == NULL) {
10958             DisplayError(_("Failed to invoke cmail"), 0);
10959         } else {
10960             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10961                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10962             }
10963             if (nBuffers > 1) {
10964                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10965                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10966                 nBytes = MSG_SIZ - 1;
10967             } else {
10968                 (void) memcpy(msg, buffer, nBytes);
10969             }
10970             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10971
10972             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10973                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10974
10975                 archived = TRUE;
10976                 for (i = 0; i < nCmailGames; i ++) {
10977                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10978                         archived = FALSE;
10979                     }
10980                 }
10981                 if (   archived
10982                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10983                         != NULL)) {
10984                     sprintf(buffer, "%s/%s.%s.archive",
10985                             arcDir,
10986                             appData.cmailGameName,
10987                             gameInfo.date);
10988                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10989                     cmailMsgLoaded = FALSE;
10990                 }
10991             }
10992
10993             DisplayInformation(msg);
10994             pclose(commandOutput);
10995         }
10996     } else {
10997         if ((*cmailMsg) != '\0') {
10998             DisplayInformation(cmailMsg);
10999         }
11000     }
11001
11002     return;
11003 #endif /* !WIN32 */
11004 }
11005
11006 char *
11007 CmailMsg()
11008 {
11009 #if WIN32
11010     return NULL;
11011 #else
11012     int  prependComma = 0;
11013     char number[5];
11014     char string[MSG_SIZ];       /* Space for game-list */
11015     int  i;
11016     
11017     if (!cmailMsgLoaded) return "";
11018
11019     if (cmailMailedMove) {
11020         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11021     } else {
11022         /* Create a list of games left */
11023         sprintf(string, "[");
11024         for (i = 0; i < nCmailGames; i ++) {
11025             if (! (   cmailMoveRegistered[i]
11026                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11027                 if (prependComma) {
11028                     sprintf(number, ",%d", i + 1);
11029                 } else {
11030                     sprintf(number, "%d", i + 1);
11031                     prependComma = 1;
11032                 }
11033                 
11034                 strcat(string, number);
11035             }
11036         }
11037         strcat(string, "]");
11038
11039         if (nCmailMovesRegistered + nCmailResults == 0) {
11040             switch (nCmailGames) {
11041               case 1:
11042                 sprintf(cmailMsg,
11043                         _("Still need to make move for game\n"));
11044                 break;
11045                 
11046               case 2:
11047                 sprintf(cmailMsg,
11048                         _("Still need to make moves for both games\n"));
11049                 break;
11050                 
11051               default:
11052                 sprintf(cmailMsg,
11053                         _("Still need to make moves for all %d games\n"),
11054                         nCmailGames);
11055                 break;
11056             }
11057         } else {
11058             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11059               case 1:
11060                 sprintf(cmailMsg,
11061                         _("Still need to make a move for game %s\n"),
11062                         string);
11063                 break;
11064                 
11065               case 0:
11066                 if (nCmailResults == nCmailGames) {
11067                     sprintf(cmailMsg, _("No unfinished games\n"));
11068                 } else {
11069                     sprintf(cmailMsg, _("Ready to send mail\n"));
11070                 }
11071                 break;
11072                 
11073               default:
11074                 sprintf(cmailMsg,
11075                         _("Still need to make moves for games %s\n"),
11076                         string);
11077             }
11078         }
11079     }
11080     return cmailMsg;
11081 #endif /* WIN32 */
11082 }
11083
11084 void
11085 ResetGameEvent()
11086 {
11087     if (gameMode == Training)
11088       SetTrainingModeOff();
11089
11090     Reset(TRUE, TRUE);
11091     cmailMsgLoaded = FALSE;
11092     if (appData.icsActive) {
11093       SendToICS(ics_prefix);
11094       SendToICS("refresh\n");
11095     }
11096 }
11097
11098 void
11099 ExitEvent(status)
11100      int status;
11101 {
11102     exiting++;
11103     if (exiting > 2) {
11104       /* Give up on clean exit */
11105       exit(status);
11106     }
11107     if (exiting > 1) {
11108       /* Keep trying for clean exit */
11109       return;
11110     }
11111
11112     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11113
11114     if (telnetISR != NULL) {
11115       RemoveInputSource(telnetISR);
11116     }
11117     if (icsPR != NoProc) {
11118       DestroyChildProcess(icsPR, TRUE);
11119     }
11120
11121     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11122     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11123
11124     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11125     /* make sure this other one finishes before killing it!                  */
11126     if(endingGame) { int count = 0;
11127         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11128         while(endingGame && count++ < 10) DoSleep(1);
11129         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11130     }
11131
11132     /* Kill off chess programs */
11133     if (first.pr != NoProc) {
11134         ExitAnalyzeMode();
11135         
11136         DoSleep( appData.delayBeforeQuit );
11137         SendToProgram("quit\n", &first);
11138         DoSleep( appData.delayAfterQuit );
11139         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11140     }
11141     if (second.pr != NoProc) {
11142         DoSleep( appData.delayBeforeQuit );
11143         SendToProgram("quit\n", &second);
11144         DoSleep( appData.delayAfterQuit );
11145         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11146     }
11147     if (first.isr != NULL) {
11148         RemoveInputSource(first.isr);
11149     }
11150     if (second.isr != NULL) {
11151         RemoveInputSource(second.isr);
11152     }
11153
11154     ShutDownFrontEnd();
11155     exit(status);
11156 }
11157
11158 void
11159 PauseEvent()
11160 {
11161     if (appData.debugMode)
11162         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11163     if (pausing) {
11164         pausing = FALSE;
11165         ModeHighlight();
11166         if (gameMode == MachinePlaysWhite ||
11167             gameMode == MachinePlaysBlack) {
11168             StartClocks();
11169         } else {
11170             DisplayBothClocks();
11171         }
11172         if (gameMode == PlayFromGameFile) {
11173             if (appData.timeDelay >= 0) 
11174                 AutoPlayGameLoop();
11175         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11176             Reset(FALSE, TRUE);
11177             SendToICS(ics_prefix);
11178             SendToICS("refresh\n");
11179         } else if (currentMove < forwardMostMove) {
11180             ForwardInner(forwardMostMove);
11181         }
11182         pauseExamInvalid = FALSE;
11183     } else {
11184         switch (gameMode) {
11185           default:
11186             return;
11187           case IcsExamining:
11188             pauseExamForwardMostMove = forwardMostMove;
11189             pauseExamInvalid = FALSE;
11190             /* fall through */
11191           case IcsObserving:
11192           case IcsPlayingWhite:
11193           case IcsPlayingBlack:
11194             pausing = TRUE;
11195             ModeHighlight();
11196             return;
11197           case PlayFromGameFile:
11198             (void) StopLoadGameTimer();
11199             pausing = TRUE;
11200             ModeHighlight();
11201             break;
11202           case BeginningOfGame:
11203             if (appData.icsActive) return;
11204             /* else fall through */
11205           case MachinePlaysWhite:
11206           case MachinePlaysBlack:
11207           case TwoMachinesPlay:
11208             if (forwardMostMove == 0)
11209               return;           /* don't pause if no one has moved */
11210             if ((gameMode == MachinePlaysWhite &&
11211                  !WhiteOnMove(forwardMostMove)) ||
11212                 (gameMode == MachinePlaysBlack &&
11213                  WhiteOnMove(forwardMostMove))) {
11214                 StopClocks();
11215             }
11216             pausing = TRUE;
11217             ModeHighlight();
11218             break;
11219         }
11220     }
11221 }
11222
11223 void
11224 EditCommentEvent()
11225 {
11226     char title[MSG_SIZ];
11227
11228     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11229         strcpy(title, _("Edit comment"));
11230     } else {
11231         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11232                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11233                 parseList[currentMove - 1]);
11234     }
11235
11236     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11237 }
11238
11239
11240 void
11241 EditTagsEvent()
11242 {
11243     char *tags = PGNTags(&gameInfo);
11244     EditTagsPopUp(tags);
11245     free(tags);
11246 }
11247
11248 void
11249 AnalyzeModeEvent()
11250 {
11251     if (appData.noChessProgram || gameMode == AnalyzeMode)
11252       return;
11253
11254     if (gameMode != AnalyzeFile) {
11255         if (!appData.icsEngineAnalyze) {
11256                EditGameEvent();
11257                if (gameMode != EditGame) return;
11258         }
11259         ResurrectChessProgram();
11260         SendToProgram("analyze\n", &first);
11261         first.analyzing = TRUE;
11262         /*first.maybeThinking = TRUE;*/
11263         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11264         EngineOutputPopUp();
11265     }
11266     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11267     pausing = FALSE;
11268     ModeHighlight();
11269     SetGameInfo();
11270
11271     StartAnalysisClock();
11272     GetTimeMark(&lastNodeCountTime);
11273     lastNodeCount = 0;
11274 }
11275
11276 void
11277 AnalyzeFileEvent()
11278 {
11279     if (appData.noChessProgram || gameMode == AnalyzeFile)
11280       return;
11281
11282     if (gameMode != AnalyzeMode) {
11283         EditGameEvent();
11284         if (gameMode != EditGame) return;
11285         ResurrectChessProgram();
11286         SendToProgram("analyze\n", &first);
11287         first.analyzing = TRUE;
11288         /*first.maybeThinking = TRUE;*/
11289         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11290         EngineOutputPopUp();
11291     }
11292     gameMode = AnalyzeFile;
11293     pausing = FALSE;
11294     ModeHighlight();
11295     SetGameInfo();
11296
11297     StartAnalysisClock();
11298     GetTimeMark(&lastNodeCountTime);
11299     lastNodeCount = 0;
11300 }
11301
11302 void
11303 MachineWhiteEvent()
11304 {
11305     char buf[MSG_SIZ];
11306     char *bookHit = NULL;
11307
11308     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11309       return;
11310
11311
11312     if (gameMode == PlayFromGameFile || 
11313         gameMode == TwoMachinesPlay  || 
11314         gameMode == Training         || 
11315         gameMode == AnalyzeMode      || 
11316         gameMode == EndOfGame)
11317         EditGameEvent();
11318
11319     if (gameMode == EditPosition) 
11320         EditPositionDone(TRUE);
11321
11322     if (!WhiteOnMove(currentMove)) {
11323         DisplayError(_("It is not White's turn"), 0);
11324         return;
11325     }
11326   
11327     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11328       ExitAnalyzeMode();
11329
11330     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11331         gameMode == AnalyzeFile)
11332         TruncateGame();
11333
11334     ResurrectChessProgram();    /* in case it isn't running */
11335     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11336         gameMode = MachinePlaysWhite;
11337         ResetClocks();
11338     } else
11339     gameMode = MachinePlaysWhite;
11340     pausing = FALSE;
11341     ModeHighlight();
11342     SetGameInfo();
11343     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11344     DisplayTitle(buf);
11345     if (first.sendName) {
11346       sprintf(buf, "name %s\n", gameInfo.black);
11347       SendToProgram(buf, &first);
11348     }
11349     if (first.sendTime) {
11350       if (first.useColors) {
11351         SendToProgram("black\n", &first); /*gnu kludge*/
11352       }
11353       SendTimeRemaining(&first, TRUE);
11354     }
11355     if (first.useColors) {
11356       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11357     }
11358     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11359     SetMachineThinkingEnables();
11360     first.maybeThinking = TRUE;
11361     StartClocks();
11362     firstMove = FALSE;
11363
11364     if (appData.autoFlipView && !flipView) {
11365       flipView = !flipView;
11366       DrawPosition(FALSE, NULL);
11367       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11368     }
11369
11370     if(bookHit) { // [HGM] book: simulate book reply
11371         static char bookMove[MSG_SIZ]; // a bit generous?
11372
11373         programStats.nodes = programStats.depth = programStats.time = 
11374         programStats.score = programStats.got_only_move = 0;
11375         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11376
11377         strcpy(bookMove, "move ");
11378         strcat(bookMove, bookHit);
11379         HandleMachineMove(bookMove, &first);
11380     }
11381 }
11382
11383 void
11384 MachineBlackEvent()
11385 {
11386     char buf[MSG_SIZ];
11387    char *bookHit = NULL;
11388
11389     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11390         return;
11391
11392
11393     if (gameMode == PlayFromGameFile || 
11394         gameMode == TwoMachinesPlay  || 
11395         gameMode == Training         || 
11396         gameMode == AnalyzeMode      || 
11397         gameMode == EndOfGame)
11398         EditGameEvent();
11399
11400     if (gameMode == EditPosition) 
11401         EditPositionDone(TRUE);
11402
11403     if (WhiteOnMove(currentMove)) {
11404         DisplayError(_("It is not Black's turn"), 0);
11405         return;
11406     }
11407     
11408     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11409       ExitAnalyzeMode();
11410
11411     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11412         gameMode == AnalyzeFile)
11413         TruncateGame();
11414
11415     ResurrectChessProgram();    /* in case it isn't running */
11416     gameMode = MachinePlaysBlack;
11417     pausing = FALSE;
11418     ModeHighlight();
11419     SetGameInfo();
11420     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11421     DisplayTitle(buf);
11422     if (first.sendName) {
11423       sprintf(buf, "name %s\n", gameInfo.white);
11424       SendToProgram(buf, &first);
11425     }
11426     if (first.sendTime) {
11427       if (first.useColors) {
11428         SendToProgram("white\n", &first); /*gnu kludge*/
11429       }
11430       SendTimeRemaining(&first, FALSE);
11431     }
11432     if (first.useColors) {
11433       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11434     }
11435     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11436     SetMachineThinkingEnables();
11437     first.maybeThinking = TRUE;
11438     StartClocks();
11439
11440     if (appData.autoFlipView && flipView) {
11441       flipView = !flipView;
11442       DrawPosition(FALSE, NULL);
11443       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11444     }
11445     if(bookHit) { // [HGM] book: simulate book reply
11446         static char bookMove[MSG_SIZ]; // a bit generous?
11447
11448         programStats.nodes = programStats.depth = programStats.time = 
11449         programStats.score = programStats.got_only_move = 0;
11450         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11451
11452         strcpy(bookMove, "move ");
11453         strcat(bookMove, bookHit);
11454         HandleMachineMove(bookMove, &first);
11455     }
11456 }
11457
11458
11459 void
11460 DisplayTwoMachinesTitle()
11461 {
11462     char buf[MSG_SIZ];
11463     if (appData.matchGames > 0) {
11464         if (first.twoMachinesColor[0] == 'w') {
11465             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11466                     gameInfo.white, gameInfo.black,
11467                     first.matchWins, second.matchWins,
11468                     matchGame - 1 - (first.matchWins + second.matchWins));
11469         } else {
11470             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11471                     gameInfo.white, gameInfo.black,
11472                     second.matchWins, first.matchWins,
11473                     matchGame - 1 - (first.matchWins + second.matchWins));
11474         }
11475     } else {
11476         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11477     }
11478     DisplayTitle(buf);
11479 }
11480
11481 void
11482 TwoMachinesEvent P((void))
11483 {
11484     int i;
11485     char buf[MSG_SIZ];
11486     ChessProgramState *onmove;
11487     char *bookHit = NULL;
11488     
11489     if (appData.noChessProgram) return;
11490
11491     switch (gameMode) {
11492       case TwoMachinesPlay:
11493         return;
11494       case MachinePlaysWhite:
11495       case MachinePlaysBlack:
11496         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11497             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11498             return;
11499         }
11500         /* fall through */
11501       case BeginningOfGame:
11502       case PlayFromGameFile:
11503       case EndOfGame:
11504         EditGameEvent();
11505         if (gameMode != EditGame) return;
11506         break;
11507       case EditPosition:
11508         EditPositionDone(TRUE);
11509         break;
11510       case AnalyzeMode:
11511       case AnalyzeFile:
11512         ExitAnalyzeMode();
11513         break;
11514       case EditGame:
11515       default:
11516         break;
11517     }
11518
11519 //    forwardMostMove = currentMove;
11520     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11521     ResurrectChessProgram();    /* in case first program isn't running */
11522
11523     if (second.pr == NULL) {
11524         StartChessProgram(&second);
11525         if (second.protocolVersion == 1) {
11526           TwoMachinesEventIfReady();
11527         } else {
11528           /* kludge: allow timeout for initial "feature" command */
11529           FreezeUI();
11530           DisplayMessage("", _("Starting second chess program"));
11531           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11532         }
11533         return;
11534     }
11535     DisplayMessage("", "");
11536     InitChessProgram(&second, FALSE);
11537     SendToProgram("force\n", &second);
11538     if (startedFromSetupPosition) {
11539         SendBoard(&second, backwardMostMove);
11540     if (appData.debugMode) {
11541         fprintf(debugFP, "Two Machines\n");
11542     }
11543     }
11544     for (i = backwardMostMove; i < forwardMostMove; i++) {
11545         SendMoveToProgram(i, &second);
11546     }
11547
11548     gameMode = TwoMachinesPlay;
11549     pausing = FALSE;
11550     ModeHighlight();
11551     SetGameInfo();
11552     DisplayTwoMachinesTitle();
11553     firstMove = TRUE;
11554     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11555         onmove = &first;
11556     } else {
11557         onmove = &second;
11558     }
11559
11560     SendToProgram(first.computerString, &first);
11561     if (first.sendName) {
11562       sprintf(buf, "name %s\n", second.tidy);
11563       SendToProgram(buf, &first);
11564     }
11565     SendToProgram(second.computerString, &second);
11566     if (second.sendName) {
11567       sprintf(buf, "name %s\n", first.tidy);
11568       SendToProgram(buf, &second);
11569     }
11570
11571     ResetClocks();
11572     if (!first.sendTime || !second.sendTime) {
11573         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11574         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11575     }
11576     if (onmove->sendTime) {
11577       if (onmove->useColors) {
11578         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11579       }
11580       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11581     }
11582     if (onmove->useColors) {
11583       SendToProgram(onmove->twoMachinesColor, onmove);
11584     }
11585     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11586 //    SendToProgram("go\n", onmove);
11587     onmove->maybeThinking = TRUE;
11588     SetMachineThinkingEnables();
11589
11590     StartClocks();
11591
11592     if(bookHit) { // [HGM] book: simulate book reply
11593         static char bookMove[MSG_SIZ]; // a bit generous?
11594
11595         programStats.nodes = programStats.depth = programStats.time = 
11596         programStats.score = programStats.got_only_move = 0;
11597         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11598
11599         strcpy(bookMove, "move ");
11600         strcat(bookMove, bookHit);
11601         savedMessage = bookMove; // args for deferred call
11602         savedState = onmove;
11603         ScheduleDelayedEvent(DeferredBookMove, 1);
11604     }
11605 }
11606
11607 void
11608 TrainingEvent()
11609 {
11610     if (gameMode == Training) {
11611       SetTrainingModeOff();
11612       gameMode = PlayFromGameFile;
11613       DisplayMessage("", _("Training mode off"));
11614     } else {
11615       gameMode = Training;
11616       animateTraining = appData.animate;
11617
11618       /* make sure we are not already at the end of the game */
11619       if (currentMove < forwardMostMove) {
11620         SetTrainingModeOn();
11621         DisplayMessage("", _("Training mode on"));
11622       } else {
11623         gameMode = PlayFromGameFile;
11624         DisplayError(_("Already at end of game"), 0);
11625       }
11626     }
11627     ModeHighlight();
11628 }
11629
11630 void
11631 IcsClientEvent()
11632 {
11633     if (!appData.icsActive) return;
11634     switch (gameMode) {
11635       case IcsPlayingWhite:
11636       case IcsPlayingBlack:
11637       case IcsObserving:
11638       case IcsIdle:
11639       case BeginningOfGame:
11640       case IcsExamining:
11641         return;
11642
11643       case EditGame:
11644         break;
11645
11646       case EditPosition:
11647         EditPositionDone(TRUE);
11648         break;
11649
11650       case AnalyzeMode:
11651       case AnalyzeFile:
11652         ExitAnalyzeMode();
11653         break;
11654         
11655       default:
11656         EditGameEvent();
11657         break;
11658     }
11659
11660     gameMode = IcsIdle;
11661     ModeHighlight();
11662     return;
11663 }
11664
11665
11666 void
11667 EditGameEvent()
11668 {
11669     int i;
11670
11671     switch (gameMode) {
11672       case Training:
11673         SetTrainingModeOff();
11674         break;
11675       case MachinePlaysWhite:
11676       case MachinePlaysBlack:
11677       case BeginningOfGame:
11678         SendToProgram("force\n", &first);
11679         SetUserThinkingEnables();
11680         break;
11681       case PlayFromGameFile:
11682         (void) StopLoadGameTimer();
11683         if (gameFileFP != NULL) {
11684             gameFileFP = NULL;
11685         }
11686         break;
11687       case EditPosition:
11688         EditPositionDone(TRUE);
11689         break;
11690       case AnalyzeMode:
11691       case AnalyzeFile:
11692         ExitAnalyzeMode();
11693         SendToProgram("force\n", &first);
11694         break;
11695       case TwoMachinesPlay:
11696         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11697         ResurrectChessProgram();
11698         SetUserThinkingEnables();
11699         break;
11700       case EndOfGame:
11701         ResurrectChessProgram();
11702         break;
11703       case IcsPlayingBlack:
11704       case IcsPlayingWhite:
11705         DisplayError(_("Warning: You are still playing a game"), 0);
11706         break;
11707       case IcsObserving:
11708         DisplayError(_("Warning: You are still observing a game"), 0);
11709         break;
11710       case IcsExamining:
11711         DisplayError(_("Warning: You are still examining a game"), 0);
11712         break;
11713       case IcsIdle:
11714         break;
11715       case EditGame:
11716       default:
11717         return;
11718     }
11719     
11720     pausing = FALSE;
11721     StopClocks();
11722     first.offeredDraw = second.offeredDraw = 0;
11723
11724     if (gameMode == PlayFromGameFile) {
11725         whiteTimeRemaining = timeRemaining[0][currentMove];
11726         blackTimeRemaining = timeRemaining[1][currentMove];
11727         DisplayTitle("");
11728     }
11729
11730     if (gameMode == MachinePlaysWhite ||
11731         gameMode == MachinePlaysBlack ||
11732         gameMode == TwoMachinesPlay ||
11733         gameMode == EndOfGame) {
11734         i = forwardMostMove;
11735         while (i > currentMove) {
11736             SendToProgram("undo\n", &first);
11737             i--;
11738         }
11739         whiteTimeRemaining = timeRemaining[0][currentMove];
11740         blackTimeRemaining = timeRemaining[1][currentMove];
11741         DisplayBothClocks();
11742         if (whiteFlag || blackFlag) {
11743             whiteFlag = blackFlag = 0;
11744         }
11745         DisplayTitle("");
11746     }           
11747     
11748     gameMode = EditGame;
11749     ModeHighlight();
11750     SetGameInfo();
11751 }
11752
11753
11754 void
11755 EditPositionEvent()
11756 {
11757     if (gameMode == EditPosition) {
11758         EditGameEvent();
11759         return;
11760     }
11761     
11762     EditGameEvent();
11763     if (gameMode != EditGame) return;
11764     
11765     gameMode = EditPosition;
11766     ModeHighlight();
11767     SetGameInfo();
11768     if (currentMove > 0)
11769       CopyBoard(boards[0], boards[currentMove]);
11770     
11771     blackPlaysFirst = !WhiteOnMove(currentMove);
11772     ResetClocks();
11773     currentMove = forwardMostMove = backwardMostMove = 0;
11774     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11775     DisplayMove(-1);
11776 }
11777
11778 void
11779 ExitAnalyzeMode()
11780 {
11781     /* [DM] icsEngineAnalyze - possible call from other functions */
11782     if (appData.icsEngineAnalyze) {
11783         appData.icsEngineAnalyze = FALSE;
11784
11785         DisplayMessage("",_("Close ICS engine analyze..."));
11786     }
11787     if (first.analysisSupport && first.analyzing) {
11788       SendToProgram("exit\n", &first);
11789       first.analyzing = FALSE;
11790     }
11791     thinkOutput[0] = NULLCHAR;
11792 }
11793
11794 void
11795 EditPositionDone(Boolean fakeRights)
11796 {
11797     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11798
11799     startedFromSetupPosition = TRUE;
11800     InitChessProgram(&first, FALSE);
11801     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11802       boards[0][EP_STATUS] = EP_NONE;
11803       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11804     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11805         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11806         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11807       } else boards[0][CASTLING][2] = NoRights;
11808     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11809         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11810         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11811       } else boards[0][CASTLING][5] = NoRights;
11812     }
11813     SendToProgram("force\n", &first);
11814     if (blackPlaysFirst) {
11815         strcpy(moveList[0], "");
11816         strcpy(parseList[0], "");
11817         currentMove = forwardMostMove = backwardMostMove = 1;
11818         CopyBoard(boards[1], boards[0]);
11819     } else {
11820         currentMove = forwardMostMove = backwardMostMove = 0;
11821     }
11822     SendBoard(&first, forwardMostMove);
11823     if (appData.debugMode) {
11824         fprintf(debugFP, "EditPosDone\n");
11825     }
11826     DisplayTitle("");
11827     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11828     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11829     gameMode = EditGame;
11830     ModeHighlight();
11831     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11832     ClearHighlights(); /* [AS] */
11833 }
11834
11835 /* Pause for `ms' milliseconds */
11836 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11837 void
11838 TimeDelay(ms)
11839      long ms;
11840 {
11841     TimeMark m1, m2;
11842
11843     GetTimeMark(&m1);
11844     do {
11845         GetTimeMark(&m2);
11846     } while (SubtractTimeMarks(&m2, &m1) < ms);
11847 }
11848
11849 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11850 void
11851 SendMultiLineToICS(buf)
11852      char *buf;
11853 {
11854     char temp[MSG_SIZ+1], *p;
11855     int len;
11856
11857     len = strlen(buf);
11858     if (len > MSG_SIZ)
11859       len = MSG_SIZ;
11860   
11861     strncpy(temp, buf, len);
11862     temp[len] = 0;
11863
11864     p = temp;
11865     while (*p) {
11866         if (*p == '\n' || *p == '\r')
11867           *p = ' ';
11868         ++p;
11869     }
11870
11871     strcat(temp, "\n");
11872     SendToICS(temp);
11873     SendToPlayer(temp, strlen(temp));
11874 }
11875
11876 void
11877 SetWhiteToPlayEvent()
11878 {
11879     if (gameMode == EditPosition) {
11880         blackPlaysFirst = FALSE;
11881         DisplayBothClocks();    /* works because currentMove is 0 */
11882     } else if (gameMode == IcsExamining) {
11883         SendToICS(ics_prefix);
11884         SendToICS("tomove white\n");
11885     }
11886 }
11887
11888 void
11889 SetBlackToPlayEvent()
11890 {
11891     if (gameMode == EditPosition) {
11892         blackPlaysFirst = TRUE;
11893         currentMove = 1;        /* kludge */
11894         DisplayBothClocks();
11895         currentMove = 0;
11896     } else if (gameMode == IcsExamining) {
11897         SendToICS(ics_prefix);
11898         SendToICS("tomove black\n");
11899     }
11900 }
11901
11902 void
11903 EditPositionMenuEvent(selection, x, y)
11904      ChessSquare selection;
11905      int x, y;
11906 {
11907     char buf[MSG_SIZ];
11908     ChessSquare piece = boards[0][y][x];
11909
11910     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11911
11912     switch (selection) {
11913       case ClearBoard:
11914         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11915             SendToICS(ics_prefix);
11916             SendToICS("bsetup clear\n");
11917         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11918             SendToICS(ics_prefix);
11919             SendToICS("clearboard\n");
11920         } else {
11921             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11922                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11923                 for (y = 0; y < BOARD_HEIGHT; y++) {
11924                     if (gameMode == IcsExamining) {
11925                         if (boards[currentMove][y][x] != EmptySquare) {
11926                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11927                                     AAA + x, ONE + y);
11928                             SendToICS(buf);
11929                         }
11930                     } else {
11931                         boards[0][y][x] = p;
11932                     }
11933                 }
11934             }
11935         }
11936         if (gameMode == EditPosition) {
11937             DrawPosition(FALSE, boards[0]);
11938         }
11939         break;
11940
11941       case WhitePlay:
11942         SetWhiteToPlayEvent();
11943         break;
11944
11945       case BlackPlay:
11946         SetBlackToPlayEvent();
11947         break;
11948
11949       case EmptySquare:
11950         if (gameMode == IcsExamining) {
11951             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11952             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11953             SendToICS(buf);
11954         } else {
11955             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11956                 if(x == BOARD_LEFT-2) {
11957                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11958                     boards[0][y][1] = 0;
11959                 } else
11960                 if(x == BOARD_RGHT+1) {
11961                     if(y >= gameInfo.holdingsSize) break;
11962                     boards[0][y][BOARD_WIDTH-2] = 0;
11963                 } else break;
11964             }
11965             boards[0][y][x] = EmptySquare;
11966             DrawPosition(FALSE, boards[0]);
11967         }
11968         break;
11969
11970       case PromotePiece:
11971         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11972            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11973             selection = (ChessSquare) (PROMOTED piece);
11974         } else if(piece == EmptySquare) selection = WhiteSilver;
11975         else selection = (ChessSquare)((int)piece - 1);
11976         goto defaultlabel;
11977
11978       case DemotePiece:
11979         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11980            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11981             selection = (ChessSquare) (DEMOTED piece);
11982         } else if(piece == EmptySquare) selection = BlackSilver;
11983         else selection = (ChessSquare)((int)piece + 1);       
11984         goto defaultlabel;
11985
11986       case WhiteQueen:
11987       case BlackQueen:
11988         if(gameInfo.variant == VariantShatranj ||
11989            gameInfo.variant == VariantXiangqi  ||
11990            gameInfo.variant == VariantCourier  ||
11991            gameInfo.variant == VariantMakruk     )
11992             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11993         goto defaultlabel;
11994
11995       case WhiteKing:
11996       case BlackKing:
11997         if(gameInfo.variant == VariantXiangqi)
11998             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11999         if(gameInfo.variant == VariantKnightmate)
12000             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12001       default:
12002         defaultlabel:
12003         if (gameMode == IcsExamining) {
12004             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12005             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12006                     PieceToChar(selection), AAA + x, ONE + y);
12007             SendToICS(buf);
12008         } else {
12009             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12010                 int n;
12011                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12012                     n = PieceToNumber(selection - BlackPawn);
12013                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12014                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12015                     boards[0][BOARD_HEIGHT-1-n][1]++;
12016                 } else
12017                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12018                     n = PieceToNumber(selection);
12019                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12020                     boards[0][n][BOARD_WIDTH-1] = selection;
12021                     boards[0][n][BOARD_WIDTH-2]++;
12022                 }
12023             } else
12024             boards[0][y][x] = selection;
12025             DrawPosition(TRUE, boards[0]);
12026         }
12027         break;
12028     }
12029 }
12030
12031
12032 void
12033 DropMenuEvent(selection, x, y)
12034      ChessSquare selection;
12035      int x, y;
12036 {
12037     ChessMove moveType;
12038
12039     switch (gameMode) {
12040       case IcsPlayingWhite:
12041       case MachinePlaysBlack:
12042         if (!WhiteOnMove(currentMove)) {
12043             DisplayMoveError(_("It is Black's turn"));
12044             return;
12045         }
12046         moveType = WhiteDrop;
12047         break;
12048       case IcsPlayingBlack:
12049       case MachinePlaysWhite:
12050         if (WhiteOnMove(currentMove)) {
12051             DisplayMoveError(_("It is White's turn"));
12052             return;
12053         }
12054         moveType = BlackDrop;
12055         break;
12056       case EditGame:
12057         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12058         break;
12059       default:
12060         return;
12061     }
12062
12063     if (moveType == BlackDrop && selection < BlackPawn) {
12064       selection = (ChessSquare) ((int) selection
12065                                  + (int) BlackPawn - (int) WhitePawn);
12066     }
12067     if (boards[currentMove][y][x] != EmptySquare) {
12068         DisplayMoveError(_("That square is occupied"));
12069         return;
12070     }
12071
12072     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12073 }
12074
12075 void
12076 AcceptEvent()
12077 {
12078     /* Accept a pending offer of any kind from opponent */
12079     
12080     if (appData.icsActive) {
12081         SendToICS(ics_prefix);
12082         SendToICS("accept\n");
12083     } else if (cmailMsgLoaded) {
12084         if (currentMove == cmailOldMove &&
12085             commentList[cmailOldMove] != NULL &&
12086             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12087                    "Black offers a draw" : "White offers a draw")) {
12088             TruncateGame();
12089             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12090             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12091         } else {
12092             DisplayError(_("There is no pending offer on this move"), 0);
12093             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12094         }
12095     } else {
12096         /* Not used for offers from chess program */
12097     }
12098 }
12099
12100 void
12101 DeclineEvent()
12102 {
12103     /* Decline a pending offer of any kind from opponent */
12104     
12105     if (appData.icsActive) {
12106         SendToICS(ics_prefix);
12107         SendToICS("decline\n");
12108     } else if (cmailMsgLoaded) {
12109         if (currentMove == cmailOldMove &&
12110             commentList[cmailOldMove] != NULL &&
12111             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12112                    "Black offers a draw" : "White offers a draw")) {
12113 #ifdef NOTDEF
12114             AppendComment(cmailOldMove, "Draw declined", TRUE);
12115             DisplayComment(cmailOldMove - 1, "Draw declined");
12116 #endif /*NOTDEF*/
12117         } else {
12118             DisplayError(_("There is no pending offer on this move"), 0);
12119         }
12120     } else {
12121         /* Not used for offers from chess program */
12122     }
12123 }
12124
12125 void
12126 RematchEvent()
12127 {
12128     /* Issue ICS rematch command */
12129     if (appData.icsActive) {
12130         SendToICS(ics_prefix);
12131         SendToICS("rematch\n");
12132     }
12133 }
12134
12135 void
12136 CallFlagEvent()
12137 {
12138     /* Call your opponent's flag (claim a win on time) */
12139     if (appData.icsActive) {
12140         SendToICS(ics_prefix);
12141         SendToICS("flag\n");
12142     } else {
12143         switch (gameMode) {
12144           default:
12145             return;
12146           case MachinePlaysWhite:
12147             if (whiteFlag) {
12148                 if (blackFlag)
12149                   GameEnds(GameIsDrawn, "Both players ran out of time",
12150                            GE_PLAYER);
12151                 else
12152                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12153             } else {
12154                 DisplayError(_("Your opponent is not out of time"), 0);
12155             }
12156             break;
12157           case MachinePlaysBlack:
12158             if (blackFlag) {
12159                 if (whiteFlag)
12160                   GameEnds(GameIsDrawn, "Both players ran out of time",
12161                            GE_PLAYER);
12162                 else
12163                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12164             } else {
12165                 DisplayError(_("Your opponent is not out of time"), 0);
12166             }
12167             break;
12168         }
12169     }
12170 }
12171
12172 void
12173 DrawEvent()
12174 {
12175     /* Offer draw or accept pending draw offer from opponent */
12176     
12177     if (appData.icsActive) {
12178         /* Note: tournament rules require draw offers to be
12179            made after you make your move but before you punch
12180            your clock.  Currently ICS doesn't let you do that;
12181            instead, you immediately punch your clock after making
12182            a move, but you can offer a draw at any time. */
12183         
12184         SendToICS(ics_prefix);
12185         SendToICS("draw\n");
12186         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12187     } else if (cmailMsgLoaded) {
12188         if (currentMove == cmailOldMove &&
12189             commentList[cmailOldMove] != NULL &&
12190             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12191                    "Black offers a draw" : "White offers a draw")) {
12192             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12193             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12194         } else if (currentMove == cmailOldMove + 1) {
12195             char *offer = WhiteOnMove(cmailOldMove) ?
12196               "White offers a draw" : "Black offers a draw";
12197             AppendComment(currentMove, offer, TRUE);
12198             DisplayComment(currentMove - 1, offer);
12199             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12200         } else {
12201             DisplayError(_("You must make your move before offering a draw"), 0);
12202             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12203         }
12204     } else if (first.offeredDraw) {
12205         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12206     } else {
12207         if (first.sendDrawOffers) {
12208             SendToProgram("draw\n", &first);
12209             userOfferedDraw = TRUE;
12210         }
12211     }
12212 }
12213
12214 void
12215 AdjournEvent()
12216 {
12217     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12218     
12219     if (appData.icsActive) {
12220         SendToICS(ics_prefix);
12221         SendToICS("adjourn\n");
12222     } else {
12223         /* Currently GNU Chess doesn't offer or accept Adjourns */
12224     }
12225 }
12226
12227
12228 void
12229 AbortEvent()
12230 {
12231     /* Offer Abort or accept pending Abort offer from opponent */
12232     
12233     if (appData.icsActive) {
12234         SendToICS(ics_prefix);
12235         SendToICS("abort\n");
12236     } else {
12237         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12238     }
12239 }
12240
12241 void
12242 ResignEvent()
12243 {
12244     /* Resign.  You can do this even if it's not your turn. */
12245     
12246     if (appData.icsActive) {
12247         SendToICS(ics_prefix);
12248         SendToICS("resign\n");
12249     } else {
12250         switch (gameMode) {
12251           case MachinePlaysWhite:
12252             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12253             break;
12254           case MachinePlaysBlack:
12255             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12256             break;
12257           case EditGame:
12258             if (cmailMsgLoaded) {
12259                 TruncateGame();
12260                 if (WhiteOnMove(cmailOldMove)) {
12261                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12262                 } else {
12263                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12264                 }
12265                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12266             }
12267             break;
12268           default:
12269             break;
12270         }
12271     }
12272 }
12273
12274
12275 void
12276 StopObservingEvent()
12277 {
12278     /* Stop observing current games */
12279     SendToICS(ics_prefix);
12280     SendToICS("unobserve\n");
12281 }
12282
12283 void
12284 StopExaminingEvent()
12285 {
12286     /* Stop observing current game */
12287     SendToICS(ics_prefix);
12288     SendToICS("unexamine\n");
12289 }
12290
12291 void
12292 ForwardInner(target)
12293      int target;
12294 {
12295     int limit;
12296
12297     if (appData.debugMode)
12298         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12299                 target, currentMove, forwardMostMove);
12300
12301     if (gameMode == EditPosition)
12302       return;
12303
12304     if (gameMode == PlayFromGameFile && !pausing)
12305       PauseEvent();
12306     
12307     if (gameMode == IcsExamining && pausing)
12308       limit = pauseExamForwardMostMove;
12309     else
12310       limit = forwardMostMove;
12311     
12312     if (target > limit) target = limit;
12313
12314     if (target > 0 && moveList[target - 1][0]) {
12315         int fromX, fromY, toX, toY;
12316         toX = moveList[target - 1][2] - AAA;
12317         toY = moveList[target - 1][3] - ONE;
12318         if (moveList[target - 1][1] == '@') {
12319             if (appData.highlightLastMove) {
12320                 SetHighlights(-1, -1, toX, toY);
12321             }
12322         } else {
12323             fromX = moveList[target - 1][0] - AAA;
12324             fromY = moveList[target - 1][1] - ONE;
12325             if (target == currentMove + 1) {
12326                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12327             }
12328             if (appData.highlightLastMove) {
12329                 SetHighlights(fromX, fromY, toX, toY);
12330             }
12331         }
12332     }
12333     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12334         gameMode == Training || gameMode == PlayFromGameFile || 
12335         gameMode == AnalyzeFile) {
12336         while (currentMove < target) {
12337             SendMoveToProgram(currentMove++, &first);
12338         }
12339     } else {
12340         currentMove = target;
12341     }
12342     
12343     if (gameMode == EditGame || gameMode == EndOfGame) {
12344         whiteTimeRemaining = timeRemaining[0][currentMove];
12345         blackTimeRemaining = timeRemaining[1][currentMove];
12346     }
12347     DisplayBothClocks();
12348     DisplayMove(currentMove - 1);
12349     DrawPosition(FALSE, boards[currentMove]);
12350     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12351     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12352         DisplayComment(currentMove - 1, commentList[currentMove]);
12353     }
12354 }
12355
12356
12357 void
12358 ForwardEvent()
12359 {
12360     if (gameMode == IcsExamining && !pausing) {
12361         SendToICS(ics_prefix);
12362         SendToICS("forward\n");
12363     } else {
12364         ForwardInner(currentMove + 1);
12365     }
12366 }
12367
12368 void
12369 ToEndEvent()
12370 {
12371     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12372         /* to optimze, we temporarily turn off analysis mode while we feed
12373          * the remaining moves to the engine. Otherwise we get analysis output
12374          * after each move.
12375          */ 
12376         if (first.analysisSupport) {
12377           SendToProgram("exit\nforce\n", &first);
12378           first.analyzing = FALSE;
12379         }
12380     }
12381         
12382     if (gameMode == IcsExamining && !pausing) {
12383         SendToICS(ics_prefix);
12384         SendToICS("forward 999999\n");
12385     } else {
12386         ForwardInner(forwardMostMove);
12387     }
12388
12389     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12390         /* we have fed all the moves, so reactivate analysis mode */
12391         SendToProgram("analyze\n", &first);
12392         first.analyzing = TRUE;
12393         /*first.maybeThinking = TRUE;*/
12394         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12395     }
12396 }
12397
12398 void
12399 BackwardInner(target)
12400      int target;
12401 {
12402     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12403
12404     if (appData.debugMode)
12405         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12406                 target, currentMove, forwardMostMove);
12407
12408     if (gameMode == EditPosition) return;
12409     if (currentMove <= backwardMostMove) {
12410         ClearHighlights();
12411         DrawPosition(full_redraw, boards[currentMove]);
12412         return;
12413     }
12414     if (gameMode == PlayFromGameFile && !pausing)
12415       PauseEvent();
12416     
12417     if (moveList[target][0]) {
12418         int fromX, fromY, toX, toY;
12419         toX = moveList[target][2] - AAA;
12420         toY = moveList[target][3] - ONE;
12421         if (moveList[target][1] == '@') {
12422             if (appData.highlightLastMove) {
12423                 SetHighlights(-1, -1, toX, toY);
12424             }
12425         } else {
12426             fromX = moveList[target][0] - AAA;
12427             fromY = moveList[target][1] - ONE;
12428             if (target == currentMove - 1) {
12429                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12430             }
12431             if (appData.highlightLastMove) {
12432                 SetHighlights(fromX, fromY, toX, toY);
12433             }
12434         }
12435     }
12436     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12437         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12438         while (currentMove > target) {
12439             SendToProgram("undo\n", &first);
12440             currentMove--;
12441         }
12442     } else {
12443         currentMove = target;
12444     }
12445     
12446     if (gameMode == EditGame || gameMode == EndOfGame) {
12447         whiteTimeRemaining = timeRemaining[0][currentMove];
12448         blackTimeRemaining = timeRemaining[1][currentMove];
12449     }
12450     DisplayBothClocks();
12451     DisplayMove(currentMove - 1);
12452     DrawPosition(full_redraw, boards[currentMove]);
12453     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12454     // [HGM] PV info: routine tests if comment empty
12455     DisplayComment(currentMove - 1, commentList[currentMove]);
12456 }
12457
12458 void
12459 BackwardEvent()
12460 {
12461     if (gameMode == IcsExamining && !pausing) {
12462         SendToICS(ics_prefix);
12463         SendToICS("backward\n");
12464     } else {
12465         BackwardInner(currentMove - 1);
12466     }
12467 }
12468
12469 void
12470 ToStartEvent()
12471 {
12472     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12473         /* to optimize, we temporarily turn off analysis mode while we undo
12474          * all the moves. Otherwise we get analysis output after each undo.
12475          */ 
12476         if (first.analysisSupport) {
12477           SendToProgram("exit\nforce\n", &first);
12478           first.analyzing = FALSE;
12479         }
12480     }
12481
12482     if (gameMode == IcsExamining && !pausing) {
12483         SendToICS(ics_prefix);
12484         SendToICS("backward 999999\n");
12485     } else {
12486         BackwardInner(backwardMostMove);
12487     }
12488
12489     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12490         /* we have fed all the moves, so reactivate analysis mode */
12491         SendToProgram("analyze\n", &first);
12492         first.analyzing = TRUE;
12493         /*first.maybeThinking = TRUE;*/
12494         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12495     }
12496 }
12497
12498 void
12499 ToNrEvent(int to)
12500 {
12501   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12502   if (to >= forwardMostMove) to = forwardMostMove;
12503   if (to <= backwardMostMove) to = backwardMostMove;
12504   if (to < currentMove) {
12505     BackwardInner(to);
12506   } else {
12507     ForwardInner(to);
12508   }
12509 }
12510
12511 void
12512 RevertEvent()
12513 {
12514     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12515         return;
12516     }
12517     if (gameMode != IcsExamining) {
12518         DisplayError(_("You are not examining a game"), 0);
12519         return;
12520     }
12521     if (pausing) {
12522         DisplayError(_("You can't revert while pausing"), 0);
12523         return;
12524     }
12525     SendToICS(ics_prefix);
12526     SendToICS("revert\n");
12527 }
12528
12529 void
12530 RetractMoveEvent()
12531 {
12532     switch (gameMode) {
12533       case MachinePlaysWhite:
12534       case MachinePlaysBlack:
12535         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12536             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12537             return;
12538         }
12539         if (forwardMostMove < 2) return;
12540         currentMove = forwardMostMove = forwardMostMove - 2;
12541         whiteTimeRemaining = timeRemaining[0][currentMove];
12542         blackTimeRemaining = timeRemaining[1][currentMove];
12543         DisplayBothClocks();
12544         DisplayMove(currentMove - 1);
12545         ClearHighlights();/*!! could figure this out*/
12546         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12547         SendToProgram("remove\n", &first);
12548         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12549         break;
12550
12551       case BeginningOfGame:
12552       default:
12553         break;
12554
12555       case IcsPlayingWhite:
12556       case IcsPlayingBlack:
12557         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12558             SendToICS(ics_prefix);
12559             SendToICS("takeback 2\n");
12560         } else {
12561             SendToICS(ics_prefix);
12562             SendToICS("takeback 1\n");
12563         }
12564         break;
12565     }
12566 }
12567
12568 void
12569 MoveNowEvent()
12570 {
12571     ChessProgramState *cps;
12572
12573     switch (gameMode) {
12574       case MachinePlaysWhite:
12575         if (!WhiteOnMove(forwardMostMove)) {
12576             DisplayError(_("It is your turn"), 0);
12577             return;
12578         }
12579         cps = &first;
12580         break;
12581       case MachinePlaysBlack:
12582         if (WhiteOnMove(forwardMostMove)) {
12583             DisplayError(_("It is your turn"), 0);
12584             return;
12585         }
12586         cps = &first;
12587         break;
12588       case TwoMachinesPlay:
12589         if (WhiteOnMove(forwardMostMove) ==
12590             (first.twoMachinesColor[0] == 'w')) {
12591             cps = &first;
12592         } else {
12593             cps = &second;
12594         }
12595         break;
12596       case BeginningOfGame:
12597       default:
12598         return;
12599     }
12600     SendToProgram("?\n", cps);
12601 }
12602
12603 void
12604 TruncateGameEvent()
12605 {
12606     EditGameEvent();
12607     if (gameMode != EditGame) return;
12608     TruncateGame();
12609 }
12610
12611 void
12612 TruncateGame()
12613 {
12614     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12615     if (forwardMostMove > currentMove) {
12616         if (gameInfo.resultDetails != NULL) {
12617             free(gameInfo.resultDetails);
12618             gameInfo.resultDetails = NULL;
12619             gameInfo.result = GameUnfinished;
12620         }
12621         forwardMostMove = currentMove;
12622         HistorySet(parseList, backwardMostMove, forwardMostMove,
12623                    currentMove-1);
12624     }
12625 }
12626
12627 void
12628 HintEvent()
12629 {
12630     if (appData.noChessProgram) return;
12631     switch (gameMode) {
12632       case MachinePlaysWhite:
12633         if (WhiteOnMove(forwardMostMove)) {
12634             DisplayError(_("Wait until your turn"), 0);
12635             return;
12636         }
12637         break;
12638       case BeginningOfGame:
12639       case MachinePlaysBlack:
12640         if (!WhiteOnMove(forwardMostMove)) {
12641             DisplayError(_("Wait until your turn"), 0);
12642             return;
12643         }
12644         break;
12645       default:
12646         DisplayError(_("No hint available"), 0);
12647         return;
12648     }
12649     SendToProgram("hint\n", &first);
12650     hintRequested = TRUE;
12651 }
12652
12653 void
12654 BookEvent()
12655 {
12656     if (appData.noChessProgram) return;
12657     switch (gameMode) {
12658       case MachinePlaysWhite:
12659         if (WhiteOnMove(forwardMostMove)) {
12660             DisplayError(_("Wait until your turn"), 0);
12661             return;
12662         }
12663         break;
12664       case BeginningOfGame:
12665       case MachinePlaysBlack:
12666         if (!WhiteOnMove(forwardMostMove)) {
12667             DisplayError(_("Wait until your turn"), 0);
12668             return;
12669         }
12670         break;
12671       case EditPosition:
12672         EditPositionDone(TRUE);
12673         break;
12674       case TwoMachinesPlay:
12675         return;
12676       default:
12677         break;
12678     }
12679     SendToProgram("bk\n", &first);
12680     bookOutput[0] = NULLCHAR;
12681     bookRequested = TRUE;
12682 }
12683
12684 void
12685 AboutGameEvent()
12686 {
12687     char *tags = PGNTags(&gameInfo);
12688     TagsPopUp(tags, CmailMsg());
12689     free(tags);
12690 }
12691
12692 /* end button procedures */
12693
12694 void
12695 PrintPosition(fp, move)
12696      FILE *fp;
12697      int move;
12698 {
12699     int i, j;
12700     
12701     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12702         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12703             char c = PieceToChar(boards[move][i][j]);
12704             fputc(c == 'x' ? '.' : c, fp);
12705             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12706         }
12707     }
12708     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12709       fprintf(fp, "white to play\n");
12710     else
12711       fprintf(fp, "black to play\n");
12712 }
12713
12714 void
12715 PrintOpponents(fp)
12716      FILE *fp;
12717 {
12718     if (gameInfo.white != NULL) {
12719         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12720     } else {
12721         fprintf(fp, "\n");
12722     }
12723 }
12724
12725 /* Find last component of program's own name, using some heuristics */
12726 void
12727 TidyProgramName(prog, host, buf)
12728      char *prog, *host, buf[MSG_SIZ];
12729 {
12730     char *p, *q;
12731     int local = (strcmp(host, "localhost") == 0);
12732     while (!local && (p = strchr(prog, ';')) != NULL) {
12733         p++;
12734         while (*p == ' ') p++;
12735         prog = p;
12736     }
12737     if (*prog == '"' || *prog == '\'') {
12738         q = strchr(prog + 1, *prog);
12739     } else {
12740         q = strchr(prog, ' ');
12741     }
12742     if (q == NULL) q = prog + strlen(prog);
12743     p = q;
12744     while (p >= prog && *p != '/' && *p != '\\') p--;
12745     p++;
12746     if(p == prog && *p == '"') p++;
12747     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12748     memcpy(buf, p, q - p);
12749     buf[q - p] = NULLCHAR;
12750     if (!local) {
12751         strcat(buf, "@");
12752         strcat(buf, host);
12753     }
12754 }
12755
12756 char *
12757 TimeControlTagValue()
12758 {
12759     char buf[MSG_SIZ];
12760     if (!appData.clockMode) {
12761         strcpy(buf, "-");
12762     } else if (movesPerSession > 0) {
12763         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12764     } else if (timeIncrement == 0) {
12765         sprintf(buf, "%ld", timeControl/1000);
12766     } else {
12767         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12768     }
12769     return StrSave(buf);
12770 }
12771
12772 void
12773 SetGameInfo()
12774 {
12775     /* This routine is used only for certain modes */
12776     VariantClass v = gameInfo.variant;
12777     ChessMove r = GameUnfinished;
12778     char *p = NULL;
12779
12780     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12781         r = gameInfo.result; 
12782         p = gameInfo.resultDetails; 
12783         gameInfo.resultDetails = NULL;
12784     }
12785     ClearGameInfo(&gameInfo);
12786     gameInfo.variant = v;
12787
12788     switch (gameMode) {
12789       case MachinePlaysWhite:
12790         gameInfo.event = StrSave( appData.pgnEventHeader );
12791         gameInfo.site = StrSave(HostName());
12792         gameInfo.date = PGNDate();
12793         gameInfo.round = StrSave("-");
12794         gameInfo.white = StrSave(first.tidy);
12795         gameInfo.black = StrSave(UserName());
12796         gameInfo.timeControl = TimeControlTagValue();
12797         break;
12798
12799       case MachinePlaysBlack:
12800         gameInfo.event = StrSave( appData.pgnEventHeader );
12801         gameInfo.site = StrSave(HostName());
12802         gameInfo.date = PGNDate();
12803         gameInfo.round = StrSave("-");
12804         gameInfo.white = StrSave(UserName());
12805         gameInfo.black = StrSave(first.tidy);
12806         gameInfo.timeControl = TimeControlTagValue();
12807         break;
12808
12809       case TwoMachinesPlay:
12810         gameInfo.event = StrSave( appData.pgnEventHeader );
12811         gameInfo.site = StrSave(HostName());
12812         gameInfo.date = PGNDate();
12813         if (matchGame > 0) {
12814             char buf[MSG_SIZ];
12815             sprintf(buf, "%d", matchGame);
12816             gameInfo.round = StrSave(buf);
12817         } else {
12818             gameInfo.round = StrSave("-");
12819         }
12820         if (first.twoMachinesColor[0] == 'w') {
12821             gameInfo.white = StrSave(first.tidy);
12822             gameInfo.black = StrSave(second.tidy);
12823         } else {
12824             gameInfo.white = StrSave(second.tidy);
12825             gameInfo.black = StrSave(first.tidy);
12826         }
12827         gameInfo.timeControl = TimeControlTagValue();
12828         break;
12829
12830       case EditGame:
12831         gameInfo.event = StrSave("Edited game");
12832         gameInfo.site = StrSave(HostName());
12833         gameInfo.date = PGNDate();
12834         gameInfo.round = StrSave("-");
12835         gameInfo.white = StrSave("-");
12836         gameInfo.black = StrSave("-");
12837         gameInfo.result = r;
12838         gameInfo.resultDetails = p;
12839         break;
12840
12841       case EditPosition:
12842         gameInfo.event = StrSave("Edited position");
12843         gameInfo.site = StrSave(HostName());
12844         gameInfo.date = PGNDate();
12845         gameInfo.round = StrSave("-");
12846         gameInfo.white = StrSave("-");
12847         gameInfo.black = StrSave("-");
12848         break;
12849
12850       case IcsPlayingWhite:
12851       case IcsPlayingBlack:
12852       case IcsObserving:
12853       case IcsExamining:
12854         break;
12855
12856       case PlayFromGameFile:
12857         gameInfo.event = StrSave("Game from non-PGN file");
12858         gameInfo.site = StrSave(HostName());
12859         gameInfo.date = PGNDate();
12860         gameInfo.round = StrSave("-");
12861         gameInfo.white = StrSave("?");
12862         gameInfo.black = StrSave("?");
12863         break;
12864
12865       default:
12866         break;
12867     }
12868 }
12869
12870 void
12871 ReplaceComment(index, text)
12872      int index;
12873      char *text;
12874 {
12875     int len;
12876
12877     while (*text == '\n') text++;
12878     len = strlen(text);
12879     while (len > 0 && text[len - 1] == '\n') len--;
12880
12881     if (commentList[index] != NULL)
12882       free(commentList[index]);
12883
12884     if (len == 0) {
12885         commentList[index] = NULL;
12886         return;
12887     }
12888   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12889       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12890       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12891     commentList[index] = (char *) malloc(len + 2);
12892     strncpy(commentList[index], text, len);
12893     commentList[index][len] = '\n';
12894     commentList[index][len + 1] = NULLCHAR;
12895   } else { 
12896     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12897     char *p;
12898     commentList[index] = (char *) malloc(len + 6);
12899     strcpy(commentList[index], "{\n");
12900     strncpy(commentList[index]+2, text, len);
12901     commentList[index][len+2] = NULLCHAR;
12902     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12903     strcat(commentList[index], "\n}\n");
12904   }
12905 }
12906
12907 void
12908 CrushCRs(text)
12909      char *text;
12910 {
12911   char *p = text;
12912   char *q = text;
12913   char ch;
12914
12915   do {
12916     ch = *p++;
12917     if (ch == '\r') continue;
12918     *q++ = ch;
12919   } while (ch != '\0');
12920 }
12921
12922 void
12923 AppendComment(index, text, addBraces)
12924      int index;
12925      char *text;
12926      Boolean addBraces; // [HGM] braces: tells if we should add {}
12927 {
12928     int oldlen, len;
12929     char *old;
12930
12931 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12932     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12933
12934     CrushCRs(text);
12935     while (*text == '\n') text++;
12936     len = strlen(text);
12937     while (len > 0 && text[len - 1] == '\n') len--;
12938
12939     if (len == 0) return;
12940
12941     if (commentList[index] != NULL) {
12942         old = commentList[index];
12943         oldlen = strlen(old);
12944         while(commentList[index][oldlen-1] ==  '\n')
12945           commentList[index][--oldlen] = NULLCHAR;
12946         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12947         strcpy(commentList[index], old);
12948         free(old);
12949         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12950         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12951           if(addBraces) addBraces = FALSE; else { text++; len--; }
12952           while (*text == '\n') { text++; len--; }
12953           commentList[index][--oldlen] = NULLCHAR;
12954       }
12955         if(addBraces) strcat(commentList[index], "\n{\n");
12956         else          strcat(commentList[index], "\n");
12957         strcat(commentList[index], text);
12958         if(addBraces) strcat(commentList[index], "\n}\n");
12959         else          strcat(commentList[index], "\n");
12960     } else {
12961         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12962         if(addBraces)
12963              strcpy(commentList[index], "{\n");
12964         else commentList[index][0] = NULLCHAR;
12965         strcat(commentList[index], text);
12966         strcat(commentList[index], "\n");
12967         if(addBraces) strcat(commentList[index], "}\n");
12968     }
12969 }
12970
12971 static char * FindStr( char * text, char * sub_text )
12972 {
12973     char * result = strstr( text, sub_text );
12974
12975     if( result != NULL ) {
12976         result += strlen( sub_text );
12977     }
12978
12979     return result;
12980 }
12981
12982 /* [AS] Try to extract PV info from PGN comment */
12983 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12984 char *GetInfoFromComment( int index, char * text )
12985 {
12986     char * sep = text;
12987
12988     if( text != NULL && index > 0 ) {
12989         int score = 0;
12990         int depth = 0;
12991         int time = -1, sec = 0, deci;
12992         char * s_eval = FindStr( text, "[%eval " );
12993         char * s_emt = FindStr( text, "[%emt " );
12994
12995         if( s_eval != NULL || s_emt != NULL ) {
12996             /* New style */
12997             char delim;
12998
12999             if( s_eval != NULL ) {
13000                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13001                     return text;
13002                 }
13003
13004                 if( delim != ']' ) {
13005                     return text;
13006                 }
13007             }
13008
13009             if( s_emt != NULL ) {
13010             }
13011                 return text;
13012         }
13013         else {
13014             /* We expect something like: [+|-]nnn.nn/dd */
13015             int score_lo = 0;
13016
13017             if(*text != '{') return text; // [HGM] braces: must be normal comment
13018
13019             sep = strchr( text, '/' );
13020             if( sep == NULL || sep < (text+4) ) {
13021                 return text;
13022             }
13023
13024             time = -1; sec = -1; deci = -1;
13025             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13026                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13027                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13028                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13029                 return text;
13030             }
13031
13032             if( score_lo < 0 || score_lo >= 100 ) {
13033                 return text;
13034             }
13035
13036             if(sec >= 0) time = 600*time + 10*sec; else
13037             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13038
13039             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13040
13041             /* [HGM] PV time: now locate end of PV info */
13042             while( *++sep >= '0' && *sep <= '9'); // strip depth
13043             if(time >= 0)
13044             while( *++sep >= '0' && *sep <= '9'); // strip time
13045             if(sec >= 0)
13046             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13047             if(deci >= 0)
13048             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13049             while(*sep == ' ') sep++;
13050         }
13051
13052         if( depth <= 0 ) {
13053             return text;
13054         }
13055
13056         if( time < 0 ) {
13057             time = -1;
13058         }
13059
13060         pvInfoList[index-1].depth = depth;
13061         pvInfoList[index-1].score = score;
13062         pvInfoList[index-1].time  = 10*time; // centi-sec
13063         if(*sep == '}') *sep = 0; else *--sep = '{';
13064     }
13065     return sep;
13066 }
13067
13068 void
13069 SendToProgram(message, cps)
13070      char *message;
13071      ChessProgramState *cps;
13072 {
13073     int count, outCount, error;
13074     char buf[MSG_SIZ];
13075
13076     if (cps->pr == NULL) return;
13077     Attention(cps);
13078     
13079     if (appData.debugMode) {
13080         TimeMark now;
13081         GetTimeMark(&now);
13082         fprintf(debugFP, "%ld >%-6s: %s", 
13083                 SubtractTimeMarks(&now, &programStartTime),
13084                 cps->which, message);
13085     }
13086     
13087     count = strlen(message);
13088     outCount = OutputToProcess(cps->pr, message, count, &error);
13089     if (outCount < count && !exiting 
13090                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13091         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13092         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13093             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13094                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13095                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13096             } else {
13097                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13098             }
13099             gameInfo.resultDetails = StrSave(buf);
13100         }
13101         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13102     }
13103 }
13104
13105 void
13106 ReceiveFromProgram(isr, closure, message, count, error)
13107      InputSourceRef isr;
13108      VOIDSTAR closure;
13109      char *message;
13110      int count;
13111      int error;
13112 {
13113     char *end_str;
13114     char buf[MSG_SIZ];
13115     ChessProgramState *cps = (ChessProgramState *)closure;
13116
13117     if (isr != cps->isr) return; /* Killed intentionally */
13118     if (count <= 0) {
13119         if (count == 0) {
13120             sprintf(buf,
13121                     _("Error: %s chess program (%s) exited unexpectedly"),
13122                     cps->which, cps->program);
13123         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13124                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13125                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13126                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13127                 } else {
13128                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13129                 }
13130                 gameInfo.resultDetails = StrSave(buf);
13131             }
13132             RemoveInputSource(cps->isr);
13133             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13134         } else {
13135             sprintf(buf,
13136                     _("Error reading from %s chess program (%s)"),
13137                     cps->which, cps->program);
13138             RemoveInputSource(cps->isr);
13139
13140             /* [AS] Program is misbehaving badly... kill it */
13141             if( count == -2 ) {
13142                 DestroyChildProcess( cps->pr, 9 );
13143                 cps->pr = NoProc;
13144             }
13145
13146             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13147         }
13148         return;
13149     }
13150     
13151     if ((end_str = strchr(message, '\r')) != NULL)
13152       *end_str = NULLCHAR;
13153     if ((end_str = strchr(message, '\n')) != NULL)
13154       *end_str = NULLCHAR;
13155     
13156     if (appData.debugMode) {
13157         TimeMark now; int print = 1;
13158         char *quote = ""; char c; int i;
13159
13160         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13161                 char start = message[0];
13162                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13163                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13164                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13165                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13166                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13167                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13168                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13169                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13170                         { quote = "# "; print = (appData.engineComments == 2); }
13171                 message[0] = start; // restore original message
13172         }
13173         if(print) {
13174                 GetTimeMark(&now);
13175                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13176                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13177                         quote,
13178                         message);
13179         }
13180     }
13181
13182     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13183     if (appData.icsEngineAnalyze) {
13184         if (strstr(message, "whisper") != NULL ||
13185              strstr(message, "kibitz") != NULL || 
13186             strstr(message, "tellics") != NULL) return;
13187     }
13188
13189     HandleMachineMove(message, cps);
13190 }
13191
13192
13193 void
13194 SendTimeControl(cps, mps, tc, inc, sd, st)
13195      ChessProgramState *cps;
13196      int mps, inc, sd, st;
13197      long tc;
13198 {
13199     char buf[MSG_SIZ];
13200     int seconds;
13201
13202     if( timeControl_2 > 0 ) {
13203         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13204             tc = timeControl_2;
13205         }
13206     }
13207     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13208     inc /= cps->timeOdds;
13209     st  /= cps->timeOdds;
13210
13211     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13212
13213     if (st > 0) {
13214       /* Set exact time per move, normally using st command */
13215       if (cps->stKludge) {
13216         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13217         seconds = st % 60;
13218         if (seconds == 0) {
13219           sprintf(buf, "level 1 %d\n", st/60);
13220         } else {
13221           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13222         }
13223       } else {
13224         sprintf(buf, "st %d\n", st);
13225       }
13226     } else {
13227       /* Set conventional or incremental time control, using level command */
13228       if (seconds == 0) {
13229         /* Note old gnuchess bug -- minutes:seconds used to not work.
13230            Fixed in later versions, but still avoid :seconds
13231            when seconds is 0. */
13232         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13233       } else {
13234         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13235                 seconds, inc/1000);
13236       }
13237     }
13238     SendToProgram(buf, cps);
13239
13240     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13241     /* Orthogonally, limit search to given depth */
13242     if (sd > 0) {
13243       if (cps->sdKludge) {
13244         sprintf(buf, "depth\n%d\n", sd);
13245       } else {
13246         sprintf(buf, "sd %d\n", sd);
13247       }
13248       SendToProgram(buf, cps);
13249     }
13250
13251     if(cps->nps > 0) { /* [HGM] nps */
13252         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13253         else {
13254                 sprintf(buf, "nps %d\n", cps->nps);
13255               SendToProgram(buf, cps);
13256         }
13257     }
13258 }
13259
13260 ChessProgramState *WhitePlayer()
13261 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13262 {
13263     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13264        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13265         return &second;
13266     return &first;
13267 }
13268
13269 void
13270 SendTimeRemaining(cps, machineWhite)
13271      ChessProgramState *cps;
13272      int /*boolean*/ machineWhite;
13273 {
13274     char message[MSG_SIZ];
13275     long time, otime;
13276
13277     /* Note: this routine must be called when the clocks are stopped
13278        or when they have *just* been set or switched; otherwise
13279        it will be off by the time since the current tick started.
13280     */
13281     if (machineWhite) {
13282         time = whiteTimeRemaining / 10;
13283         otime = blackTimeRemaining / 10;
13284     } else {
13285         time = blackTimeRemaining / 10;
13286         otime = whiteTimeRemaining / 10;
13287     }
13288     /* [HGM] translate opponent's time by time-odds factor */
13289     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13290     if (appData.debugMode) {
13291         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13292     }
13293
13294     if (time <= 0) time = 1;
13295     if (otime <= 0) otime = 1;
13296     
13297     sprintf(message, "time %ld\n", time);
13298     SendToProgram(message, cps);
13299
13300     sprintf(message, "otim %ld\n", otime);
13301     SendToProgram(message, cps);
13302 }
13303
13304 int
13305 BoolFeature(p, name, loc, cps)
13306      char **p;
13307      char *name;
13308      int *loc;
13309      ChessProgramState *cps;
13310 {
13311   char buf[MSG_SIZ];
13312   int len = strlen(name);
13313   int val;
13314   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13315     (*p) += len + 1;
13316     sscanf(*p, "%d", &val);
13317     *loc = (val != 0);
13318     while (**p && **p != ' ') (*p)++;
13319     sprintf(buf, "accepted %s\n", name);
13320     SendToProgram(buf, cps);
13321     return TRUE;
13322   }
13323   return FALSE;
13324 }
13325
13326 int
13327 IntFeature(p, name, loc, cps)
13328      char **p;
13329      char *name;
13330      int *loc;
13331      ChessProgramState *cps;
13332 {
13333   char buf[MSG_SIZ];
13334   int len = strlen(name);
13335   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13336     (*p) += len + 1;
13337     sscanf(*p, "%d", loc);
13338     while (**p && **p != ' ') (*p)++;
13339     sprintf(buf, "accepted %s\n", name);
13340     SendToProgram(buf, cps);
13341     return TRUE;
13342   }
13343   return FALSE;
13344 }
13345
13346 int
13347 StringFeature(p, name, loc, cps)
13348      char **p;
13349      char *name;
13350      char loc[];
13351      ChessProgramState *cps;
13352 {
13353   char buf[MSG_SIZ];
13354   int len = strlen(name);
13355   if (strncmp((*p), name, len) == 0
13356       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13357     (*p) += len + 2;
13358     sscanf(*p, "%[^\"]", loc);
13359     while (**p && **p != '\"') (*p)++;
13360     if (**p == '\"') (*p)++;
13361     sprintf(buf, "accepted %s\n", name);
13362     SendToProgram(buf, cps);
13363     return TRUE;
13364   }
13365   return FALSE;
13366 }
13367
13368 int 
13369 ParseOption(Option *opt, ChessProgramState *cps)
13370 // [HGM] options: process the string that defines an engine option, and determine
13371 // name, type, default value, and allowed value range
13372 {
13373         char *p, *q, buf[MSG_SIZ];
13374         int n, min = (-1)<<31, max = 1<<31, def;
13375
13376         if(p = strstr(opt->name, " -spin ")) {
13377             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13378             if(max < min) max = min; // enforce consistency
13379             if(def < min) def = min;
13380             if(def > max) def = max;
13381             opt->value = def;
13382             opt->min = min;
13383             opt->max = max;
13384             opt->type = Spin;
13385         } else if((p = strstr(opt->name, " -slider "))) {
13386             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13387             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13388             if(max < min) max = min; // enforce consistency
13389             if(def < min) def = min;
13390             if(def > max) def = max;
13391             opt->value = def;
13392             opt->min = min;
13393             opt->max = max;
13394             opt->type = Spin; // Slider;
13395         } else if((p = strstr(opt->name, " -string "))) {
13396             opt->textValue = p+9;
13397             opt->type = TextBox;
13398         } else if((p = strstr(opt->name, " -file "))) {
13399             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13400             opt->textValue = p+7;
13401             opt->type = TextBox; // FileName;
13402         } else if((p = strstr(opt->name, " -path "))) {
13403             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13404             opt->textValue = p+7;
13405             opt->type = TextBox; // PathName;
13406         } else if(p = strstr(opt->name, " -check ")) {
13407             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13408             opt->value = (def != 0);
13409             opt->type = CheckBox;
13410         } else if(p = strstr(opt->name, " -combo ")) {
13411             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13412             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13413             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13414             opt->value = n = 0;
13415             while(q = StrStr(q, " /// ")) {
13416                 n++; *q = 0;    // count choices, and null-terminate each of them
13417                 q += 5;
13418                 if(*q == '*') { // remember default, which is marked with * prefix
13419                     q++;
13420                     opt->value = n;
13421                 }
13422                 cps->comboList[cps->comboCnt++] = q;
13423             }
13424             cps->comboList[cps->comboCnt++] = NULL;
13425             opt->max = n + 1;
13426             opt->type = ComboBox;
13427         } else if(p = strstr(opt->name, " -button")) {
13428             opt->type = Button;
13429         } else if(p = strstr(opt->name, " -save")) {
13430             opt->type = SaveButton;
13431         } else return FALSE;
13432         *p = 0; // terminate option name
13433         // now look if the command-line options define a setting for this engine option.
13434         if(cps->optionSettings && cps->optionSettings[0])
13435             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13436         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13437                 sprintf(buf, "option %s", p);
13438                 if(p = strstr(buf, ",")) *p = 0;
13439                 strcat(buf, "\n");
13440                 SendToProgram(buf, cps);
13441         }
13442         return TRUE;
13443 }
13444
13445 void
13446 FeatureDone(cps, val)
13447      ChessProgramState* cps;
13448      int val;
13449 {
13450   DelayedEventCallback cb = GetDelayedEvent();
13451   if ((cb == InitBackEnd3 && cps == &first) ||
13452       (cb == TwoMachinesEventIfReady && cps == &second)) {
13453     CancelDelayedEvent();
13454     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13455   }
13456   cps->initDone = val;
13457 }
13458
13459 /* Parse feature command from engine */
13460 void
13461 ParseFeatures(args, cps)
13462      char* args;
13463      ChessProgramState *cps;  
13464 {
13465   char *p = args;
13466   char *q;
13467   int val;
13468   char buf[MSG_SIZ];
13469
13470   for (;;) {
13471     while (*p == ' ') p++;
13472     if (*p == NULLCHAR) return;
13473
13474     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13475     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13476     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13477     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13478     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13479     if (BoolFeature(&p, "reuse", &val, cps)) {
13480       /* Engine can disable reuse, but can't enable it if user said no */
13481       if (!val) cps->reuse = FALSE;
13482       continue;
13483     }
13484     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13485     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13486       if (gameMode == TwoMachinesPlay) {
13487         DisplayTwoMachinesTitle();
13488       } else {
13489         DisplayTitle("");
13490       }
13491       continue;
13492     }
13493     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13494     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13495     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13496     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13497     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13498     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13499     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13500     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13501     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13502     if (IntFeature(&p, "done", &val, cps)) {
13503       FeatureDone(cps, val);
13504       continue;
13505     }
13506     /* Added by Tord: */
13507     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13508     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13509     /* End of additions by Tord */
13510
13511     /* [HGM] added features: */
13512     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13513     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13514     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13515     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13516     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13517     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13518     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13519         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13520             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13521             SendToProgram(buf, cps);
13522             continue;
13523         }
13524         if(cps->nrOptions >= MAX_OPTIONS) {
13525             cps->nrOptions--;
13526             sprintf(buf, "%s engine has too many options\n", cps->which);
13527             DisplayError(buf, 0);
13528         }
13529         continue;
13530     }
13531     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13532     /* End of additions by HGM */
13533
13534     /* unknown feature: complain and skip */
13535     q = p;
13536     while (*q && *q != '=') q++;
13537     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13538     SendToProgram(buf, cps);
13539     p = q;
13540     if (*p == '=') {
13541       p++;
13542       if (*p == '\"') {
13543         p++;
13544         while (*p && *p != '\"') p++;
13545         if (*p == '\"') p++;
13546       } else {
13547         while (*p && *p != ' ') p++;
13548       }
13549     }
13550   }
13551
13552 }
13553
13554 void
13555 PeriodicUpdatesEvent(newState)
13556      int newState;
13557 {
13558     if (newState == appData.periodicUpdates)
13559       return;
13560
13561     appData.periodicUpdates=newState;
13562
13563     /* Display type changes, so update it now */
13564 //    DisplayAnalysis();
13565
13566     /* Get the ball rolling again... */
13567     if (newState) {
13568         AnalysisPeriodicEvent(1);
13569         StartAnalysisClock();
13570     }
13571 }
13572
13573 void
13574 PonderNextMoveEvent(newState)
13575      int newState;
13576 {
13577     if (newState == appData.ponderNextMove) return;
13578     if (gameMode == EditPosition) EditPositionDone(TRUE);
13579     if (newState) {
13580         SendToProgram("hard\n", &first);
13581         if (gameMode == TwoMachinesPlay) {
13582             SendToProgram("hard\n", &second);
13583         }
13584     } else {
13585         SendToProgram("easy\n", &first);
13586         thinkOutput[0] = NULLCHAR;
13587         if (gameMode == TwoMachinesPlay) {
13588             SendToProgram("easy\n", &second);
13589         }
13590     }
13591     appData.ponderNextMove = newState;
13592 }
13593
13594 void
13595 NewSettingEvent(option, command, value)
13596      char *command;
13597      int option, value;
13598 {
13599     char buf[MSG_SIZ];
13600
13601     if (gameMode == EditPosition) EditPositionDone(TRUE);
13602     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13603     SendToProgram(buf, &first);
13604     if (gameMode == TwoMachinesPlay) {
13605         SendToProgram(buf, &second);
13606     }
13607 }
13608
13609 void
13610 ShowThinkingEvent()
13611 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13612 {
13613     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13614     int newState = appData.showThinking
13615         // [HGM] thinking: other features now need thinking output as well
13616         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13617     
13618     if (oldState == newState) return;
13619     oldState = newState;
13620     if (gameMode == EditPosition) EditPositionDone(TRUE);
13621     if (oldState) {
13622         SendToProgram("post\n", &first);
13623         if (gameMode == TwoMachinesPlay) {
13624             SendToProgram("post\n", &second);
13625         }
13626     } else {
13627         SendToProgram("nopost\n", &first);
13628         thinkOutput[0] = NULLCHAR;
13629         if (gameMode == TwoMachinesPlay) {
13630             SendToProgram("nopost\n", &second);
13631         }
13632     }
13633 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13634 }
13635
13636 void
13637 AskQuestionEvent(title, question, replyPrefix, which)
13638      char *title; char *question; char *replyPrefix; char *which;
13639 {
13640   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13641   if (pr == NoProc) return;
13642   AskQuestion(title, question, replyPrefix, pr);
13643 }
13644
13645 void
13646 DisplayMove(moveNumber)
13647      int moveNumber;
13648 {
13649     char message[MSG_SIZ];
13650     char res[MSG_SIZ];
13651     char cpThinkOutput[MSG_SIZ];
13652
13653     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13654     
13655     if (moveNumber == forwardMostMove - 1 || 
13656         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13657
13658         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13659
13660         if (strchr(cpThinkOutput, '\n')) {
13661             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13662         }
13663     } else {
13664         *cpThinkOutput = NULLCHAR;
13665     }
13666
13667     /* [AS] Hide thinking from human user */
13668     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13669         *cpThinkOutput = NULLCHAR;
13670         if( thinkOutput[0] != NULLCHAR ) {
13671             int i;
13672
13673             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13674                 cpThinkOutput[i] = '.';
13675             }
13676             cpThinkOutput[i] = NULLCHAR;
13677             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13678         }
13679     }
13680
13681     if (moveNumber == forwardMostMove - 1 &&
13682         gameInfo.resultDetails != NULL) {
13683         if (gameInfo.resultDetails[0] == NULLCHAR) {
13684             sprintf(res, " %s", PGNResult(gameInfo.result));
13685         } else {
13686             sprintf(res, " {%s} %s",
13687                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13688         }
13689     } else {
13690         res[0] = NULLCHAR;
13691     }
13692
13693     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13694         DisplayMessage(res, cpThinkOutput);
13695     } else {
13696         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13697                 WhiteOnMove(moveNumber) ? " " : ".. ",
13698                 parseList[moveNumber], res);
13699         DisplayMessage(message, cpThinkOutput);
13700     }
13701 }
13702
13703 void
13704 DisplayComment(moveNumber, text)
13705      int moveNumber;
13706      char *text;
13707 {
13708     char title[MSG_SIZ];
13709     char buf[8000]; // comment can be long!
13710     int score, depth;
13711     
13712     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13713       strcpy(title, "Comment");
13714     } else {
13715       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13716               WhiteOnMove(moveNumber) ? " " : ".. ",
13717               parseList[moveNumber]);
13718     }
13719     // [HGM] PV info: display PV info together with (or as) comment
13720     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13721       if(text == NULL) text = "";                                           
13722       score = pvInfoList[moveNumber].score;
13723       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13724               depth, (pvInfoList[moveNumber].time+50)/100, text);
13725       text = buf;
13726     }
13727     if (text != NULL && (appData.autoDisplayComment || commentUp))
13728         CommentPopUp(title, text);
13729 }
13730
13731 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13732  * might be busy thinking or pondering.  It can be omitted if your
13733  * gnuchess is configured to stop thinking immediately on any user
13734  * input.  However, that gnuchess feature depends on the FIONREAD
13735  * ioctl, which does not work properly on some flavors of Unix.
13736  */
13737 void
13738 Attention(cps)
13739      ChessProgramState *cps;
13740 {
13741 #if ATTENTION
13742     if (!cps->useSigint) return;
13743     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13744     switch (gameMode) {
13745       case MachinePlaysWhite:
13746       case MachinePlaysBlack:
13747       case TwoMachinesPlay:
13748       case IcsPlayingWhite:
13749       case IcsPlayingBlack:
13750       case AnalyzeMode:
13751       case AnalyzeFile:
13752         /* Skip if we know it isn't thinking */
13753         if (!cps->maybeThinking) return;
13754         if (appData.debugMode)
13755           fprintf(debugFP, "Interrupting %s\n", cps->which);
13756         InterruptChildProcess(cps->pr);
13757         cps->maybeThinking = FALSE;
13758         break;
13759       default:
13760         break;
13761     }
13762 #endif /*ATTENTION*/
13763 }
13764
13765 int
13766 CheckFlags()
13767 {
13768     if (whiteTimeRemaining <= 0) {
13769         if (!whiteFlag) {
13770             whiteFlag = TRUE;
13771             if (appData.icsActive) {
13772                 if (appData.autoCallFlag &&
13773                     gameMode == IcsPlayingBlack && !blackFlag) {
13774                   SendToICS(ics_prefix);
13775                   SendToICS("flag\n");
13776                 }
13777             } else {
13778                 if (blackFlag) {
13779                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13780                 } else {
13781                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13782                     if (appData.autoCallFlag) {
13783                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13784                         return TRUE;
13785                     }
13786                 }
13787             }
13788         }
13789     }
13790     if (blackTimeRemaining <= 0) {
13791         if (!blackFlag) {
13792             blackFlag = TRUE;
13793             if (appData.icsActive) {
13794                 if (appData.autoCallFlag &&
13795                     gameMode == IcsPlayingWhite && !whiteFlag) {
13796                   SendToICS(ics_prefix);
13797                   SendToICS("flag\n");
13798                 }
13799             } else {
13800                 if (whiteFlag) {
13801                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13802                 } else {
13803                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13804                     if (appData.autoCallFlag) {
13805                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13806                         return TRUE;
13807                     }
13808                 }
13809             }
13810         }
13811     }
13812     return FALSE;
13813 }
13814
13815 void
13816 CheckTimeControl()
13817 {
13818     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13819         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13820
13821     /*
13822      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13823      */
13824     if ( !WhiteOnMove(forwardMostMove) )
13825         /* White made time control */
13826         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13827         /* [HGM] time odds: correct new time quota for time odds! */
13828                                             / WhitePlayer()->timeOdds;
13829       else
13830         /* Black made time control */
13831         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13832                                             / WhitePlayer()->other->timeOdds;
13833 }
13834
13835 void
13836 DisplayBothClocks()
13837 {
13838     int wom = gameMode == EditPosition ?
13839       !blackPlaysFirst : WhiteOnMove(currentMove);
13840     DisplayWhiteClock(whiteTimeRemaining, wom);
13841     DisplayBlackClock(blackTimeRemaining, !wom);
13842 }
13843
13844
13845 /* Timekeeping seems to be a portability nightmare.  I think everyone
13846    has ftime(), but I'm really not sure, so I'm including some ifdefs
13847    to use other calls if you don't.  Clocks will be less accurate if
13848    you have neither ftime nor gettimeofday.
13849 */
13850
13851 /* VS 2008 requires the #include outside of the function */
13852 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13853 #include <sys/timeb.h>
13854 #endif
13855
13856 /* Get the current time as a TimeMark */
13857 void
13858 GetTimeMark(tm)
13859      TimeMark *tm;
13860 {
13861 #if HAVE_GETTIMEOFDAY
13862
13863     struct timeval timeVal;
13864     struct timezone timeZone;
13865
13866     gettimeofday(&timeVal, &timeZone);
13867     tm->sec = (long) timeVal.tv_sec; 
13868     tm->ms = (int) (timeVal.tv_usec / 1000L);
13869
13870 #else /*!HAVE_GETTIMEOFDAY*/
13871 #if HAVE_FTIME
13872
13873 // include <sys/timeb.h> / moved to just above start of function
13874     struct timeb timeB;
13875
13876     ftime(&timeB);
13877     tm->sec = (long) timeB.time;
13878     tm->ms = (int) timeB.millitm;
13879
13880 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13881     tm->sec = (long) time(NULL);
13882     tm->ms = 0;
13883 #endif
13884 #endif
13885 }
13886
13887 /* Return the difference in milliseconds between two
13888    time marks.  We assume the difference will fit in a long!
13889 */
13890 long
13891 SubtractTimeMarks(tm2, tm1)
13892      TimeMark *tm2, *tm1;
13893 {
13894     return 1000L*(tm2->sec - tm1->sec) +
13895            (long) (tm2->ms - tm1->ms);
13896 }
13897
13898
13899 /*
13900  * Code to manage the game clocks.
13901  *
13902  * In tournament play, black starts the clock and then white makes a move.
13903  * We give the human user a slight advantage if he is playing white---the
13904  * clocks don't run until he makes his first move, so it takes zero time.
13905  * Also, we don't account for network lag, so we could get out of sync
13906  * with GNU Chess's clock -- but then, referees are always right.  
13907  */
13908
13909 static TimeMark tickStartTM;
13910 static long intendedTickLength;
13911
13912 long
13913 NextTickLength(timeRemaining)
13914      long timeRemaining;
13915 {
13916     long nominalTickLength, nextTickLength;
13917
13918     if (timeRemaining > 0L && timeRemaining <= 10000L)
13919       nominalTickLength = 100L;
13920     else
13921       nominalTickLength = 1000L;
13922     nextTickLength = timeRemaining % nominalTickLength;
13923     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13924
13925     return nextTickLength;
13926 }
13927
13928 /* Adjust clock one minute up or down */
13929 void
13930 AdjustClock(Boolean which, int dir)
13931 {
13932     if(which) blackTimeRemaining += 60000*dir;
13933     else      whiteTimeRemaining += 60000*dir;
13934     DisplayBothClocks();
13935 }
13936
13937 /* Stop clocks and reset to a fresh time control */
13938 void
13939 ResetClocks() 
13940 {
13941     (void) StopClockTimer();
13942     if (appData.icsActive) {
13943         whiteTimeRemaining = blackTimeRemaining = 0;
13944     } else if (searchTime) {
13945         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13946         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13947     } else { /* [HGM] correct new time quote for time odds */
13948         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13949         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13950     }
13951     if (whiteFlag || blackFlag) {
13952         DisplayTitle("");
13953         whiteFlag = blackFlag = FALSE;
13954     }
13955     DisplayBothClocks();
13956 }
13957
13958 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13959
13960 /* Decrement running clock by amount of time that has passed */
13961 void
13962 DecrementClocks()
13963 {
13964     long timeRemaining;
13965     long lastTickLength, fudge;
13966     TimeMark now;
13967
13968     if (!appData.clockMode) return;
13969     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13970         
13971     GetTimeMark(&now);
13972
13973     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13974
13975     /* Fudge if we woke up a little too soon */
13976     fudge = intendedTickLength - lastTickLength;
13977     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13978
13979     if (WhiteOnMove(forwardMostMove)) {
13980         if(whiteNPS >= 0) lastTickLength = 0;
13981         timeRemaining = whiteTimeRemaining -= lastTickLength;
13982         DisplayWhiteClock(whiteTimeRemaining - fudge,
13983                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13984     } else {
13985         if(blackNPS >= 0) lastTickLength = 0;
13986         timeRemaining = blackTimeRemaining -= lastTickLength;
13987         DisplayBlackClock(blackTimeRemaining - fudge,
13988                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13989     }
13990
13991     if (CheckFlags()) return;
13992         
13993     tickStartTM = now;
13994     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13995     StartClockTimer(intendedTickLength);
13996
13997     /* if the time remaining has fallen below the alarm threshold, sound the
13998      * alarm. if the alarm has sounded and (due to a takeback or time control
13999      * with increment) the time remaining has increased to a level above the
14000      * threshold, reset the alarm so it can sound again. 
14001      */
14002     
14003     if (appData.icsActive && appData.icsAlarm) {
14004
14005         /* make sure we are dealing with the user's clock */
14006         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14007                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14008            )) return;
14009
14010         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14011             alarmSounded = FALSE;
14012         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14013             PlayAlarmSound();
14014             alarmSounded = TRUE;
14015         }
14016     }
14017 }
14018
14019
14020 /* A player has just moved, so stop the previously running
14021    clock and (if in clock mode) start the other one.
14022    We redisplay both clocks in case we're in ICS mode, because
14023    ICS gives us an update to both clocks after every move.
14024    Note that this routine is called *after* forwardMostMove
14025    is updated, so the last fractional tick must be subtracted
14026    from the color that is *not* on move now.
14027 */
14028 void
14029 SwitchClocks()
14030 {
14031     long lastTickLength;
14032     TimeMark now;
14033     int flagged = FALSE;
14034
14035     GetTimeMark(&now);
14036
14037     if (StopClockTimer() && appData.clockMode) {
14038         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14039         if (WhiteOnMove(forwardMostMove)) {
14040             if(blackNPS >= 0) lastTickLength = 0;
14041             blackTimeRemaining -= lastTickLength;
14042            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14043 //         if(pvInfoList[forwardMostMove-1].time == -1)
14044                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14045                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14046         } else {
14047            if(whiteNPS >= 0) lastTickLength = 0;
14048            whiteTimeRemaining -= lastTickLength;
14049            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14050 //         if(pvInfoList[forwardMostMove-1].time == -1)
14051                  pvInfoList[forwardMostMove-1].time = 
14052                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14053         }
14054         flagged = CheckFlags();
14055     }
14056     CheckTimeControl();
14057
14058     if (flagged || !appData.clockMode) return;
14059
14060     switch (gameMode) {
14061       case MachinePlaysBlack:
14062       case MachinePlaysWhite:
14063       case BeginningOfGame:
14064         if (pausing) return;
14065         break;
14066
14067       case EditGame:
14068       case PlayFromGameFile:
14069       case IcsExamining:
14070         return;
14071
14072       default:
14073         break;
14074     }
14075
14076     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14077         if(WhiteOnMove(forwardMostMove))
14078              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14079         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14080     }
14081
14082     tickStartTM = now;
14083     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14084       whiteTimeRemaining : blackTimeRemaining);
14085     StartClockTimer(intendedTickLength);
14086 }
14087         
14088
14089 /* Stop both clocks */
14090 void
14091 StopClocks()
14092 {       
14093     long lastTickLength;
14094     TimeMark now;
14095
14096     if (!StopClockTimer()) return;
14097     if (!appData.clockMode) return;
14098
14099     GetTimeMark(&now);
14100
14101     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14102     if (WhiteOnMove(forwardMostMove)) {
14103         if(whiteNPS >= 0) lastTickLength = 0;
14104         whiteTimeRemaining -= lastTickLength;
14105         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14106     } else {
14107         if(blackNPS >= 0) lastTickLength = 0;
14108         blackTimeRemaining -= lastTickLength;
14109         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14110     }
14111     CheckFlags();
14112 }
14113         
14114 /* Start clock of player on move.  Time may have been reset, so
14115    if clock is already running, stop and restart it. */
14116 void
14117 StartClocks()
14118 {
14119     (void) StopClockTimer(); /* in case it was running already */
14120     DisplayBothClocks();
14121     if (CheckFlags()) return;
14122
14123     if (!appData.clockMode) return;
14124     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14125
14126     GetTimeMark(&tickStartTM);
14127     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14128       whiteTimeRemaining : blackTimeRemaining);
14129
14130    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14131     whiteNPS = blackNPS = -1; 
14132     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14133        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14134         whiteNPS = first.nps;
14135     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14136        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14137         blackNPS = first.nps;
14138     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14139         whiteNPS = second.nps;
14140     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14141         blackNPS = second.nps;
14142     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14143
14144     StartClockTimer(intendedTickLength);
14145 }
14146
14147 char *
14148 TimeString(ms)
14149      long ms;
14150 {
14151     long second, minute, hour, day;
14152     char *sign = "";
14153     static char buf[32];
14154     
14155     if (ms > 0 && ms <= 9900) {
14156       /* convert milliseconds to tenths, rounding up */
14157       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14158
14159       sprintf(buf, " %03.1f ", tenths/10.0);
14160       return buf;
14161     }
14162
14163     /* convert milliseconds to seconds, rounding up */
14164     /* use floating point to avoid strangeness of integer division
14165        with negative dividends on many machines */
14166     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14167
14168     if (second < 0) {
14169         sign = "-";
14170         second = -second;
14171     }
14172     
14173     day = second / (60 * 60 * 24);
14174     second = second % (60 * 60 * 24);
14175     hour = second / (60 * 60);
14176     second = second % (60 * 60);
14177     minute = second / 60;
14178     second = second % 60;
14179     
14180     if (day > 0)
14181       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14182               sign, day, hour, minute, second);
14183     else if (hour > 0)
14184       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14185     else
14186       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14187     
14188     return buf;
14189 }
14190
14191
14192 /*
14193  * This is necessary because some C libraries aren't ANSI C compliant yet.
14194  */
14195 char *
14196 StrStr(string, match)
14197      char *string, *match;
14198 {
14199     int i, length;
14200     
14201     length = strlen(match);
14202     
14203     for (i = strlen(string) - length; i >= 0; i--, string++)
14204       if (!strncmp(match, string, length))
14205         return string;
14206     
14207     return NULL;
14208 }
14209
14210 char *
14211 StrCaseStr(string, match)
14212      char *string, *match;
14213 {
14214     int i, j, length;
14215     
14216     length = strlen(match);
14217     
14218     for (i = strlen(string) - length; i >= 0; i--, string++) {
14219         for (j = 0; j < length; j++) {
14220             if (ToLower(match[j]) != ToLower(string[j]))
14221               break;
14222         }
14223         if (j == length) return string;
14224     }
14225
14226     return NULL;
14227 }
14228
14229 #ifndef _amigados
14230 int
14231 StrCaseCmp(s1, s2)
14232      char *s1, *s2;
14233 {
14234     char c1, c2;
14235     
14236     for (;;) {
14237         c1 = ToLower(*s1++);
14238         c2 = ToLower(*s2++);
14239         if (c1 > c2) return 1;
14240         if (c1 < c2) return -1;
14241         if (c1 == NULLCHAR) return 0;
14242     }
14243 }
14244
14245
14246 int
14247 ToLower(c)
14248      int c;
14249 {
14250     return isupper(c) ? tolower(c) : c;
14251 }
14252
14253
14254 int
14255 ToUpper(c)
14256      int c;
14257 {
14258     return islower(c) ? toupper(c) : c;
14259 }
14260 #endif /* !_amigados    */
14261
14262 char *
14263 StrSave(s)
14264      char *s;
14265 {
14266     char *ret;
14267
14268     if ((ret = (char *) malloc(strlen(s) + 1))) {
14269         strcpy(ret, s);
14270     }
14271     return ret;
14272 }
14273
14274 char *
14275 StrSavePtr(s, savePtr)
14276      char *s, **savePtr;
14277 {
14278     if (*savePtr) {
14279         free(*savePtr);
14280     }
14281     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14282         strcpy(*savePtr, s);
14283     }
14284     return(*savePtr);
14285 }
14286
14287 char *
14288 PGNDate()
14289 {
14290     time_t clock;
14291     struct tm *tm;
14292     char buf[MSG_SIZ];
14293
14294     clock = time((time_t *)NULL);
14295     tm = localtime(&clock);
14296     sprintf(buf, "%04d.%02d.%02d",
14297             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14298     return StrSave(buf);
14299 }
14300
14301
14302 char *
14303 PositionToFEN(move, overrideCastling)
14304      int move;
14305      char *overrideCastling;
14306 {
14307     int i, j, fromX, fromY, toX, toY;
14308     int whiteToPlay;
14309     char buf[128];
14310     char *p, *q;
14311     int emptycount;
14312     ChessSquare piece;
14313
14314     whiteToPlay = (gameMode == EditPosition) ?
14315       !blackPlaysFirst : (move % 2 == 0);
14316     p = buf;
14317
14318     /* Piece placement data */
14319     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14320         emptycount = 0;
14321         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14322             if (boards[move][i][j] == EmptySquare) {
14323                 emptycount++;
14324             } else { ChessSquare piece = boards[move][i][j];
14325                 if (emptycount > 0) {
14326                     if(emptycount<10) /* [HGM] can be >= 10 */
14327                         *p++ = '0' + emptycount;
14328                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14329                     emptycount = 0;
14330                 }
14331                 if(PieceToChar(piece) == '+') {
14332                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14333                     *p++ = '+';
14334                     piece = (ChessSquare)(DEMOTED piece);
14335                 } 
14336                 *p++ = PieceToChar(piece);
14337                 if(p[-1] == '~') {
14338                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14339                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14340                     *p++ = '~';
14341                 }
14342             }
14343         }
14344         if (emptycount > 0) {
14345             if(emptycount<10) /* [HGM] can be >= 10 */
14346                 *p++ = '0' + emptycount;
14347             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14348             emptycount = 0;
14349         }
14350         *p++ = '/';
14351     }
14352     *(p - 1) = ' ';
14353
14354     /* [HGM] print Crazyhouse or Shogi holdings */
14355     if( gameInfo.holdingsWidth ) {
14356         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14357         q = p;
14358         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14359             piece = boards[move][i][BOARD_WIDTH-1];
14360             if( piece != EmptySquare )
14361               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14362                   *p++ = PieceToChar(piece);
14363         }
14364         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14365             piece = boards[move][BOARD_HEIGHT-i-1][0];
14366             if( piece != EmptySquare )
14367               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14368                   *p++ = PieceToChar(piece);
14369         }
14370
14371         if( q == p ) *p++ = '-';
14372         *p++ = ']';
14373         *p++ = ' ';
14374     }
14375
14376     /* Active color */
14377     *p++ = whiteToPlay ? 'w' : 'b';
14378     *p++ = ' ';
14379
14380   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14381     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14382   } else {
14383   if(nrCastlingRights) {
14384      q = p;
14385      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14386        /* [HGM] write directly from rights */
14387            if(boards[move][CASTLING][2] != NoRights &&
14388               boards[move][CASTLING][0] != NoRights   )
14389                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14390            if(boards[move][CASTLING][2] != NoRights &&
14391               boards[move][CASTLING][1] != NoRights   )
14392                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14393            if(boards[move][CASTLING][5] != NoRights &&
14394               boards[move][CASTLING][3] != NoRights   )
14395                 *p++ = boards[move][CASTLING][3] + AAA;
14396            if(boards[move][CASTLING][5] != NoRights &&
14397               boards[move][CASTLING][4] != NoRights   )
14398                 *p++ = boards[move][CASTLING][4] + AAA;
14399      } else {
14400
14401         /* [HGM] write true castling rights */
14402         if( nrCastlingRights == 6 ) {
14403             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14404                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14405             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14406                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14407             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14408                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14409             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14410                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14411         }
14412      }
14413      if (q == p) *p++ = '-'; /* No castling rights */
14414      *p++ = ' ';
14415   }
14416
14417   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14418      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14419     /* En passant target square */
14420     if (move > backwardMostMove) {
14421         fromX = moveList[move - 1][0] - AAA;
14422         fromY = moveList[move - 1][1] - ONE;
14423         toX = moveList[move - 1][2] - AAA;
14424         toY = moveList[move - 1][3] - ONE;
14425         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14426             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14427             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14428             fromX == toX) {
14429             /* 2-square pawn move just happened */
14430             *p++ = toX + AAA;
14431             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14432         } else {
14433             *p++ = '-';
14434         }
14435     } else if(move == backwardMostMove) {
14436         // [HGM] perhaps we should always do it like this, and forget the above?
14437         if((signed char)boards[move][EP_STATUS] >= 0) {
14438             *p++ = boards[move][EP_STATUS] + AAA;
14439             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14440         } else {
14441             *p++ = '-';
14442         }
14443     } else {
14444         *p++ = '-';
14445     }
14446     *p++ = ' ';
14447   }
14448   }
14449
14450     /* [HGM] find reversible plies */
14451     {   int i = 0, j=move;
14452
14453         if (appData.debugMode) { int k;
14454             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14455             for(k=backwardMostMove; k<=forwardMostMove; k++)
14456                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14457
14458         }
14459
14460         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14461         if( j == backwardMostMove ) i += initialRulePlies;
14462         sprintf(p, "%d ", i);
14463         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14464     }
14465     /* Fullmove number */
14466     sprintf(p, "%d", (move / 2) + 1);
14467     
14468     return StrSave(buf);
14469 }
14470
14471 Boolean
14472 ParseFEN(board, blackPlaysFirst, fen)
14473     Board board;
14474      int *blackPlaysFirst;
14475      char *fen;
14476 {
14477     int i, j;
14478     char *p;
14479     int emptycount;
14480     ChessSquare piece;
14481
14482     p = fen;
14483
14484     /* [HGM] by default clear Crazyhouse holdings, if present */
14485     if(gameInfo.holdingsWidth) {
14486        for(i=0; i<BOARD_HEIGHT; i++) {
14487            board[i][0]             = EmptySquare; /* black holdings */
14488            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14489            board[i][1]             = (ChessSquare) 0; /* black counts */
14490            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14491        }
14492     }
14493
14494     /* Piece placement data */
14495     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14496         j = 0;
14497         for (;;) {
14498             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14499                 if (*p == '/') p++;
14500                 emptycount = gameInfo.boardWidth - j;
14501                 while (emptycount--)
14502                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14503                 break;
14504 #if(BOARD_FILES >= 10)
14505             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14506                 p++; emptycount=10;
14507                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14508                 while (emptycount--)
14509                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14510 #endif
14511             } else if (isdigit(*p)) {
14512                 emptycount = *p++ - '0';
14513                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14514                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14515                 while (emptycount--)
14516                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14517             } else if (*p == '+' || isalpha(*p)) {
14518                 if (j >= gameInfo.boardWidth) return FALSE;
14519                 if(*p=='+') {
14520                     piece = CharToPiece(*++p);
14521                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14522                     piece = (ChessSquare) (PROMOTED piece ); p++;
14523                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14524                 } else piece = CharToPiece(*p++);
14525
14526                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14527                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14528                     piece = (ChessSquare) (PROMOTED piece);
14529                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14530                     p++;
14531                 }
14532                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14533             } else {
14534                 return FALSE;
14535             }
14536         }
14537     }
14538     while (*p == '/' || *p == ' ') p++;
14539
14540     /* [HGM] look for Crazyhouse holdings here */
14541     while(*p==' ') p++;
14542     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14543         if(*p == '[') p++;
14544         if(*p == '-' ) *p++; /* empty holdings */ else {
14545             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14546             /* if we would allow FEN reading to set board size, we would   */
14547             /* have to add holdings and shift the board read so far here   */
14548             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14549                 *p++;
14550                 if((int) piece >= (int) BlackPawn ) {
14551                     i = (int)piece - (int)BlackPawn;
14552                     i = PieceToNumber((ChessSquare)i);
14553                     if( i >= gameInfo.holdingsSize ) return FALSE;
14554                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14555                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14556                 } else {
14557                     i = (int)piece - (int)WhitePawn;
14558                     i = PieceToNumber((ChessSquare)i);
14559                     if( i >= gameInfo.holdingsSize ) return FALSE;
14560                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14561                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14562                 }
14563             }
14564         }
14565         if(*p == ']') *p++;
14566     }
14567
14568     while(*p == ' ') p++;
14569
14570     /* Active color */
14571     switch (*p++) {
14572       case 'w':
14573         *blackPlaysFirst = FALSE;
14574         break;
14575       case 'b': 
14576         *blackPlaysFirst = TRUE;
14577         break;
14578       default:
14579         return FALSE;
14580     }
14581
14582     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14583     /* return the extra info in global variiables             */
14584
14585     /* set defaults in case FEN is incomplete */
14586     board[EP_STATUS] = EP_UNKNOWN;
14587     for(i=0; i<nrCastlingRights; i++ ) {
14588         board[CASTLING][i] =
14589             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14590     }   /* assume possible unless obviously impossible */
14591     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14592     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14593     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14594                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14595     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14596     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14597     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14598                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14599     FENrulePlies = 0;
14600
14601     while(*p==' ') p++;
14602     if(nrCastlingRights) {
14603       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14604           /* castling indicator present, so default becomes no castlings */
14605           for(i=0; i<nrCastlingRights; i++ ) {
14606                  board[CASTLING][i] = NoRights;
14607           }
14608       }
14609       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14610              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14611              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14612              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14613         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14614
14615         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14616             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14617             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14618         }
14619         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14620             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14621         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14622                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14623         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14624                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14625         switch(c) {
14626           case'K':
14627               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14628               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14629               board[CASTLING][2] = whiteKingFile;
14630               break;
14631           case'Q':
14632               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14633               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14634               board[CASTLING][2] = whiteKingFile;
14635               break;
14636           case'k':
14637               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14638               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14639               board[CASTLING][5] = blackKingFile;
14640               break;
14641           case'q':
14642               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14643               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14644               board[CASTLING][5] = blackKingFile;
14645           case '-':
14646               break;
14647           default: /* FRC castlings */
14648               if(c >= 'a') { /* black rights */
14649                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14650                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14651                   if(i == BOARD_RGHT) break;
14652                   board[CASTLING][5] = i;
14653                   c -= AAA;
14654                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14655                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14656                   if(c > i)
14657                       board[CASTLING][3] = c;
14658                   else
14659                       board[CASTLING][4] = c;
14660               } else { /* white rights */
14661                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14662                     if(board[0][i] == WhiteKing) break;
14663                   if(i == BOARD_RGHT) break;
14664                   board[CASTLING][2] = i;
14665                   c -= AAA - 'a' + 'A';
14666                   if(board[0][c] >= WhiteKing) break;
14667                   if(c > i)
14668                       board[CASTLING][0] = c;
14669                   else
14670                       board[CASTLING][1] = c;
14671               }
14672         }
14673       }
14674       for(i=0; i<nrCastlingRights; i++)
14675         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14676     if (appData.debugMode) {
14677         fprintf(debugFP, "FEN castling rights:");
14678         for(i=0; i<nrCastlingRights; i++)
14679         fprintf(debugFP, " %d", board[CASTLING][i]);
14680         fprintf(debugFP, "\n");
14681     }
14682
14683       while(*p==' ') p++;
14684     }
14685
14686     /* read e.p. field in games that know e.p. capture */
14687     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14688        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14689       if(*p=='-') {
14690         p++; board[EP_STATUS] = EP_NONE;
14691       } else {
14692          char c = *p++ - AAA;
14693
14694          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14695          if(*p >= '0' && *p <='9') *p++;
14696          board[EP_STATUS] = c;
14697       }
14698     }
14699
14700
14701     if(sscanf(p, "%d", &i) == 1) {
14702         FENrulePlies = i; /* 50-move ply counter */
14703         /* (The move number is still ignored)    */
14704     }
14705
14706     return TRUE;
14707 }
14708       
14709 void
14710 EditPositionPasteFEN(char *fen)
14711 {
14712   if (fen != NULL) {
14713     Board initial_position;
14714
14715     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14716       DisplayError(_("Bad FEN position in clipboard"), 0);
14717       return ;
14718     } else {
14719       int savedBlackPlaysFirst = blackPlaysFirst;
14720       EditPositionEvent();
14721       blackPlaysFirst = savedBlackPlaysFirst;
14722       CopyBoard(boards[0], initial_position);
14723       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14724       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14725       DisplayBothClocks();
14726       DrawPosition(FALSE, boards[currentMove]);
14727     }
14728   }
14729 }
14730
14731 static char cseq[12] = "\\   ";
14732
14733 Boolean set_cont_sequence(char *new_seq)
14734 {
14735     int len;
14736     Boolean ret;
14737
14738     // handle bad attempts to set the sequence
14739         if (!new_seq)
14740                 return 0; // acceptable error - no debug
14741
14742     len = strlen(new_seq);
14743     ret = (len > 0) && (len < sizeof(cseq));
14744     if (ret)
14745         strcpy(cseq, new_seq);
14746     else if (appData.debugMode)
14747         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14748     return ret;
14749 }
14750
14751 /*
14752     reformat a source message so words don't cross the width boundary.  internal
14753     newlines are not removed.  returns the wrapped size (no null character unless
14754     included in source message).  If dest is NULL, only calculate the size required
14755     for the dest buffer.  lp argument indicats line position upon entry, and it's
14756     passed back upon exit.
14757 */
14758 int wrap(char *dest, char *src, int count, int width, int *lp)
14759 {
14760     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14761
14762     cseq_len = strlen(cseq);
14763     old_line = line = *lp;
14764     ansi = len = clen = 0;
14765
14766     for (i=0; i < count; i++)
14767     {
14768         if (src[i] == '\033')
14769             ansi = 1;
14770
14771         // if we hit the width, back up
14772         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14773         {
14774             // store i & len in case the word is too long
14775             old_i = i, old_len = len;
14776
14777             // find the end of the last word
14778             while (i && src[i] != ' ' && src[i] != '\n')
14779             {
14780                 i--;
14781                 len--;
14782             }
14783
14784             // word too long?  restore i & len before splitting it
14785             if ((old_i-i+clen) >= width)
14786             {
14787                 i = old_i;
14788                 len = old_len;
14789             }
14790
14791             // extra space?
14792             if (i && src[i-1] == ' ')
14793                 len--;
14794
14795             if (src[i] != ' ' && src[i] != '\n')
14796             {
14797                 i--;
14798                 if (len)
14799                     len--;
14800             }
14801
14802             // now append the newline and continuation sequence
14803             if (dest)
14804                 dest[len] = '\n';
14805             len++;
14806             if (dest)
14807                 strncpy(dest+len, cseq, cseq_len);
14808             len += cseq_len;
14809             line = cseq_len;
14810             clen = cseq_len;
14811             continue;
14812         }
14813
14814         if (dest)
14815             dest[len] = src[i];
14816         len++;
14817         if (!ansi)
14818             line++;
14819         if (src[i] == '\n')
14820             line = 0;
14821         if (src[i] == 'm')
14822             ansi = 0;
14823     }
14824     if (dest && appData.debugMode)
14825     {
14826         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14827             count, width, line, len, *lp);
14828         show_bytes(debugFP, src, count);
14829         fprintf(debugFP, "\ndest: ");
14830         show_bytes(debugFP, dest, len);
14831         fprintf(debugFP, "\n");
14832     }
14833     *lp = dest ? line : old_line;
14834
14835     return len;
14836 }
14837
14838 // [HGM] vari: routines for shelving variations
14839
14840 void 
14841 PushTail(int firstMove, int lastMove)
14842 {
14843         int i, j, nrMoves = lastMove - firstMove;
14844
14845         if(appData.icsActive) { // only in local mode
14846                 forwardMostMove = currentMove; // mimic old ICS behavior
14847                 return;
14848         }
14849         if(storedGames >= MAX_VARIATIONS-1) return;
14850
14851         // push current tail of game on stack
14852         savedResult[storedGames] = gameInfo.result;
14853         savedDetails[storedGames] = gameInfo.resultDetails;
14854         gameInfo.resultDetails = NULL;
14855         savedFirst[storedGames] = firstMove;
14856         savedLast [storedGames] = lastMove;
14857         savedFramePtr[storedGames] = framePtr;
14858         framePtr -= nrMoves; // reserve space for the boards
14859         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14860             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14861             for(j=0; j<MOVE_LEN; j++)
14862                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14863             for(j=0; j<2*MOVE_LEN; j++)
14864                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14865             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14866             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14867             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14868             pvInfoList[firstMove+i-1].depth = 0;
14869             commentList[framePtr+i] = commentList[firstMove+i];
14870             commentList[firstMove+i] = NULL;
14871         }
14872
14873         storedGames++;
14874         forwardMostMove = currentMove; // truncte game so we can start variation
14875         if(storedGames == 1) GreyRevert(FALSE);
14876 }
14877
14878 Boolean
14879 PopTail(Boolean annotate)
14880 {
14881         int i, j, nrMoves;
14882         char buf[8000], moveBuf[20];
14883
14884         if(appData.icsActive) return FALSE; // only in local mode
14885         if(!storedGames) return FALSE; // sanity
14886
14887         storedGames--;
14888         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14889         nrMoves = savedLast[storedGames] - currentMove;
14890         if(annotate) {
14891                 int cnt = 10;
14892                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14893                 else strcpy(buf, "(");
14894                 for(i=currentMove; i<forwardMostMove; i++) {
14895                         if(WhiteOnMove(i))
14896                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14897                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14898                         strcat(buf, moveBuf);
14899                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14900                 }
14901                 strcat(buf, ")");
14902         }
14903         for(i=1; i<nrMoves; i++) { // copy last variation back
14904             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14905             for(j=0; j<MOVE_LEN; j++)
14906                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14907             for(j=0; j<2*MOVE_LEN; j++)
14908                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14909             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14910             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14911             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14912             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14913             commentList[currentMove+i] = commentList[framePtr+i];
14914             commentList[framePtr+i] = NULL;
14915         }
14916         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14917         framePtr = savedFramePtr[storedGames];
14918         gameInfo.result = savedResult[storedGames];
14919         if(gameInfo.resultDetails != NULL) {
14920             free(gameInfo.resultDetails);
14921       }
14922         gameInfo.resultDetails = savedDetails[storedGames];
14923         forwardMostMove = currentMove + nrMoves;
14924         if(storedGames == 0) GreyRevert(TRUE);
14925         return TRUE;
14926 }
14927
14928 void 
14929 CleanupTail()
14930 {       // remove all shelved variations
14931         int i;
14932         for(i=0; i<storedGames; i++) {
14933             if(savedDetails[i])
14934                 free(savedDetails[i]);
14935             savedDetails[i] = NULL;
14936         }
14937         for(i=framePtr; i<MAX_MOVES; i++) {
14938                 if(commentList[i]) free(commentList[i]);
14939                 commentList[i] = NULL;
14940         }
14941         framePtr = MAX_MOVES-1;
14942         storedGames = 0;
14943 }