Use right mouse button to view seek ads
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
254 int chattingPartner;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
256
257 /* States for ics_getting_history */
258 #define H_FALSE 0
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
264
265 /* whosays values for GameEnds */
266 #define GE_ICS 0
267 #define GE_ENGINE 1
268 #define GE_PLAYER 2
269 #define GE_FILE 3
270 #define GE_XBOARD 4
271 #define GE_ENGINE1 5
272 #define GE_ENGINE2 6
273
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
276
277 /* Different types of move when calling RegisterMove */
278 #define CMAIL_MOVE   0
279 #define CMAIL_RESIGN 1
280 #define CMAIL_DRAW   2
281 #define CMAIL_ACCEPT 3
282
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
287
288 /* Telnet protocol constants */
289 #define TN_WILL 0373
290 #define TN_WONT 0374
291 #define TN_DO   0375
292 #define TN_DONT 0376
293 #define TN_IAC  0377
294 #define TN_ECHO 0001
295 #define TN_SGA  0003
296 #define TN_PORT 23
297
298 /* [AS] */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
300 {
301     assert( dst != NULL );
302     assert( src != NULL );
303     assert( count > 0 );
304
305     strncpy( dst, src, count );
306     dst[ count-1 ] = '\0';
307     return dst;
308 }
309
310 /* Some compiler can't cast u64 to double
311  * This function do the job for us:
312
313  * We use the highest bit for cast, this only
314  * works if the highest bit is not
315  * in use (This should not happen)
316  *
317  * We used this for all compiler
318  */
319 double
320 u64ToDouble(u64 value)
321 {
322   double r;
323   u64 tmp = value & u64Const(0x7fffffffffffffff);
324   r = (double)(s64)tmp;
325   if (value & u64Const(0x8000000000000000))
326        r +=  9.2233720368547758080e18; /* 2^63 */
327  return r;
328 }
329
330 /* Fake up flags for now, as we aren't keeping track of castling
331    availability yet. [HGM] Change of logic: the flag now only
332    indicates the type of castlings allowed by the rule of the game.
333    The actual rights themselves are maintained in the array
334    castlingRights, as part of the game history, and are not probed
335    by this function.
336  */
337 int
338 PosFlags(index)
339 {
340   int flags = F_ALL_CASTLE_OK;
341   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342   switch (gameInfo.variant) {
343   case VariantSuicide:
344     flags &= ~F_ALL_CASTLE_OK;
345   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346     flags |= F_IGNORE_CHECK;
347   case VariantLosers:
348     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
349     break;
350   case VariantAtomic:
351     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
352     break;
353   case VariantKriegspiel:
354     flags |= F_KRIEGSPIEL_CAPTURE;
355     break;
356   case VariantCapaRandom: 
357   case VariantFischeRandom:
358     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359   case VariantNoCastle:
360   case VariantShatranj:
361   case VariantCourier:
362   case VariantMakruk:
363     flags &= ~F_ALL_CASTLE_OK;
364     break;
365   default:
366     break;
367   }
368   return flags;
369 }
370
371 FILE *gameFileFP, *debugFP;
372
373 /* 
374     [AS] Note: sometimes, the sscanf() function is used to parse the input
375     into a fixed-size buffer. Because of this, we must be prepared to
376     receive strings as long as the size of the input buffer, which is currently
377     set to 4K for Windows and 8K for the rest.
378     So, we must either allocate sufficiently large buffers here, or
379     reduce the size of the input buffer in the input reading part.
380 */
381
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
385
386 ChessProgramState first, second;
387
388 /* premove variables */
389 int premoveToX = 0;
390 int premoveToY = 0;
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
394 int gotPremove = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
397
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
400
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
428 int movesPerSession;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
433 int matchGame = 0;
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
437
438 /* animateTraining preserves the state of appData.animate
439  * when Training mode is activated. This allows the
440  * response to be animated when appData.animate == TRUE and
441  * appData.animateDragging == TRUE.
442  */
443 Boolean animateTraining;
444
445 GameInfo gameInfo;
446
447 AppData appData;
448
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char  initialRights[BOARD_FILES];
453 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int   initialRulePlies, FENrulePlies;
455 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
456 int loadFlag = 0; 
457 int shuffleOpenings;
458 int mute; // mute all sounds
459
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
463 int storedGames = 0;
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
469
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
473
474 ChessSquare  FIDEArray[2][BOARD_FILES] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackBishop, BlackKnight, BlackRook }
479 };
480
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackKing, BlackKnight, BlackRook }
486 };
487
488 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
489     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491     { BlackRook, BlackMan, BlackBishop, BlackQueen,
492         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
493 };
494
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
500 };
501
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 };
508
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackMan, BlackFerz,
513         BlackKing, BlackMan, BlackKnight, BlackRook }
514 };
515
516
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
523 };
524
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
530 };
531
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
534         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
536         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
537 };
538
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
541         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
543         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
544 };
545
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
548         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
550         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
551 };
552
553 #ifdef GOTHIC
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
556         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
558         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !GOTHIC
561 #define GothicArray CapablancaArray
562 #endif // !GOTHIC
563
564 #ifdef FALCON
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
567         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
569         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
570 };
571 #else // !FALCON
572 #define FalconArray CapablancaArray
573 #endif // !FALCON
574
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
581
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
588 };
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
592
593
594 Board initialPosition;
595
596
597 /* Convert str to a rating. Checks for special cases of "----",
598
599    "++++", etc. Also strips ()'s */
600 int
601 string_to_rating(str)
602   char *str;
603 {
604   while(*str && !isdigit(*str)) ++str;
605   if (!*str)
606     return 0;   /* One of the special "no rating" cases */
607   else
608     return atoi(str);
609 }
610
611 void
612 ClearProgramStats()
613 {
614     /* Init programStats */
615     programStats.movelist[0] = 0;
616     programStats.depth = 0;
617     programStats.nr_moves = 0;
618     programStats.moves_left = 0;
619     programStats.nodes = 0;
620     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
621     programStats.score = 0;
622     programStats.got_only_move = 0;
623     programStats.got_fail = 0;
624     programStats.line_is_book = 0;
625 }
626
627 void
628 InitBackEnd1()
629 {
630     int matched, min, sec;
631
632     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
633
634     GetTimeMark(&programStartTime);
635     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
636
637     ClearProgramStats();
638     programStats.ok_to_send = 1;
639     programStats.seen_stat = 0;
640
641     /*
642      * Initialize game list
643      */
644     ListNew(&gameList);
645
646
647     /*
648      * Internet chess server status
649      */
650     if (appData.icsActive) {
651         appData.matchMode = FALSE;
652         appData.matchGames = 0;
653 #if ZIPPY       
654         appData.noChessProgram = !appData.zippyPlay;
655 #else
656         appData.zippyPlay = FALSE;
657         appData.zippyTalk = FALSE;
658         appData.noChessProgram = TRUE;
659 #endif
660         if (*appData.icsHelper != NULLCHAR) {
661             appData.useTelnet = TRUE;
662             appData.telnetProgram = appData.icsHelper;
663         }
664     } else {
665         appData.zippyTalk = appData.zippyPlay = FALSE;
666     }
667
668     /* [AS] Initialize pv info list [HGM] and game state */
669     {
670         int i, j;
671
672         for( i=0; i<=framePtr; i++ ) {
673             pvInfoList[i].depth = -1;
674             boards[i][EP_STATUS] = EP_NONE;
675             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
676         }
677     }
678
679     /*
680      * Parse timeControl resource
681      */
682     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683                           appData.movesPerSession)) {
684         char buf[MSG_SIZ];
685         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686         DisplayFatalError(buf, 0, 2);
687     }
688
689     /*
690      * Parse searchTime resource
691      */
692     if (*appData.searchTime != NULLCHAR) {
693         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
694         if (matched == 1) {
695             searchTime = min * 60;
696         } else if (matched == 2) {
697             searchTime = min * 60 + sec;
698         } else {
699             char buf[MSG_SIZ];
700             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701             DisplayFatalError(buf, 0, 2);
702         }
703     }
704
705     /* [AS] Adjudication threshold */
706     adjudicateLossThreshold = appData.adjudicateLossThreshold;
707     
708     first.which = "first";
709     second.which = "second";
710     first.maybeThinking = second.maybeThinking = FALSE;
711     first.pr = second.pr = NoProc;
712     first.isr = second.isr = NULL;
713     first.sendTime = second.sendTime = 2;
714     first.sendDrawOffers = 1;
715     if (appData.firstPlaysBlack) {
716         first.twoMachinesColor = "black\n";
717         second.twoMachinesColor = "white\n";
718     } else {
719         first.twoMachinesColor = "white\n";
720         second.twoMachinesColor = "black\n";
721     }
722     first.program = appData.firstChessProgram;
723     second.program = appData.secondChessProgram;
724     first.host = appData.firstHost;
725     second.host = appData.secondHost;
726     first.dir = appData.firstDirectory;
727     second.dir = appData.secondDirectory;
728     first.other = &second;
729     second.other = &first;
730     first.initString = appData.initString;
731     second.initString = appData.secondInitString;
732     first.computerString = appData.firstComputerString;
733     second.computerString = appData.secondComputerString;
734     first.useSigint = second.useSigint = TRUE;
735     first.useSigterm = second.useSigterm = TRUE;
736     first.reuse = appData.reuseFirst;
737     second.reuse = appData.reuseSecond;
738     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
739     second.nps = appData.secondNPS;
740     first.useSetboard = second.useSetboard = FALSE;
741     first.useSAN = second.useSAN = FALSE;
742     first.usePing = second.usePing = FALSE;
743     first.lastPing = second.lastPing = 0;
744     first.lastPong = second.lastPong = 0;
745     first.usePlayother = second.usePlayother = FALSE;
746     first.useColors = second.useColors = TRUE;
747     first.useUsermove = second.useUsermove = FALSE;
748     first.sendICS = second.sendICS = FALSE;
749     first.sendName = second.sendName = appData.icsActive;
750     first.sdKludge = second.sdKludge = FALSE;
751     first.stKludge = second.stKludge = FALSE;
752     TidyProgramName(first.program, first.host, first.tidy);
753     TidyProgramName(second.program, second.host, second.tidy);
754     first.matchWins = second.matchWins = 0;
755     strcpy(first.variants, appData.variant);
756     strcpy(second.variants, appData.variant);
757     first.analysisSupport = second.analysisSupport = 2; /* detect */
758     first.analyzing = second.analyzing = FALSE;
759     first.initDone = second.initDone = FALSE;
760
761     /* New features added by Tord: */
762     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764     /* End of new features added by Tord. */
765     first.fenOverride  = appData.fenOverride1;
766     second.fenOverride = appData.fenOverride2;
767
768     /* [HGM] time odds: set factor for each machine */
769     first.timeOdds  = appData.firstTimeOdds;
770     second.timeOdds = appData.secondTimeOdds;
771     { float norm = 1;
772         if(appData.timeOddsMode) {
773             norm = first.timeOdds;
774             if(norm > second.timeOdds) norm = second.timeOdds;
775         }
776         first.timeOdds /= norm;
777         second.timeOdds /= norm;
778     }
779
780     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781     first.accumulateTC = appData.firstAccumulateTC;
782     second.accumulateTC = appData.secondAccumulateTC;
783     first.maxNrOfSessions = second.maxNrOfSessions = 1;
784
785     /* [HGM] debug */
786     first.debug = second.debug = FALSE;
787     first.supportsNPS = second.supportsNPS = UNKNOWN;
788
789     /* [HGM] options */
790     first.optionSettings  = appData.firstOptions;
791     second.optionSettings = appData.secondOptions;
792
793     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795     first.isUCI = appData.firstIsUCI; /* [AS] */
796     second.isUCI = appData.secondIsUCI; /* [AS] */
797     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
799
800     if (appData.firstProtocolVersion > PROTOVER ||
801         appData.firstProtocolVersion < 1) {
802       char buf[MSG_SIZ];
803       sprintf(buf, _("protocol version %d not supported"),
804               appData.firstProtocolVersion);
805       DisplayFatalError(buf, 0, 2);
806     } else {
807       first.protocolVersion = appData.firstProtocolVersion;
808     }
809
810     if (appData.secondProtocolVersion > PROTOVER ||
811         appData.secondProtocolVersion < 1) {
812       char buf[MSG_SIZ];
813       sprintf(buf, _("protocol version %d not supported"),
814               appData.secondProtocolVersion);
815       DisplayFatalError(buf, 0, 2);
816     } else {
817       second.protocolVersion = appData.secondProtocolVersion;
818     }
819
820     if (appData.icsActive) {
821         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
822 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824         appData.clockMode = FALSE;
825         first.sendTime = second.sendTime = 0;
826     }
827     
828 #if ZIPPY
829     /* Override some settings from environment variables, for backward
830        compatibility.  Unfortunately it's not feasible to have the env
831        vars just set defaults, at least in xboard.  Ugh.
832     */
833     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
834       ZippyInit();
835     }
836 #endif
837     
838     if (appData.noChessProgram) {
839         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840         sprintf(programVersion, "%s", PACKAGE_STRING);
841     } else {
842       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
845     }
846
847     if (!appData.icsActive) {
848       char buf[MSG_SIZ];
849       /* Check for variants that are supported only in ICS mode,
850          or not at all.  Some that are accepted here nevertheless
851          have bugs; see comments below.
852       */
853       VariantClass variant = StringToVariant(appData.variant);
854       switch (variant) {
855       case VariantBughouse:     /* need four players and two boards */
856       case VariantKriegspiel:   /* need to hide pieces and move details */
857       /* case VariantFischeRandom: (Fabien: moved below) */
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859         DisplayFatalError(buf, 0, 2);
860         return;
861
862       case VariantUnknown:
863       case VariantLoadable:
864       case Variant29:
865       case Variant30:
866       case Variant31:
867       case Variant32:
868       case Variant33:
869       case Variant34:
870       case Variant35:
871       case Variant36:
872       default:
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);
874         DisplayFatalError(buf, 0, 2);
875         return;
876
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */
879       case VariantGothic:     /* [HGM] should work */
880       case VariantCapablanca: /* [HGM] should work */
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */
882       case VariantShogi:      /* [HGM] drops not tested for legality */
883       case VariantKnightmate: /* [HGM] should work */
884       case VariantCylinder:   /* [HGM] untested */
885       case VariantFalcon:     /* [HGM] untested */
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887                                  offboard interposition not understood */
888       case VariantNormal:     /* definitely works! */
889       case VariantWildCastle: /* pieces not automatically shuffled */
890       case VariantNoCastle:   /* pieces not automatically shuffled */
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892       case VariantLosers:     /* should work except for win condition,
893                                  and doesn't know captures are mandatory */
894       case VariantSuicide:    /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantGiveaway:   /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantTwoKings:   /* should work */
899       case VariantAtomic:     /* should work except for win condition */
900       case Variant3Check:     /* should work except for win condition */
901       case VariantShatranj:   /* should work except for all win conditions */
902       case VariantMakruk:     /* should work except for daw countdown */
903       case VariantBerolina:   /* might work if TestLegality is off */
904       case VariantCapaRandom: /* should work */
905       case VariantJanus:      /* should work */
906       case VariantSuper:      /* experimental */
907       case VariantGreat:      /* experimental, requires legality testing to be off */
908         break;
909       }
910     }
911
912     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
913     InitEngineUCI( installDir, &second );
914 }
915
916 int NextIntegerFromString( char ** str, long * value )
917 {
918     int result = -1;
919     char * s = *str;
920
921     while( *s == ' ' || *s == '\t' ) {
922         s++;
923     }
924
925     *value = 0;
926
927     if( *s >= '0' && *s <= '9' ) {
928         while( *s >= '0' && *s <= '9' ) {
929             *value = *value * 10 + (*s - '0');
930             s++;
931         }
932
933         result = 0;
934     }
935
936     *str = s;
937
938     return result;
939 }
940
941 int NextTimeControlFromString( char ** str, long * value )
942 {
943     long temp;
944     int result = NextIntegerFromString( str, &temp );
945
946     if( result == 0 ) {
947         *value = temp * 60; /* Minutes */
948         if( **str == ':' ) {
949             (*str)++;
950             result = NextIntegerFromString( str, &temp );
951             *value += temp; /* Seconds */
952         }
953     }
954
955     return result;
956 }
957
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
960     int result = -1; long temp, temp2;
961
962     if(**str != '+') return -1; // old params remain in force!
963     (*str)++;
964     if( NextTimeControlFromString( str, &temp ) ) return -1;
965
966     if(**str != '/') {
967         /* time only: incremental or sudden-death time control */
968         if(**str == '+') { /* increment follows; read it */
969             (*str)++;
970             if(result = NextIntegerFromString( str, &temp2)) return -1;
971             *inc = temp2 * 1000;
972         } else *inc = 0;
973         *moves = 0; *tc = temp * 1000; 
974         return 0;
975     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
976
977     (*str)++; /* classical time control */
978     result = NextTimeControlFromString( str, &temp2);
979     if(result == 0) {
980         *moves = temp/60;
981         *tc    = temp2 * 1000;
982         *inc   = 0;
983     }
984     return result;
985 }
986
987 int GetTimeQuota(int movenr)
988 {   /* [HGM] get time to add from the multi-session time-control string */
989     int moves=1; /* kludge to force reading of first session */
990     long time, increment;
991     char *s = fullTimeControlString;
992
993     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
994     do {
995         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997         if(movenr == -1) return time;    /* last move before new session     */
998         if(!moves) return increment;     /* current session is incremental   */
999         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000     } while(movenr >= -1);               /* try again for next session       */
1001
1002     return 0; // no new time quota on this move
1003 }
1004
1005 int
1006 ParseTimeControl(tc, ti, mps)
1007      char *tc;
1008      int ti;
1009      int mps;
1010 {
1011   long tc1;
1012   long tc2;
1013   char buf[MSG_SIZ];
1014   
1015   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1016   if(ti > 0) {
1017     if(mps)
1018       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019     else sprintf(buf, "+%s+%d", tc, ti);
1020   } else {
1021     if(mps)
1022              sprintf(buf, "+%d/%s", mps, tc);
1023     else sprintf(buf, "+%s", tc);
1024   }
1025   fullTimeControlString = StrSave(buf);
1026   
1027   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1028     return FALSE;
1029   }
1030   
1031   if( *tc == '/' ) {
1032     /* Parse second time control */
1033     tc++;
1034     
1035     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1036       return FALSE;
1037     }
1038     
1039     if( tc2 == 0 ) {
1040       return FALSE;
1041     }
1042     
1043     timeControl_2 = tc2 * 1000;
1044   }
1045   else {
1046     timeControl_2 = 0;
1047   }
1048   
1049   if( tc1 == 0 ) {
1050     return FALSE;
1051   }
1052   
1053   timeControl = tc1 * 1000;
1054   
1055   if (ti >= 0) {
1056     timeIncrement = ti * 1000;  /* convert to ms */
1057     movesPerSession = 0;
1058   } else {
1059     timeIncrement = 0;
1060     movesPerSession = mps;
1061   }
1062   return TRUE;
1063 }
1064
1065 void
1066 InitBackEnd2()
1067 {
1068     if (appData.debugMode) {
1069         fprintf(debugFP, "%s\n", programVersion);
1070     }
1071
1072     set_cont_sequence(appData.wrapContSeq);
1073     if (appData.matchGames > 0) {
1074         appData.matchMode = TRUE;
1075     } else if (appData.matchMode) {
1076         appData.matchGames = 1;
1077     }
1078     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079         appData.matchGames = appData.sameColorGames;
1080     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1083     }
1084     Reset(TRUE, FALSE);
1085     if (appData.noChessProgram || first.protocolVersion == 1) {
1086       InitBackEnd3();
1087     } else {
1088       /* kludge: allow timeout for initial "feature" commands */
1089       FreezeUI();
1090       DisplayMessage("", _("Starting chess program"));
1091       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1092     }
1093 }
1094
1095 void
1096 InitBackEnd3 P((void))
1097 {
1098     GameMode initialMode;
1099     char buf[MSG_SIZ];
1100     int err;
1101
1102     InitChessProgram(&first, startedFromSetupPosition);
1103
1104
1105     if (appData.icsActive) {
1106 #ifdef WIN32
1107         /* [DM] Make a console window if needed [HGM] merged ifs */
1108         ConsoleCreate(); 
1109 #endif
1110         err = establish();
1111         if (err != 0) {
1112             if (*appData.icsCommPort != NULLCHAR) {
1113                 sprintf(buf, _("Could not open comm port %s"),  
1114                         appData.icsCommPort);
1115             } else {
1116                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1117                         appData.icsHost, appData.icsPort);
1118             }
1119             DisplayFatalError(buf, err, 1);
1120             return;
1121         }
1122         SetICSMode();
1123         telnetISR =
1124           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1125         fromUserISR =
1126           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1127         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1128             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1129     } else if (appData.noChessProgram) {
1130         SetNCPMode();
1131     } else {
1132         SetGNUMode();
1133     }
1134
1135     if (*appData.cmailGameName != NULLCHAR) {
1136         SetCmailMode();
1137         OpenLoopback(&cmailPR);
1138         cmailISR =
1139           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1140     }
1141     
1142     ThawUI();
1143     DisplayMessage("", "");
1144     if (StrCaseCmp(appData.initialMode, "") == 0) {
1145       initialMode = BeginningOfGame;
1146     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1147       initialMode = TwoMachinesPlay;
1148     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1149       initialMode = AnalyzeFile; 
1150     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1151       initialMode = AnalyzeMode;
1152     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1153       initialMode = MachinePlaysWhite;
1154     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1155       initialMode = MachinePlaysBlack;
1156     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1157       initialMode = EditGame;
1158     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1159       initialMode = EditPosition;
1160     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1161       initialMode = Training;
1162     } else {
1163       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1164       DisplayFatalError(buf, 0, 2);
1165       return;
1166     }
1167
1168     if (appData.matchMode) {
1169         /* Set up machine vs. machine match */
1170         if (appData.noChessProgram) {
1171             DisplayFatalError(_("Can't have a match with no chess programs"),
1172                               0, 2);
1173             return;
1174         }
1175         matchMode = TRUE;
1176         matchGame = 1;
1177         if (*appData.loadGameFile != NULLCHAR) {
1178             int index = appData.loadGameIndex; // [HGM] autoinc
1179             if(index<0) lastIndex = index = 1;
1180             if (!LoadGameFromFile(appData.loadGameFile,
1181                                   index,
1182                                   appData.loadGameFile, FALSE)) {
1183                 DisplayFatalError(_("Bad game file"), 0, 1);
1184                 return;
1185             }
1186         } else if (*appData.loadPositionFile != NULLCHAR) {
1187             int index = appData.loadPositionIndex; // [HGM] autoinc
1188             if(index<0) lastIndex = index = 1;
1189             if (!LoadPositionFromFile(appData.loadPositionFile,
1190                                       index,
1191                                       appData.loadPositionFile)) {
1192                 DisplayFatalError(_("Bad position file"), 0, 1);
1193                 return;
1194             }
1195         }
1196         TwoMachinesEvent();
1197     } else if (*appData.cmailGameName != NULLCHAR) {
1198         /* Set up cmail mode */
1199         ReloadCmailMsgEvent(TRUE);
1200     } else {
1201         /* Set up other modes */
1202         if (initialMode == AnalyzeFile) {
1203           if (*appData.loadGameFile == NULLCHAR) {
1204             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1205             return;
1206           }
1207         }
1208         if (*appData.loadGameFile != NULLCHAR) {
1209             (void) LoadGameFromFile(appData.loadGameFile,
1210                                     appData.loadGameIndex,
1211                                     appData.loadGameFile, TRUE);
1212         } else if (*appData.loadPositionFile != NULLCHAR) {
1213             (void) LoadPositionFromFile(appData.loadPositionFile,
1214                                         appData.loadPositionIndex,
1215                                         appData.loadPositionFile);
1216             /* [HGM] try to make self-starting even after FEN load */
1217             /* to allow automatic setup of fairy variants with wtm */
1218             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1219                 gameMode = BeginningOfGame;
1220                 setboardSpoiledMachineBlack = 1;
1221             }
1222             /* [HGM] loadPos: make that every new game uses the setup */
1223             /* from file as long as we do not switch variant          */
1224             if(!blackPlaysFirst) {
1225                 startedFromPositionFile = TRUE;
1226                 CopyBoard(filePosition, boards[0]);
1227             }
1228         }
1229         if (initialMode == AnalyzeMode) {
1230           if (appData.noChessProgram) {
1231             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1236             return;
1237           }
1238           AnalyzeModeEvent();
1239         } else if (initialMode == AnalyzeFile) {
1240           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1241           ShowThinkingEvent();
1242           AnalyzeFileEvent();
1243           AnalysisPeriodicEvent(1);
1244         } else if (initialMode == MachinePlaysWhite) {
1245           if (appData.noChessProgram) {
1246             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1247                               0, 2);
1248             return;
1249           }
1250           if (appData.icsActive) {
1251             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1252                               0, 2);
1253             return;
1254           }
1255           MachineWhiteEvent();
1256         } else if (initialMode == MachinePlaysBlack) {
1257           if (appData.noChessProgram) {
1258             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1259                               0, 2);
1260             return;
1261           }
1262           if (appData.icsActive) {
1263             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1264                               0, 2);
1265             return;
1266           }
1267           MachineBlackEvent();
1268         } else if (initialMode == TwoMachinesPlay) {
1269           if (appData.noChessProgram) {
1270             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1271                               0, 2);
1272             return;
1273           }
1274           if (appData.icsActive) {
1275             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1276                               0, 2);
1277             return;
1278           }
1279           TwoMachinesEvent();
1280         } else if (initialMode == EditGame) {
1281           EditGameEvent();
1282         } else if (initialMode == EditPosition) {
1283           EditPositionEvent();
1284         } else if (initialMode == Training) {
1285           if (*appData.loadGameFile == NULLCHAR) {
1286             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1287             return;
1288           }
1289           TrainingEvent();
1290         }
1291     }
1292 }
1293
1294 /*
1295  * Establish will establish a contact to a remote host.port.
1296  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1297  *  used to talk to the host.
1298  * Returns 0 if okay, error code if not.
1299  */
1300 int
1301 establish()
1302 {
1303     char buf[MSG_SIZ];
1304
1305     if (*appData.icsCommPort != NULLCHAR) {
1306         /* Talk to the host through a serial comm port */
1307         return OpenCommPort(appData.icsCommPort, &icsPR);
1308
1309     } else if (*appData.gateway != NULLCHAR) {
1310         if (*appData.remoteShell == NULLCHAR) {
1311             /* Use the rcmd protocol to run telnet program on a gateway host */
1312             snprintf(buf, sizeof(buf), "%s %s %s",
1313                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1314             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1315
1316         } else {
1317             /* Use the rsh program to run telnet program on a gateway host */
1318             if (*appData.remoteUser == NULLCHAR) {
1319                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1320                         appData.gateway, appData.telnetProgram,
1321                         appData.icsHost, appData.icsPort);
1322             } else {
1323                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1324                         appData.remoteShell, appData.gateway, 
1325                         appData.remoteUser, appData.telnetProgram,
1326                         appData.icsHost, appData.icsPort);
1327             }
1328             return StartChildProcess(buf, "", &icsPR);
1329
1330         }
1331     } else if (appData.useTelnet) {
1332         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1333
1334     } else {
1335         /* TCP socket interface differs somewhat between
1336            Unix and NT; handle details in the front end.
1337            */
1338         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1339     }
1340 }
1341
1342 void
1343 show_bytes(fp, buf, count)
1344      FILE *fp;
1345      char *buf;
1346      int count;
1347 {
1348     while (count--) {
1349         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1350             fprintf(fp, "\\%03o", *buf & 0xff);
1351         } else {
1352             putc(*buf, fp);
1353         }
1354         buf++;
1355     }
1356     fflush(fp);
1357 }
1358
1359 /* Returns an errno value */
1360 int
1361 OutputMaybeTelnet(pr, message, count, outError)
1362      ProcRef pr;
1363      char *message;
1364      int count;
1365      int *outError;
1366 {
1367     char buf[8192], *p, *q, *buflim;
1368     int left, newcount, outcount;
1369
1370     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1371         *appData.gateway != NULLCHAR) {
1372         if (appData.debugMode) {
1373             fprintf(debugFP, ">ICS: ");
1374             show_bytes(debugFP, message, count);
1375             fprintf(debugFP, "\n");
1376         }
1377         return OutputToProcess(pr, message, count, outError);
1378     }
1379
1380     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1381     p = message;
1382     q = buf;
1383     left = count;
1384     newcount = 0;
1385     while (left) {
1386         if (q >= buflim) {
1387             if (appData.debugMode) {
1388                 fprintf(debugFP, ">ICS: ");
1389                 show_bytes(debugFP, buf, newcount);
1390                 fprintf(debugFP, "\n");
1391             }
1392             outcount = OutputToProcess(pr, buf, newcount, outError);
1393             if (outcount < newcount) return -1; /* to be sure */
1394             q = buf;
1395             newcount = 0;
1396         }
1397         if (*p == '\n') {
1398             *q++ = '\r';
1399             newcount++;
1400         } else if (((unsigned char) *p) == TN_IAC) {
1401             *q++ = (char) TN_IAC;
1402             newcount ++;
1403         }
1404         *q++ = *p++;
1405         newcount++;
1406         left--;
1407     }
1408     if (appData.debugMode) {
1409         fprintf(debugFP, ">ICS: ");
1410         show_bytes(debugFP, buf, newcount);
1411         fprintf(debugFP, "\n");
1412     }
1413     outcount = OutputToProcess(pr, buf, newcount, outError);
1414     if (outcount < newcount) return -1; /* to be sure */
1415     return count;
1416 }
1417
1418 void
1419 read_from_player(isr, closure, message, count, error)
1420      InputSourceRef isr;
1421      VOIDSTAR closure;
1422      char *message;
1423      int count;
1424      int error;
1425 {
1426     int outError, outCount;
1427     static int gotEof = 0;
1428
1429     /* Pass data read from player on to ICS */
1430     if (count > 0) {
1431         gotEof = 0;
1432         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1433         if (outCount < count) {
1434             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1435         }
1436     } else if (count < 0) {
1437         RemoveInputSource(isr);
1438         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1439     } else if (gotEof++ > 0) {
1440         RemoveInputSource(isr);
1441         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1442     }
1443 }
1444
1445 void
1446 KeepAlive()
1447 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1448     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1449     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1450     SendToICS("date\n");
1451     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1452 }
1453
1454 /* added routine for printf style output to ics */
1455 void ics_printf(char *format, ...)
1456 {
1457     char buffer[MSG_SIZ];
1458     va_list args;
1459
1460     va_start(args, format);
1461     vsnprintf(buffer, sizeof(buffer), format, args);
1462     buffer[sizeof(buffer)-1] = '\0';
1463     SendToICS(buffer);
1464     va_end(args);
1465 }
1466
1467 void
1468 SendToICS(s)
1469      char *s;
1470 {
1471     int count, outCount, outError;
1472
1473     if (icsPR == NULL) return;
1474
1475     count = strlen(s);
1476     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1477     if (outCount < count) {
1478         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1479     }
1480 }
1481
1482 /* This is used for sending logon scripts to the ICS. Sending
1483    without a delay causes problems when using timestamp on ICC
1484    (at least on my machine). */
1485 void
1486 SendToICSDelayed(s,msdelay)
1487      char *s;
1488      long msdelay;
1489 {
1490     int count, outCount, outError;
1491
1492     if (icsPR == NULL) return;
1493
1494     count = strlen(s);
1495     if (appData.debugMode) {
1496         fprintf(debugFP, ">ICS: ");
1497         show_bytes(debugFP, s, count);
1498         fprintf(debugFP, "\n");
1499     }
1500     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1501                                       msdelay);
1502     if (outCount < count) {
1503         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1504     }
1505 }
1506
1507
1508 /* Remove all highlighting escape sequences in s
1509    Also deletes any suffix starting with '(' 
1510    */
1511 char *
1512 StripHighlightAndTitle(s)
1513      char *s;
1514 {
1515     static char retbuf[MSG_SIZ];
1516     char *p = retbuf;
1517
1518     while (*s != NULLCHAR) {
1519         while (*s == '\033') {
1520             while (*s != NULLCHAR && !isalpha(*s)) s++;
1521             if (*s != NULLCHAR) s++;
1522         }
1523         while (*s != NULLCHAR && *s != '\033') {
1524             if (*s == '(' || *s == '[') {
1525                 *p = NULLCHAR;
1526                 return retbuf;
1527             }
1528             *p++ = *s++;
1529         }
1530     }
1531     *p = NULLCHAR;
1532     return retbuf;
1533 }
1534
1535 /* Remove all highlighting escape sequences in s */
1536 char *
1537 StripHighlight(s)
1538      char *s;
1539 {
1540     static char retbuf[MSG_SIZ];
1541     char *p = retbuf;
1542
1543     while (*s != NULLCHAR) {
1544         while (*s == '\033') {
1545             while (*s != NULLCHAR && !isalpha(*s)) s++;
1546             if (*s != NULLCHAR) s++;
1547         }
1548         while (*s != NULLCHAR && *s != '\033') {
1549             *p++ = *s++;
1550         }
1551     }
1552     *p = NULLCHAR;
1553     return retbuf;
1554 }
1555
1556 char *variantNames[] = VARIANT_NAMES;
1557 char *
1558 VariantName(v)
1559      VariantClass v;
1560 {
1561     return variantNames[v];
1562 }
1563
1564
1565 /* Identify a variant from the strings the chess servers use or the
1566    PGN Variant tag names we use. */
1567 VariantClass
1568 StringToVariant(e)
1569      char *e;
1570 {
1571     char *p;
1572     int wnum = -1;
1573     VariantClass v = VariantNormal;
1574     int i, found = FALSE;
1575     char buf[MSG_SIZ];
1576
1577     if (!e) return v;
1578
1579     /* [HGM] skip over optional board-size prefixes */
1580     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1581         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1582         while( *e++ != '_');
1583     }
1584
1585     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1586         v = VariantNormal;
1587         found = TRUE;
1588     } else
1589     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1590       if (StrCaseStr(e, variantNames[i])) {
1591         v = (VariantClass) i;
1592         found = TRUE;
1593         break;
1594       }
1595     }
1596
1597     if (!found) {
1598       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1599           || StrCaseStr(e, "wild/fr") 
1600           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1601         v = VariantFischeRandom;
1602       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1603                  (i = 1, p = StrCaseStr(e, "w"))) {
1604         p += i;
1605         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1606         if (isdigit(*p)) {
1607           wnum = atoi(p);
1608         } else {
1609           wnum = -1;
1610         }
1611         switch (wnum) {
1612         case 0: /* FICS only, actually */
1613         case 1:
1614           /* Castling legal even if K starts on d-file */
1615           v = VariantWildCastle;
1616           break;
1617         case 2:
1618         case 3:
1619         case 4:
1620           /* Castling illegal even if K & R happen to start in
1621              normal positions. */
1622           v = VariantNoCastle;
1623           break;
1624         case 5:
1625         case 7:
1626         case 8:
1627         case 10:
1628         case 11:
1629         case 12:
1630         case 13:
1631         case 14:
1632         case 15:
1633         case 18:
1634         case 19:
1635           /* Castling legal iff K & R start in normal positions */
1636           v = VariantNormal;
1637           break;
1638         case 6:
1639         case 20:
1640         case 21:
1641           /* Special wilds for position setup; unclear what to do here */
1642           v = VariantLoadable;
1643           break;
1644         case 9:
1645           /* Bizarre ICC game */
1646           v = VariantTwoKings;
1647           break;
1648         case 16:
1649           v = VariantKriegspiel;
1650           break;
1651         case 17:
1652           v = VariantLosers;
1653           break;
1654         case 22:
1655           v = VariantFischeRandom;
1656           break;
1657         case 23:
1658           v = VariantCrazyhouse;
1659           break;
1660         case 24:
1661           v = VariantBughouse;
1662           break;
1663         case 25:
1664           v = Variant3Check;
1665           break;
1666         case 26:
1667           /* Not quite the same as FICS suicide! */
1668           v = VariantGiveaway;
1669           break;
1670         case 27:
1671           v = VariantAtomic;
1672           break;
1673         case 28:
1674           v = VariantShatranj;
1675           break;
1676
1677         /* Temporary names for future ICC types.  The name *will* change in 
1678            the next xboard/WinBoard release after ICC defines it. */
1679         case 29:
1680           v = Variant29;
1681           break;
1682         case 30:
1683           v = Variant30;
1684           break;
1685         case 31:
1686           v = Variant31;
1687           break;
1688         case 32:
1689           v = Variant32;
1690           break;
1691         case 33:
1692           v = Variant33;
1693           break;
1694         case 34:
1695           v = Variant34;
1696           break;
1697         case 35:
1698           v = Variant35;
1699           break;
1700         case 36:
1701           v = Variant36;
1702           break;
1703         case 37:
1704           v = VariantShogi;
1705           break;
1706         case 38:
1707           v = VariantXiangqi;
1708           break;
1709         case 39:
1710           v = VariantCourier;
1711           break;
1712         case 40:
1713           v = VariantGothic;
1714           break;
1715         case 41:
1716           v = VariantCapablanca;
1717           break;
1718         case 42:
1719           v = VariantKnightmate;
1720           break;
1721         case 43:
1722           v = VariantFairy;
1723           break;
1724         case 44:
1725           v = VariantCylinder;
1726           break;
1727         case 45:
1728           v = VariantFalcon;
1729           break;
1730         case 46:
1731           v = VariantCapaRandom;
1732           break;
1733         case 47:
1734           v = VariantBerolina;
1735           break;
1736         case 48:
1737           v = VariantJanus;
1738           break;
1739         case 49:
1740           v = VariantSuper;
1741           break;
1742         case 50:
1743           v = VariantGreat;
1744           break;
1745         case -1:
1746           /* Found "wild" or "w" in the string but no number;
1747              must assume it's normal chess. */
1748           v = VariantNormal;
1749           break;
1750         default:
1751           sprintf(buf, _("Unknown wild type %d"), wnum);
1752           DisplayError(buf, 0);
1753           v = VariantUnknown;
1754           break;
1755         }
1756       }
1757     }
1758     if (appData.debugMode) {
1759       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1760               e, wnum, VariantName(v));
1761     }
1762     return v;
1763 }
1764
1765 static int leftover_start = 0, leftover_len = 0;
1766 char star_match[STAR_MATCH_N][MSG_SIZ];
1767
1768 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1769    advance *index beyond it, and set leftover_start to the new value of
1770    *index; else return FALSE.  If pattern contains the character '*', it
1771    matches any sequence of characters not containing '\r', '\n', or the
1772    character following the '*' (if any), and the matched sequence(s) are
1773    copied into star_match.
1774    */
1775 int
1776 looking_at(buf, index, pattern)
1777      char *buf;
1778      int *index;
1779      char *pattern;
1780 {
1781     char *bufp = &buf[*index], *patternp = pattern;
1782     int star_count = 0;
1783     char *matchp = star_match[0];
1784     
1785     for (;;) {
1786         if (*patternp == NULLCHAR) {
1787             *index = leftover_start = bufp - buf;
1788             *matchp = NULLCHAR;
1789             return TRUE;
1790         }
1791         if (*bufp == NULLCHAR) return FALSE;
1792         if (*patternp == '*') {
1793             if (*bufp == *(patternp + 1)) {
1794                 *matchp = NULLCHAR;
1795                 matchp = star_match[++star_count];
1796                 patternp += 2;
1797                 bufp++;
1798                 continue;
1799             } else if (*bufp == '\n' || *bufp == '\r') {
1800                 patternp++;
1801                 if (*patternp == NULLCHAR)
1802                   continue;
1803                 else
1804                   return FALSE;
1805             } else {
1806                 *matchp++ = *bufp++;
1807                 continue;
1808             }
1809         }
1810         if (*patternp != *bufp) return FALSE;
1811         patternp++;
1812         bufp++;
1813     }
1814 }
1815
1816 void
1817 SendToPlayer(data, length)
1818      char *data;
1819      int length;
1820 {
1821     int error, outCount;
1822     outCount = OutputToProcess(NoProc, data, length, &error);
1823     if (outCount < length) {
1824         DisplayFatalError(_("Error writing to display"), error, 1);
1825     }
1826 }
1827
1828 void
1829 PackHolding(packed, holding)
1830      char packed[];
1831      char *holding;
1832 {
1833     char *p = holding;
1834     char *q = packed;
1835     int runlength = 0;
1836     int curr = 9999;
1837     do {
1838         if (*p == curr) {
1839             runlength++;
1840         } else {
1841             switch (runlength) {
1842               case 0:
1843                 break;
1844               case 1:
1845                 *q++ = curr;
1846                 break;
1847               case 2:
1848                 *q++ = curr;
1849                 *q++ = curr;
1850                 break;
1851               default:
1852                 sprintf(q, "%d", runlength);
1853                 while (*q) q++;
1854                 *q++ = curr;
1855                 break;
1856             }
1857             runlength = 1;
1858             curr = *p;
1859         }
1860     } while (*p++);
1861     *q = NULLCHAR;
1862 }
1863
1864 /* Telnet protocol requests from the front end */
1865 void
1866 TelnetRequest(ddww, option)
1867      unsigned char ddww, option;
1868 {
1869     unsigned char msg[3];
1870     int outCount, outError;
1871
1872     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1873
1874     if (appData.debugMode) {
1875         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1876         switch (ddww) {
1877           case TN_DO:
1878             ddwwStr = "DO";
1879             break;
1880           case TN_DONT:
1881             ddwwStr = "DONT";
1882             break;
1883           case TN_WILL:
1884             ddwwStr = "WILL";
1885             break;
1886           case TN_WONT:
1887             ddwwStr = "WONT";
1888             break;
1889           default:
1890             ddwwStr = buf1;
1891             sprintf(buf1, "%d", ddww);
1892             break;
1893         }
1894         switch (option) {
1895           case TN_ECHO:
1896             optionStr = "ECHO";
1897             break;
1898           default:
1899             optionStr = buf2;
1900             sprintf(buf2, "%d", option);
1901             break;
1902         }
1903         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1904     }
1905     msg[0] = TN_IAC;
1906     msg[1] = ddww;
1907     msg[2] = option;
1908     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1909     if (outCount < 3) {
1910         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1911     }
1912 }
1913
1914 void
1915 DoEcho()
1916 {
1917     if (!appData.icsActive) return;
1918     TelnetRequest(TN_DO, TN_ECHO);
1919 }
1920
1921 void
1922 DontEcho()
1923 {
1924     if (!appData.icsActive) return;
1925     TelnetRequest(TN_DONT, TN_ECHO);
1926 }
1927
1928 void
1929 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1930 {
1931     /* put the holdings sent to us by the server on the board holdings area */
1932     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1933     char p;
1934     ChessSquare piece;
1935
1936     if(gameInfo.holdingsWidth < 2)  return;
1937     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1938         return; // prevent overwriting by pre-board holdings
1939
1940     if( (int)lowestPiece >= BlackPawn ) {
1941         holdingsColumn = 0;
1942         countsColumn = 1;
1943         holdingsStartRow = BOARD_HEIGHT-1;
1944         direction = -1;
1945     } else {
1946         holdingsColumn = BOARD_WIDTH-1;
1947         countsColumn = BOARD_WIDTH-2;
1948         holdingsStartRow = 0;
1949         direction = 1;
1950     }
1951
1952     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1953         board[i][holdingsColumn] = EmptySquare;
1954         board[i][countsColumn]   = (ChessSquare) 0;
1955     }
1956     while( (p=*holdings++) != NULLCHAR ) {
1957         piece = CharToPiece( ToUpper(p) );
1958         if(piece == EmptySquare) continue;
1959         /*j = (int) piece - (int) WhitePawn;*/
1960         j = PieceToNumber(piece);
1961         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1962         if(j < 0) continue;               /* should not happen */
1963         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1964         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1965         board[holdingsStartRow+j*direction][countsColumn]++;
1966     }
1967 }
1968
1969
1970 void
1971 VariantSwitch(Board board, VariantClass newVariant)
1972 {
1973    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1974    Board oldBoard;
1975
1976    startedFromPositionFile = FALSE;
1977    if(gameInfo.variant == newVariant) return;
1978
1979    /* [HGM] This routine is called each time an assignment is made to
1980     * gameInfo.variant during a game, to make sure the board sizes
1981     * are set to match the new variant. If that means adding or deleting
1982     * holdings, we shift the playing board accordingly
1983     * This kludge is needed because in ICS observe mode, we get boards
1984     * of an ongoing game without knowing the variant, and learn about the
1985     * latter only later. This can be because of the move list we requested,
1986     * in which case the game history is refilled from the beginning anyway,
1987     * but also when receiving holdings of a crazyhouse game. In the latter
1988     * case we want to add those holdings to the already received position.
1989     */
1990
1991    
1992    if (appData.debugMode) {
1993      fprintf(debugFP, "Switch board from %s to %s\n",
1994              VariantName(gameInfo.variant), VariantName(newVariant));
1995      setbuf(debugFP, NULL);
1996    }
1997    shuffleOpenings = 0;       /* [HGM] shuffle */
1998    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1999    switch(newVariant) 
2000      {
2001      case VariantShogi:
2002        newWidth = 9;  newHeight = 9;
2003        gameInfo.holdingsSize = 7;
2004      case VariantBughouse:
2005      case VariantCrazyhouse:
2006        newHoldingsWidth = 2; break;
2007      case VariantGreat:
2008        newWidth = 10;
2009      case VariantSuper:
2010        newHoldingsWidth = 2;
2011        gameInfo.holdingsSize = 8;
2012        break;
2013      case VariantGothic:
2014      case VariantCapablanca:
2015      case VariantCapaRandom:
2016        newWidth = 10;
2017      default:
2018        newHoldingsWidth = gameInfo.holdingsSize = 0;
2019      };
2020    
2021    if(newWidth  != gameInfo.boardWidth  ||
2022       newHeight != gameInfo.boardHeight ||
2023       newHoldingsWidth != gameInfo.holdingsWidth ) {
2024      
2025      /* shift position to new playing area, if needed */
2026      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2027        for(i=0; i<BOARD_HEIGHT; i++) 
2028          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2029            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2030              board[i][j];
2031        for(i=0; i<newHeight; i++) {
2032          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2033          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2034        }
2035      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2036        for(i=0; i<BOARD_HEIGHT; i++)
2037          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2038            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2039              board[i][j];
2040      }
2041      gameInfo.boardWidth  = newWidth;
2042      gameInfo.boardHeight = newHeight;
2043      gameInfo.holdingsWidth = newHoldingsWidth;
2044      gameInfo.variant = newVariant;
2045      InitDrawingSizes(-2, 0);
2046    } else gameInfo.variant = newVariant;
2047    CopyBoard(oldBoard, board);   // remember correctly formatted board
2048      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2049    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2050 }
2051
2052 static int loggedOn = FALSE;
2053
2054 /*-- Game start info cache: --*/
2055 int gs_gamenum;
2056 char gs_kind[MSG_SIZ];
2057 static char player1Name[128] = "";
2058 static char player2Name[128] = "";
2059 static char cont_seq[] = "\n\\   ";
2060 static int player1Rating = -1;
2061 static int player2Rating = -1;
2062 /*----------------------------*/
2063
2064 ColorClass curColor = ColorNormal;
2065 int suppressKibitz = 0;
2066
2067 // [HGM] seekgraph
2068 Boolean soughtPending = FALSE;
2069 Boolean seekGraphUp;
2070 #define MAX_SEEK_ADS 200
2071 char *seekAdList[MAX_SEEK_ADS];
2072 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2073 float tcList[MAX_SEEK_ADS];
2074 char colorList[MAX_SEEK_ADS];
2075 int nrOfSeekAds = 0;
2076 int minRating = 1010, maxRating = 2800;
2077 int hMargin = 10, vMargin = 20, h, w;
2078 extern int squareSize, lineGap;
2079
2080 void
2081 PlotSeekAd(int i)
2082 {
2083         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2084         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2085         if(r < minRating+100 && r >=0 ) r = minRating+100;
2086         if(r > maxRating) r = maxRating;
2087         if(tc < 1.) tc = 1.;
2088         if(tc > 95.) tc = 95.;
2089         x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2090         y = ((double)r - minRating)/(maxRating - minRating)
2091             * (h-vMargin-squareSize/8-1) + vMargin;
2092         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2093         if(strstr(seekAdList[i], " u ")) color = 1;
2094         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2095            !strstr(seekAdList[i], "bullet") &&
2096            !strstr(seekAdList[i], "blitz") &&
2097            !strstr(seekAdList[i], "standard") ) color = 2;
2098         DrawSeekDot(xList[i]=x+3*color, yList[i]=h-1-y, colorList[i]=color);
2099 }
2100
2101 void
2102 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2103 {
2104         char buf[MSG_SIZ], *ext = "";
2105         VariantClass v = StringToVariant(type);
2106         if(strstr(type, "wild")) {
2107             ext = type + 4; // append wild number
2108             if(v == VariantFischeRandom) type = "chess960"; else
2109             if(v == VariantLoadable) type = "setup"; else
2110             type = VariantName(v);
2111         }
2112         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2113         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2114             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2115             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2116             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2117             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2118             seekNrList[nrOfSeekAds] = nr;
2119             zList[nrOfSeekAds] = 0;
2120             seekAdList[nrOfSeekAds++] = StrSave(buf);
2121             if(plot) PlotSeekAd(nrOfSeekAds-1);
2122         }
2123 }
2124
2125 void
2126 EraseSeekDot(int i)
2127 {
2128     int x = xList[i], y = yList[i], d=squareSize/4, k;
2129     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2130     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2131     // now replot every dot that overlapped
2132     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2133         int xx = xList[k], yy = yList[k];
2134         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2135             DrawSeekDot(xx, yy, colorList[k]);
2136     }
2137 }
2138
2139 void
2140 RemoveSeekAd(int nr)
2141 {
2142         int i;
2143         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2144             EraseSeekDot(i);
2145             if(seekAdList[i]) free(seekAdList[i]);
2146             seekAdList[i] = seekAdList[--nrOfSeekAds];
2147             seekNrList[i] = seekNrList[nrOfSeekAds];
2148             ratingList[i] = ratingList[nrOfSeekAds];
2149             colorList[i]  = colorList[nrOfSeekAds];
2150             tcList[i] = tcList[nrOfSeekAds];
2151             xList[i]  = xList[nrOfSeekAds];
2152             yList[i]  = yList[nrOfSeekAds];
2153             zList[i]  = zList[nrOfSeekAds];
2154             seekAdList[nrOfSeekAds] = NULL;
2155             break;
2156         }
2157 }
2158
2159 Boolean
2160 MatchSoughtLine(char *line)
2161 {
2162     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2163     int nr, base, inc, u=0; char dummy;
2164
2165     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2166        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2167        (u=1) &&
2168        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2169         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2170         // match: compact and save the line
2171         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2172         return TRUE;
2173     }
2174     return FALSE;
2175 }
2176
2177 int
2178 DrawSeekGraph()
2179 {
2180     if(!seekGraphUp) return FALSE;
2181     int i;
2182     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2183     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2184
2185     DrawSeekBackground(0, 0, w, h);
2186     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2187     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2188     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2189         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2190         yy = h-1-yy;
2191         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2192         if(i%500 == 0) {
2193             char buf[MSG_SIZ];
2194             sprintf(buf, "%d", i);
2195             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2196         }
2197     }
2198     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2199     for(i=1; i<100; i+=(i<10?1:5)) {
2200         int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2201         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2202         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2203             char buf[MSG_SIZ];
2204             sprintf(buf, "%d", i);
2205             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2206         }
2207     }
2208     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2209     return TRUE;
2210 }
2211
2212 int SeekGraphClick(ClickType click, int x, int y, int moving)
2213 {
2214     static int lastDown = 0, displayed = 0, lastSecond;
2215     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2216         if(click == Release || moving) return FALSE;
2217         nrOfSeekAds = 0;
2218         soughtPending = TRUE;
2219         SendToICS(ics_prefix);
2220         SendToICS("sought\n"); // should this be "sought all"?
2221     } else { // issue challenge based on clicked ad
2222         int dist = 10000; int i, closest = 0, second = 0;
2223         for(i=0; i<nrOfSeekAds; i++) {
2224             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2225             if(d < dist) { dist = d; closest = i; }
2226             second += (d - zList[i] < 120); // count in-range ads
2227             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2228         }
2229         if(dist < 120) {
2230             char buf[MSG_SIZ];
2231             second = (second > 1);
2232             if(displayed != closest || second != lastSecond) {
2233                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2234                 lastSecond = second; displayed = closest;
2235             }
2236             sprintf(buf, "play %d\n", seekNrList[closest]);
2237             if(click == Press) {
2238                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2239                 lastDown = closest;
2240                 return TRUE;
2241             } // on press 'hit', only show info
2242             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2243             SendToICS(ics_prefix);
2244             SendToICS(buf); // should this be "sought all"?
2245         } else if(click == Release) { // release 'miss' is ignored
2246             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2247             if(moving == 2) { // right up-click
2248                 nrOfSeekAds = 0; // refresh graph
2249                 soughtPending = TRUE;
2250                 SendToICS(ics_prefix);
2251                 SendToICS("sought\n"); // should this be "sought all"?
2252             }
2253             return TRUE;
2254         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2255         // press miss or release hit 'pop down' seek graph
2256         seekGraphUp = FALSE;
2257         DrawPosition(TRUE, NULL);
2258     }
2259     return TRUE;
2260 }
2261
2262 void
2263 read_from_ics(isr, closure, data, count, error)
2264      InputSourceRef isr;
2265      VOIDSTAR closure;
2266      char *data;
2267      int count;
2268      int error;
2269 {
2270 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2271 #define STARTED_NONE 0
2272 #define STARTED_MOVES 1
2273 #define STARTED_BOARD 2
2274 #define STARTED_OBSERVE 3
2275 #define STARTED_HOLDINGS 4
2276 #define STARTED_CHATTER 5
2277 #define STARTED_COMMENT 6
2278 #define STARTED_MOVES_NOHIDE 7
2279     
2280     static int started = STARTED_NONE;
2281     static char parse[20000];
2282     static int parse_pos = 0;
2283     static char buf[BUF_SIZE + 1];
2284     static int firstTime = TRUE, intfSet = FALSE;
2285     static ColorClass prevColor = ColorNormal;
2286     static int savingComment = FALSE;
2287     static int cmatch = 0; // continuation sequence match
2288     char *bp;
2289     char str[500];
2290     int i, oldi;
2291     int buf_len;
2292     int next_out;
2293     int tkind;
2294     int backup;    /* [DM] For zippy color lines */
2295     char *p;
2296     char talker[MSG_SIZ]; // [HGM] chat
2297     int channel;
2298
2299     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2300
2301     if (appData.debugMode) {
2302       if (!error) {
2303         fprintf(debugFP, "<ICS: ");
2304         show_bytes(debugFP, data, count);
2305         fprintf(debugFP, "\n");
2306       }
2307     }
2308
2309     if (appData.debugMode) { int f = forwardMostMove;
2310         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2311                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2312                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2313     }
2314     if (count > 0) {
2315         /* If last read ended with a partial line that we couldn't parse,
2316            prepend it to the new read and try again. */
2317         if (leftover_len > 0) {
2318             for (i=0; i<leftover_len; i++)
2319               buf[i] = buf[leftover_start + i];
2320         }
2321
2322     /* copy new characters into the buffer */
2323     bp = buf + leftover_len;
2324     buf_len=leftover_len;
2325     for (i=0; i<count; i++)
2326     {
2327         // ignore these
2328         if (data[i] == '\r')
2329             continue;
2330
2331         // join lines split by ICS?
2332         if (!appData.noJoin)
2333         {
2334             /*
2335                 Joining just consists of finding matches against the
2336                 continuation sequence, and discarding that sequence
2337                 if found instead of copying it.  So, until a match
2338                 fails, there's nothing to do since it might be the
2339                 complete sequence, and thus, something we don't want
2340                 copied.
2341             */
2342             if (data[i] == cont_seq[cmatch])
2343             {
2344                 cmatch++;
2345                 if (cmatch == strlen(cont_seq))
2346                 {
2347                     cmatch = 0; // complete match.  just reset the counter
2348
2349                     /*
2350                         it's possible for the ICS to not include the space
2351                         at the end of the last word, making our [correct]
2352                         join operation fuse two separate words.  the server
2353                         does this when the space occurs at the width setting.
2354                     */
2355                     if (!buf_len || buf[buf_len-1] != ' ')
2356                     {
2357                         *bp++ = ' ';
2358                         buf_len++;
2359                     }
2360                 }
2361                 continue;
2362             }
2363             else if (cmatch)
2364             {
2365                 /*
2366                     match failed, so we have to copy what matched before
2367                     falling through and copying this character.  In reality,
2368                     this will only ever be just the newline character, but
2369                     it doesn't hurt to be precise.
2370                 */
2371                 strncpy(bp, cont_seq, cmatch);
2372                 bp += cmatch;
2373                 buf_len += cmatch;
2374                 cmatch = 0;
2375             }
2376         }
2377
2378         // copy this char
2379         *bp++ = data[i];
2380         buf_len++;
2381     }
2382
2383         buf[buf_len] = NULLCHAR;
2384 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2385         next_out = 0;
2386         leftover_start = 0;
2387         
2388         i = 0;
2389         while (i < buf_len) {
2390             /* Deal with part of the TELNET option negotiation
2391                protocol.  We refuse to do anything beyond the
2392                defaults, except that we allow the WILL ECHO option,
2393                which ICS uses to turn off password echoing when we are
2394                directly connected to it.  We reject this option
2395                if localLineEditing mode is on (always on in xboard)
2396                and we are talking to port 23, which might be a real
2397                telnet server that will try to keep WILL ECHO on permanently.
2398              */
2399             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2400                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2401                 unsigned char option;
2402                 oldi = i;
2403                 switch ((unsigned char) buf[++i]) {
2404                   case TN_WILL:
2405                     if (appData.debugMode)
2406                       fprintf(debugFP, "\n<WILL ");
2407                     switch (option = (unsigned char) buf[++i]) {
2408                       case TN_ECHO:
2409                         if (appData.debugMode)
2410                           fprintf(debugFP, "ECHO ");
2411                         /* Reply only if this is a change, according
2412                            to the protocol rules. */
2413                         if (remoteEchoOption) break;
2414                         if (appData.localLineEditing &&
2415                             atoi(appData.icsPort) == TN_PORT) {
2416                             TelnetRequest(TN_DONT, TN_ECHO);
2417                         } else {
2418                             EchoOff();
2419                             TelnetRequest(TN_DO, TN_ECHO);
2420                             remoteEchoOption = TRUE;
2421                         }
2422                         break;
2423                       default:
2424                         if (appData.debugMode)
2425                           fprintf(debugFP, "%d ", option);
2426                         /* Whatever this is, we don't want it. */
2427                         TelnetRequest(TN_DONT, option);
2428                         break;
2429                     }
2430                     break;
2431                   case TN_WONT:
2432                     if (appData.debugMode)
2433                       fprintf(debugFP, "\n<WONT ");
2434                     switch (option = (unsigned char) buf[++i]) {
2435                       case TN_ECHO:
2436                         if (appData.debugMode)
2437                           fprintf(debugFP, "ECHO ");
2438                         /* Reply only if this is a change, according
2439                            to the protocol rules. */
2440                         if (!remoteEchoOption) break;
2441                         EchoOn();
2442                         TelnetRequest(TN_DONT, TN_ECHO);
2443                         remoteEchoOption = FALSE;
2444                         break;
2445                       default:
2446                         if (appData.debugMode)
2447                           fprintf(debugFP, "%d ", (unsigned char) option);
2448                         /* Whatever this is, it must already be turned
2449                            off, because we never agree to turn on
2450                            anything non-default, so according to the
2451                            protocol rules, we don't reply. */
2452                         break;
2453                     }
2454                     break;
2455                   case TN_DO:
2456                     if (appData.debugMode)
2457                       fprintf(debugFP, "\n<DO ");
2458                     switch (option = (unsigned char) buf[++i]) {
2459                       default:
2460                         /* Whatever this is, we refuse to do it. */
2461                         if (appData.debugMode)
2462                           fprintf(debugFP, "%d ", option);
2463                         TelnetRequest(TN_WONT, option);
2464                         break;
2465                     }
2466                     break;
2467                   case TN_DONT:
2468                     if (appData.debugMode)
2469                       fprintf(debugFP, "\n<DONT ");
2470                     switch (option = (unsigned char) buf[++i]) {
2471                       default:
2472                         if (appData.debugMode)
2473                           fprintf(debugFP, "%d ", option);
2474                         /* Whatever this is, we are already not doing
2475                            it, because we never agree to do anything
2476                            non-default, so according to the protocol
2477                            rules, we don't reply. */
2478                         break;
2479                     }
2480                     break;
2481                   case TN_IAC:
2482                     if (appData.debugMode)
2483                       fprintf(debugFP, "\n<IAC ");
2484                     /* Doubled IAC; pass it through */
2485                     i--;
2486                     break;
2487                   default:
2488                     if (appData.debugMode)
2489                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2490                     /* Drop all other telnet commands on the floor */
2491                     break;
2492                 }
2493                 if (oldi > next_out)
2494                   SendToPlayer(&buf[next_out], oldi - next_out);
2495                 if (++i > next_out)
2496                   next_out = i;
2497                 continue;
2498             }
2499                 
2500             /* OK, this at least will *usually* work */
2501             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2502                 loggedOn = TRUE;
2503             }
2504             
2505             if (loggedOn && !intfSet) {
2506                 if (ics_type == ICS_ICC) {
2507                   sprintf(str,
2508                           "/set-quietly interface %s\n/set-quietly style 12\n",
2509                           programVersion);
2510                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2511                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2512                 } else if (ics_type == ICS_CHESSNET) {
2513                   sprintf(str, "/style 12\n");
2514                 } else {
2515                   strcpy(str, "alias $ @\n$set interface ");
2516                   strcat(str, programVersion);
2517                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2518                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2519                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2520 #ifdef WIN32
2521                   strcat(str, "$iset nohighlight 1\n");
2522 #endif
2523                   strcat(str, "$iset lock 1\n$style 12\n");
2524                 }
2525                 SendToICS(str);
2526                 NotifyFrontendLogin();
2527                 intfSet = TRUE;
2528             }
2529
2530             if (started == STARTED_COMMENT) {
2531                 /* Accumulate characters in comment */
2532                 parse[parse_pos++] = buf[i];
2533                 if (buf[i] == '\n') {
2534                     parse[parse_pos] = NULLCHAR;
2535                     if(chattingPartner>=0) {
2536                         char mess[MSG_SIZ];
2537                         sprintf(mess, "%s%s", talker, parse);
2538                         OutputChatMessage(chattingPartner, mess);
2539                         chattingPartner = -1;
2540                     } else
2541                     if(!suppressKibitz) // [HGM] kibitz
2542                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2543                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2544                         int nrDigit = 0, nrAlph = 0, j;
2545                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2546                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2547                         parse[parse_pos] = NULLCHAR;
2548                         // try to be smart: if it does not look like search info, it should go to
2549                         // ICS interaction window after all, not to engine-output window.
2550                         for(j=0; j<parse_pos; j++) { // count letters and digits
2551                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2552                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2553                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2554                         }
2555                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2556                             int depth=0; float score;
2557                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2558                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2559                                 pvInfoList[forwardMostMove-1].depth = depth;
2560                                 pvInfoList[forwardMostMove-1].score = 100*score;
2561                             }
2562                             OutputKibitz(suppressKibitz, parse);
2563                             next_out = i+1; // [HGM] suppress printing in ICS window
2564                         } else {
2565                             char tmp[MSG_SIZ];
2566                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2567                             SendToPlayer(tmp, strlen(tmp));
2568                         }
2569                     }
2570                     started = STARTED_NONE;
2571                 } else {
2572                     /* Don't match patterns against characters in comment */
2573                     i++;
2574                     continue;
2575                 }
2576             }
2577             if (started == STARTED_CHATTER) {
2578                 if (buf[i] != '\n') {
2579                     /* Don't match patterns against characters in chatter */
2580                     i++;
2581                     continue;
2582                 }
2583                 started = STARTED_NONE;
2584             }
2585
2586             /* Kludge to deal with rcmd protocol */
2587             if (firstTime && looking_at(buf, &i, "\001*")) {
2588                 DisplayFatalError(&buf[1], 0, 1);
2589                 continue;
2590             } else {
2591                 firstTime = FALSE;
2592             }
2593
2594             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2595                 ics_type = ICS_ICC;
2596                 ics_prefix = "/";
2597                 if (appData.debugMode)
2598                   fprintf(debugFP, "ics_type %d\n", ics_type);
2599                 continue;
2600             }
2601             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2602                 ics_type = ICS_FICS;
2603                 ics_prefix = "$";
2604                 if (appData.debugMode)
2605                   fprintf(debugFP, "ics_type %d\n", ics_type);
2606                 continue;
2607             }
2608             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2609                 ics_type = ICS_CHESSNET;
2610                 ics_prefix = "/";
2611                 if (appData.debugMode)
2612                   fprintf(debugFP, "ics_type %d\n", ics_type);
2613                 continue;
2614             }
2615
2616             if (!loggedOn &&
2617                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2618                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2619                  looking_at(buf, &i, "will be \"*\""))) {
2620               strcpy(ics_handle, star_match[0]);
2621               continue;
2622             }
2623
2624             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2625               char buf[MSG_SIZ];
2626               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2627               DisplayIcsInteractionTitle(buf);
2628               have_set_title = TRUE;
2629             }
2630
2631             /* skip finger notes */
2632             if (started == STARTED_NONE &&
2633                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2634                  (buf[i] == '1' && buf[i+1] == '0')) &&
2635                 buf[i+2] == ':' && buf[i+3] == ' ') {
2636               started = STARTED_CHATTER;
2637               i += 3;
2638               continue;
2639             }
2640
2641             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2642             if(appData.seekGraph) {
2643                 if(soughtPending && MatchSoughtLine(buf+i)) {
2644                     i = strstr(buf+i, "rated") - buf;
2645                     next_out = leftover_start = i;
2646                     started = STARTED_CHATTER;
2647                     suppressKibitz = TRUE;
2648                     continue;
2649                 }
2650                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2651                         && looking_at(buf, &i, "* ads displayed")) {
2652                     soughtPending = FALSE;
2653                     seekGraphUp = TRUE;
2654                     DrawSeekGraph();
2655                     continue;
2656                 }
2657                 if(appData.autoRefresh) {
2658                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2659                         int s = (ics_type == ICS_ICC); // ICC format differs
2660                         if(seekGraphUp)
2661                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2662                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2663                         looking_at(buf, &i, "*% "); // eat prompt
2664                         next_out = i; // suppress
2665                         continue;
2666                     }
2667                     if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2668                         char *p = star_match[0];
2669                         while(*p) {
2670                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2671                             while(*p && *p++ != ' '); // next
2672                         }
2673                         looking_at(buf, &i, "*% "); // eat prompt
2674                         next_out = i;
2675                         continue;
2676                     }
2677                 }
2678             }
2679
2680             /* skip formula vars */
2681             if (started == STARTED_NONE &&
2682                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2683               started = STARTED_CHATTER;
2684               i += 3;
2685               continue;
2686             }
2687
2688             oldi = i;
2689             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2690             if (appData.autoKibitz && started == STARTED_NONE && 
2691                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2692                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2693                 if(looking_at(buf, &i, "* kibitzes: ") &&
2694                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2695                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2696                         suppressKibitz = TRUE;
2697                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2698                                 && (gameMode == IcsPlayingWhite)) ||
2699                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2700                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2701                             started = STARTED_CHATTER; // own kibitz we simply discard
2702                         else {
2703                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2704                             parse_pos = 0; parse[0] = NULLCHAR;
2705                             savingComment = TRUE;
2706                             suppressKibitz = gameMode != IcsObserving ? 2 :
2707                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2708                         } 
2709                         continue;
2710                 } else
2711                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2712                     // suppress the acknowledgements of our own autoKibitz
2713                     char *p;
2714                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2715                     SendToPlayer(star_match[0], strlen(star_match[0]));
2716                     looking_at(buf, &i, "*% "); // eat prompt
2717                     next_out = i;
2718                 }
2719             } // [HGM] kibitz: end of patch
2720
2721 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2722
2723             // [HGM] chat: intercept tells by users for which we have an open chat window
2724             channel = -1;
2725             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2726                                            looking_at(buf, &i, "* whispers:") ||
2727                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2728                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2729                 int p;
2730                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2731                 chattingPartner = -1;
2732
2733                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2734                 for(p=0; p<MAX_CHAT; p++) {
2735                     if(channel == atoi(chatPartner[p])) {
2736                     talker[0] = '['; strcat(talker, "] ");
2737                     chattingPartner = p; break;
2738                     }
2739                 } else
2740                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2741                 for(p=0; p<MAX_CHAT; p++) {
2742                     if(!strcmp("WHISPER", chatPartner[p])) {
2743                         talker[0] = '['; strcat(talker, "] ");
2744                         chattingPartner = p; break;
2745                     }
2746                 }
2747                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2748                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2749                     talker[0] = 0;
2750                     chattingPartner = p; break;
2751                 }
2752                 if(chattingPartner<0) i = oldi; else {
2753                     started = STARTED_COMMENT;
2754                     parse_pos = 0; parse[0] = NULLCHAR;
2755                     savingComment = 3 + chattingPartner; // counts as TRUE
2756                     suppressKibitz = TRUE;
2757                 }
2758             } // [HGM] chat: end of patch
2759
2760             if (appData.zippyTalk || appData.zippyPlay) {
2761                 /* [DM] Backup address for color zippy lines */
2762                 backup = i;
2763 #if ZIPPY
2764        #ifdef WIN32
2765                if (loggedOn == TRUE)
2766                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2767                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2768        #else
2769                 if (ZippyControl(buf, &i) ||
2770                     ZippyConverse(buf, &i) ||
2771                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2772                       loggedOn = TRUE;
2773                       if (!appData.colorize) continue;
2774                 }
2775        #endif
2776 #endif
2777             } // [DM] 'else { ' deleted
2778                 if (
2779                     /* Regular tells and says */
2780                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2781                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2782                     looking_at(buf, &i, "* says: ") ||
2783                     /* Don't color "message" or "messages" output */
2784                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2785                     looking_at(buf, &i, "*. * at *:*: ") ||
2786                     looking_at(buf, &i, "--* (*:*): ") ||
2787                     /* Message notifications (same color as tells) */
2788                     looking_at(buf, &i, "* has left a message ") ||
2789                     looking_at(buf, &i, "* just sent you a message:\n") ||
2790                     /* Whispers and kibitzes */
2791                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2792                     looking_at(buf, &i, "* kibitzes: ") ||
2793                     /* Channel tells */
2794                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2795
2796                   if (tkind == 1 && strchr(star_match[0], ':')) {
2797                       /* Avoid "tells you:" spoofs in channels */
2798                      tkind = 3;
2799                   }
2800                   if (star_match[0][0] == NULLCHAR ||
2801                       strchr(star_match[0], ' ') ||
2802                       (tkind == 3 && strchr(star_match[1], ' '))) {
2803                     /* Reject bogus matches */
2804                     i = oldi;
2805                   } else {
2806                     if (appData.colorize) {
2807                       if (oldi > next_out) {
2808                         SendToPlayer(&buf[next_out], oldi - next_out);
2809                         next_out = oldi;
2810                       }
2811                       switch (tkind) {
2812                       case 1:
2813                         Colorize(ColorTell, FALSE);
2814                         curColor = ColorTell;
2815                         break;
2816                       case 2:
2817                         Colorize(ColorKibitz, FALSE);
2818                         curColor = ColorKibitz;
2819                         break;
2820                       case 3:
2821                         p = strrchr(star_match[1], '(');
2822                         if (p == NULL) {
2823                           p = star_match[1];
2824                         } else {
2825                           p++;
2826                         }
2827                         if (atoi(p) == 1) {
2828                           Colorize(ColorChannel1, FALSE);
2829                           curColor = ColorChannel1;
2830                         } else {
2831                           Colorize(ColorChannel, FALSE);
2832                           curColor = ColorChannel;
2833                         }
2834                         break;
2835                       case 5:
2836                         curColor = ColorNormal;
2837                         break;
2838                       }
2839                     }
2840                     if (started == STARTED_NONE && appData.autoComment &&
2841                         (gameMode == IcsObserving ||
2842                          gameMode == IcsPlayingWhite ||
2843                          gameMode == IcsPlayingBlack)) {
2844                       parse_pos = i - oldi;
2845                       memcpy(parse, &buf[oldi], parse_pos);
2846                       parse[parse_pos] = NULLCHAR;
2847                       started = STARTED_COMMENT;
2848                       savingComment = TRUE;
2849                     } else {
2850                       started = STARTED_CHATTER;
2851                       savingComment = FALSE;
2852                     }
2853                     loggedOn = TRUE;
2854                     continue;
2855                   }
2856                 }
2857
2858                 if (looking_at(buf, &i, "* s-shouts: ") ||
2859                     looking_at(buf, &i, "* c-shouts: ")) {
2860                     if (appData.colorize) {
2861                         if (oldi > next_out) {
2862                             SendToPlayer(&buf[next_out], oldi - next_out);
2863                             next_out = oldi;
2864                         }
2865                         Colorize(ColorSShout, FALSE);
2866                         curColor = ColorSShout;
2867                     }
2868                     loggedOn = TRUE;
2869                     started = STARTED_CHATTER;
2870                     continue;
2871                 }
2872
2873                 if (looking_at(buf, &i, "--->")) {
2874                     loggedOn = TRUE;
2875                     continue;
2876                 }
2877
2878                 if (looking_at(buf, &i, "* shouts: ") ||
2879                     looking_at(buf, &i, "--> ")) {
2880                     if (appData.colorize) {
2881                         if (oldi > next_out) {
2882                             SendToPlayer(&buf[next_out], oldi - next_out);
2883                             next_out = oldi;
2884                         }
2885                         Colorize(ColorShout, FALSE);
2886                         curColor = ColorShout;
2887                     }
2888                     loggedOn = TRUE;
2889                     started = STARTED_CHATTER;
2890                     continue;
2891                 }
2892
2893                 if (looking_at( buf, &i, "Challenge:")) {
2894                     if (appData.colorize) {
2895                         if (oldi > next_out) {
2896                             SendToPlayer(&buf[next_out], oldi - next_out);
2897                             next_out = oldi;
2898                         }
2899                         Colorize(ColorChallenge, FALSE);
2900                         curColor = ColorChallenge;
2901                     }
2902                     loggedOn = TRUE;
2903                     continue;
2904                 }
2905
2906                 if (looking_at(buf, &i, "* offers you") ||
2907                     looking_at(buf, &i, "* offers to be") ||
2908                     looking_at(buf, &i, "* would like to") ||
2909                     looking_at(buf, &i, "* requests to") ||
2910                     looking_at(buf, &i, "Your opponent offers") ||
2911                     looking_at(buf, &i, "Your opponent requests")) {
2912
2913                     if (appData.colorize) {
2914                         if (oldi > next_out) {
2915                             SendToPlayer(&buf[next_out], oldi - next_out);
2916                             next_out = oldi;
2917                         }
2918                         Colorize(ColorRequest, FALSE);
2919                         curColor = ColorRequest;
2920                     }
2921                     continue;
2922                 }
2923
2924                 if (looking_at(buf, &i, "* (*) seeking")) {
2925                     if (appData.colorize) {
2926                         if (oldi > next_out) {
2927                             SendToPlayer(&buf[next_out], oldi - next_out);
2928                             next_out = oldi;
2929                         }
2930                         Colorize(ColorSeek, FALSE);
2931                         curColor = ColorSeek;
2932                     }
2933                     continue;
2934             }
2935
2936             if (looking_at(buf, &i, "\\   ")) {
2937                 if (prevColor != ColorNormal) {
2938                     if (oldi > next_out) {
2939                         SendToPlayer(&buf[next_out], oldi - next_out);
2940                         next_out = oldi;
2941                     }
2942                     Colorize(prevColor, TRUE);
2943                     curColor = prevColor;
2944                 }
2945                 if (savingComment) {
2946                     parse_pos = i - oldi;
2947                     memcpy(parse, &buf[oldi], parse_pos);
2948                     parse[parse_pos] = NULLCHAR;
2949                     started = STARTED_COMMENT;
2950                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2951                         chattingPartner = savingComment - 3; // kludge to remember the box
2952                 } else {
2953                     started = STARTED_CHATTER;
2954                 }
2955                 continue;
2956             }
2957
2958             if (looking_at(buf, &i, "Black Strength :") ||
2959                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2960                 looking_at(buf, &i, "<10>") ||
2961                 looking_at(buf, &i, "#@#")) {
2962                 /* Wrong board style */
2963                 loggedOn = TRUE;
2964                 SendToICS(ics_prefix);
2965                 SendToICS("set style 12\n");
2966                 SendToICS(ics_prefix);
2967                 SendToICS("refresh\n");
2968                 continue;
2969             }
2970             
2971             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2972                 ICSInitScript();
2973                 have_sent_ICS_logon = 1;
2974                 continue;
2975             }
2976               
2977             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2978                 (looking_at(buf, &i, "\n<12> ") ||
2979                  looking_at(buf, &i, "<12> "))) {
2980                 loggedOn = TRUE;
2981                 if (oldi > next_out) {
2982                     SendToPlayer(&buf[next_out], oldi - next_out);
2983                 }
2984                 next_out = i;
2985                 started = STARTED_BOARD;
2986                 parse_pos = 0;
2987                 continue;
2988             }
2989
2990             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2991                 looking_at(buf, &i, "<b1> ")) {
2992                 if (oldi > next_out) {
2993                     SendToPlayer(&buf[next_out], oldi - next_out);
2994                 }
2995                 next_out = i;
2996                 started = STARTED_HOLDINGS;
2997                 parse_pos = 0;
2998                 continue;
2999             }
3000
3001             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3002                 loggedOn = TRUE;
3003                 /* Header for a move list -- first line */
3004
3005                 switch (ics_getting_history) {
3006                   case H_FALSE:
3007                     switch (gameMode) {
3008                       case IcsIdle:
3009                       case BeginningOfGame:
3010                         /* User typed "moves" or "oldmoves" while we
3011                            were idle.  Pretend we asked for these
3012                            moves and soak them up so user can step
3013                            through them and/or save them.
3014                            */
3015                         Reset(FALSE, TRUE);
3016                         gameMode = IcsObserving;
3017                         ModeHighlight();
3018                         ics_gamenum = -1;
3019                         ics_getting_history = H_GOT_UNREQ_HEADER;
3020                         break;
3021                       case EditGame: /*?*/
3022                       case EditPosition: /*?*/
3023                         /* Should above feature work in these modes too? */
3024                         /* For now it doesn't */
3025                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3026                         break;
3027                       default:
3028                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3029                         break;
3030                     }
3031                     break;
3032                   case H_REQUESTED:
3033                     /* Is this the right one? */
3034                     if (gameInfo.white && gameInfo.black &&
3035                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3036                         strcmp(gameInfo.black, star_match[2]) == 0) {
3037                         /* All is well */
3038                         ics_getting_history = H_GOT_REQ_HEADER;
3039                     }
3040                     break;
3041                   case H_GOT_REQ_HEADER:
3042                   case H_GOT_UNREQ_HEADER:
3043                   case H_GOT_UNWANTED_HEADER:
3044                   case H_GETTING_MOVES:
3045                     /* Should not happen */
3046                     DisplayError(_("Error gathering move list: two headers"), 0);
3047                     ics_getting_history = H_FALSE;
3048                     break;
3049                 }
3050
3051                 /* Save player ratings into gameInfo if needed */
3052                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3053                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3054                     (gameInfo.whiteRating == -1 ||
3055                      gameInfo.blackRating == -1)) {
3056
3057                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3058                     gameInfo.blackRating = string_to_rating(star_match[3]);
3059                     if (appData.debugMode)
3060                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3061                               gameInfo.whiteRating, gameInfo.blackRating);
3062                 }
3063                 continue;
3064             }
3065
3066             if (looking_at(buf, &i,
3067               "* * match, initial time: * minute*, increment: * second")) {
3068                 /* Header for a move list -- second line */
3069                 /* Initial board will follow if this is a wild game */
3070                 if (gameInfo.event != NULL) free(gameInfo.event);
3071                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3072                 gameInfo.event = StrSave(str);
3073                 /* [HGM] we switched variant. Translate boards if needed. */
3074                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3075                 continue;
3076             }
3077
3078             if (looking_at(buf, &i, "Move  ")) {
3079                 /* Beginning of a move list */
3080                 switch (ics_getting_history) {
3081                   case H_FALSE:
3082                     /* Normally should not happen */
3083                     /* Maybe user hit reset while we were parsing */
3084                     break;
3085                   case H_REQUESTED:
3086                     /* Happens if we are ignoring a move list that is not
3087                      * the one we just requested.  Common if the user
3088                      * tries to observe two games without turning off
3089                      * getMoveList */
3090                     break;
3091                   case H_GETTING_MOVES:
3092                     /* Should not happen */
3093                     DisplayError(_("Error gathering move list: nested"), 0);
3094                     ics_getting_history = H_FALSE;
3095                     break;
3096                   case H_GOT_REQ_HEADER:
3097                     ics_getting_history = H_GETTING_MOVES;
3098                     started = STARTED_MOVES;
3099                     parse_pos = 0;
3100                     if (oldi > next_out) {
3101                         SendToPlayer(&buf[next_out], oldi - next_out);
3102                     }
3103                     break;
3104                   case H_GOT_UNREQ_HEADER:
3105                     ics_getting_history = H_GETTING_MOVES;
3106                     started = STARTED_MOVES_NOHIDE;
3107                     parse_pos = 0;
3108                     break;
3109                   case H_GOT_UNWANTED_HEADER:
3110                     ics_getting_history = H_FALSE;
3111                     break;
3112                 }
3113                 continue;
3114             }                           
3115             
3116             if (looking_at(buf, &i, "% ") ||
3117                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3118                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3119                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3120                     soughtPending = FALSE;
3121                     seekGraphUp = TRUE;
3122                     DrawSeekGraph();
3123                 }
3124                 if(suppressKibitz) next_out = i;
3125                 savingComment = FALSE;
3126                 suppressKibitz = 0;
3127                 switch (started) {
3128                   case STARTED_MOVES:
3129                   case STARTED_MOVES_NOHIDE:
3130                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3131                     parse[parse_pos + i - oldi] = NULLCHAR;
3132                     ParseGameHistory(parse);
3133 #if ZIPPY
3134                     if (appData.zippyPlay && first.initDone) {
3135                         FeedMovesToProgram(&first, forwardMostMove);
3136                         if (gameMode == IcsPlayingWhite) {
3137                             if (WhiteOnMove(forwardMostMove)) {
3138                                 if (first.sendTime) {
3139                                   if (first.useColors) {
3140                                     SendToProgram("black\n", &first); 
3141                                   }
3142                                   SendTimeRemaining(&first, TRUE);
3143                                 }
3144                                 if (first.useColors) {
3145                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3146                                 }
3147                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3148                                 first.maybeThinking = TRUE;
3149                             } else {
3150                                 if (first.usePlayother) {
3151                                   if (first.sendTime) {
3152                                     SendTimeRemaining(&first, TRUE);
3153                                   }
3154                                   SendToProgram("playother\n", &first);
3155                                   firstMove = FALSE;
3156                                 } else {
3157                                   firstMove = TRUE;
3158                                 }
3159                             }
3160                         } else if (gameMode == IcsPlayingBlack) {
3161                             if (!WhiteOnMove(forwardMostMove)) {
3162                                 if (first.sendTime) {
3163                                   if (first.useColors) {
3164                                     SendToProgram("white\n", &first);
3165                                   }
3166                                   SendTimeRemaining(&first, FALSE);
3167                                 }
3168                                 if (first.useColors) {
3169                                   SendToProgram("black\n", &first);
3170                                 }
3171                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3172                                 first.maybeThinking = TRUE;
3173                             } else {
3174                                 if (first.usePlayother) {
3175                                   if (first.sendTime) {
3176                                     SendTimeRemaining(&first, FALSE);
3177                                   }
3178                                   SendToProgram("playother\n", &first);
3179                                   firstMove = FALSE;
3180                                 } else {
3181                                   firstMove = TRUE;
3182                                 }
3183                             }
3184                         }                       
3185                     }
3186 #endif
3187                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3188                         /* Moves came from oldmoves or moves command
3189                            while we weren't doing anything else.
3190                            */
3191                         currentMove = forwardMostMove;
3192                         ClearHighlights();/*!!could figure this out*/
3193                         flipView = appData.flipView;
3194                         DrawPosition(TRUE, boards[currentMove]);
3195                         DisplayBothClocks();
3196                         sprintf(str, "%s vs. %s",
3197                                 gameInfo.white, gameInfo.black);
3198                         DisplayTitle(str);
3199                         gameMode = IcsIdle;
3200                     } else {
3201                         /* Moves were history of an active game */
3202                         if (gameInfo.resultDetails != NULL) {
3203                             free(gameInfo.resultDetails);
3204                             gameInfo.resultDetails = NULL;
3205                         }
3206                     }
3207                     HistorySet(parseList, backwardMostMove,
3208                                forwardMostMove, currentMove-1);
3209                     DisplayMove(currentMove - 1);
3210                     if (started == STARTED_MOVES) next_out = i;
3211                     started = STARTED_NONE;
3212                     ics_getting_history = H_FALSE;
3213                     break;
3214
3215                   case STARTED_OBSERVE:
3216                     started = STARTED_NONE;
3217                     SendToICS(ics_prefix);
3218                     SendToICS("refresh\n");
3219                     break;
3220
3221                   default:
3222                     break;
3223                 }
3224                 if(bookHit) { // [HGM] book: simulate book reply
3225                     static char bookMove[MSG_SIZ]; // a bit generous?
3226
3227                     programStats.nodes = programStats.depth = programStats.time = 
3228                     programStats.score = programStats.got_only_move = 0;
3229                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3230
3231                     strcpy(bookMove, "move ");
3232                     strcat(bookMove, bookHit);
3233                     HandleMachineMove(bookMove, &first);
3234                 }
3235                 continue;
3236             }
3237             
3238             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3239                  started == STARTED_HOLDINGS ||
3240                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3241                 /* Accumulate characters in move list or board */
3242                 parse[parse_pos++] = buf[i];
3243             }
3244             
3245             /* Start of game messages.  Mostly we detect start of game
3246                when the first board image arrives.  On some versions
3247                of the ICS, though, we need to do a "refresh" after starting
3248                to observe in order to get the current board right away. */
3249             if (looking_at(buf, &i, "Adding game * to observation list")) {
3250                 started = STARTED_OBSERVE;
3251                 continue;
3252             }
3253
3254             /* Handle auto-observe */
3255             if (appData.autoObserve &&
3256                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3257                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3258                 char *player;
3259                 /* Choose the player that was highlighted, if any. */
3260                 if (star_match[0][0] == '\033' ||
3261                     star_match[1][0] != '\033') {
3262                     player = star_match[0];
3263                 } else {
3264                     player = star_match[2];
3265                 }
3266                 sprintf(str, "%sobserve %s\n",
3267                         ics_prefix, StripHighlightAndTitle(player));
3268                 SendToICS(str);
3269
3270                 /* Save ratings from notify string */
3271                 strcpy(player1Name, star_match[0]);
3272                 player1Rating = string_to_rating(star_match[1]);
3273                 strcpy(player2Name, star_match[2]);
3274                 player2Rating = string_to_rating(star_match[3]);
3275
3276                 if (appData.debugMode)
3277                   fprintf(debugFP, 
3278                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3279                           player1Name, player1Rating,
3280                           player2Name, player2Rating);
3281
3282                 continue;
3283             }
3284
3285             /* Deal with automatic examine mode after a game,
3286                and with IcsObserving -> IcsExamining transition */
3287             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3288                 looking_at(buf, &i, "has made you an examiner of game *")) {
3289
3290                 int gamenum = atoi(star_match[0]);
3291                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3292                     gamenum == ics_gamenum) {
3293                     /* We were already playing or observing this game;
3294                        no need to refetch history */
3295                     gameMode = IcsExamining;
3296                     if (pausing) {
3297                         pauseExamForwardMostMove = forwardMostMove;
3298                     } else if (currentMove < forwardMostMove) {
3299                         ForwardInner(forwardMostMove);
3300                     }
3301                 } else {
3302                     /* I don't think this case really can happen */
3303                     SendToICS(ics_prefix);
3304                     SendToICS("refresh\n");
3305                 }
3306                 continue;
3307             }    
3308             
3309             /* Error messages */
3310 //          if (ics_user_moved) {
3311             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3312                 if (looking_at(buf, &i, "Illegal move") ||
3313                     looking_at(buf, &i, "Not a legal move") ||
3314                     looking_at(buf, &i, "Your king is in check") ||
3315                     looking_at(buf, &i, "It isn't your turn") ||
3316                     looking_at(buf, &i, "It is not your move")) {
3317                     /* Illegal move */
3318                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3319                         currentMove = --forwardMostMove;
3320                         DisplayMove(currentMove - 1); /* before DMError */
3321                         DrawPosition(FALSE, boards[currentMove]);
3322                         SwitchClocks();
3323                         DisplayBothClocks();
3324                     }
3325                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3326                     ics_user_moved = 0;
3327                     continue;
3328                 }
3329             }
3330
3331             if (looking_at(buf, &i, "still have time") ||
3332                 looking_at(buf, &i, "not out of time") ||
3333                 looking_at(buf, &i, "either player is out of time") ||
3334                 looking_at(buf, &i, "has timeseal; checking")) {
3335                 /* We must have called his flag a little too soon */
3336                 whiteFlag = blackFlag = FALSE;
3337                 continue;
3338             }
3339
3340             if (looking_at(buf, &i, "added * seconds to") ||
3341                 looking_at(buf, &i, "seconds were added to")) {
3342                 /* Update the clocks */
3343                 SendToICS(ics_prefix);
3344                 SendToICS("refresh\n");
3345                 continue;
3346             }
3347
3348             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3349                 ics_clock_paused = TRUE;
3350                 StopClocks();
3351                 continue;
3352             }
3353
3354             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3355                 ics_clock_paused = FALSE;
3356                 StartClocks();
3357                 continue;
3358             }
3359
3360             /* Grab player ratings from the Creating: message.
3361                Note we have to check for the special case when
3362                the ICS inserts things like [white] or [black]. */
3363             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3364                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3365                 /* star_matches:
3366                    0    player 1 name (not necessarily white)
3367                    1    player 1 rating
3368                    2    empty, white, or black (IGNORED)
3369                    3    player 2 name (not necessarily black)
3370                    4    player 2 rating
3371                    
3372                    The names/ratings are sorted out when the game
3373                    actually starts (below).
3374                 */
3375                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3376                 player1Rating = string_to_rating(star_match[1]);
3377                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3378                 player2Rating = string_to_rating(star_match[4]);
3379
3380                 if (appData.debugMode)
3381                   fprintf(debugFP, 
3382                           "Ratings from 'Creating:' %s %d, %s %d\n",
3383                           player1Name, player1Rating,
3384                           player2Name, player2Rating);
3385
3386                 continue;
3387             }
3388             
3389             /* Improved generic start/end-of-game messages */
3390             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3391                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3392                 /* If tkind == 0: */
3393                 /* star_match[0] is the game number */
3394                 /*           [1] is the white player's name */
3395                 /*           [2] is the black player's name */
3396                 /* For end-of-game: */
3397                 /*           [3] is the reason for the game end */
3398                 /*           [4] is a PGN end game-token, preceded by " " */
3399                 /* For start-of-game: */
3400                 /*           [3] begins with "Creating" or "Continuing" */
3401                 /*           [4] is " *" or empty (don't care). */
3402                 int gamenum = atoi(star_match[0]);
3403                 char *whitename, *blackname, *why, *endtoken;
3404                 ChessMove endtype = (ChessMove) 0;
3405
3406                 if (tkind == 0) {
3407                   whitename = star_match[1];
3408                   blackname = star_match[2];
3409                   why = star_match[3];
3410                   endtoken = star_match[4];
3411                 } else {
3412                   whitename = star_match[1];
3413                   blackname = star_match[3];
3414                   why = star_match[5];
3415                   endtoken = star_match[6];
3416                 }
3417
3418                 /* Game start messages */
3419                 if (strncmp(why, "Creating ", 9) == 0 ||
3420                     strncmp(why, "Continuing ", 11) == 0) {
3421                     gs_gamenum = gamenum;
3422                     strcpy(gs_kind, strchr(why, ' ') + 1);
3423                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3424 #if ZIPPY
3425                     if (appData.zippyPlay) {
3426                         ZippyGameStart(whitename, blackname);
3427                     }
3428 #endif /*ZIPPY*/
3429                     continue;
3430                 }
3431
3432                 /* Game end messages */
3433                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3434                     ics_gamenum != gamenum) {
3435                     continue;
3436                 }
3437                 while (endtoken[0] == ' ') endtoken++;
3438                 switch (endtoken[0]) {
3439                   case '*':
3440                   default:
3441                     endtype = GameUnfinished;
3442                     break;
3443                   case '0':
3444                     endtype = BlackWins;
3445                     break;
3446                   case '1':
3447                     if (endtoken[1] == '/')
3448                       endtype = GameIsDrawn;
3449                     else
3450                       endtype = WhiteWins;
3451                     break;
3452                 }
3453                 GameEnds(endtype, why, GE_ICS);
3454 #if ZIPPY
3455                 if (appData.zippyPlay && first.initDone) {
3456                     ZippyGameEnd(endtype, why);
3457                     if (first.pr == NULL) {
3458                       /* Start the next process early so that we'll
3459                          be ready for the next challenge */
3460                       StartChessProgram(&first);
3461                     }
3462                     /* Send "new" early, in case this command takes
3463                        a long time to finish, so that we'll be ready
3464                        for the next challenge. */
3465                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3466                     Reset(TRUE, TRUE);
3467                 }
3468 #endif /*ZIPPY*/
3469                 continue;
3470             }
3471
3472             if (looking_at(buf, &i, "Removing game * from observation") ||
3473                 looking_at(buf, &i, "no longer observing game *") ||
3474                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3475                 if (gameMode == IcsObserving &&
3476                     atoi(star_match[0]) == ics_gamenum)
3477                   {
3478                       /* icsEngineAnalyze */
3479                       if (appData.icsEngineAnalyze) {
3480                             ExitAnalyzeMode();
3481                             ModeHighlight();
3482                       }
3483                       StopClocks();
3484                       gameMode = IcsIdle;
3485                       ics_gamenum = -1;
3486                       ics_user_moved = FALSE;
3487                   }
3488                 continue;
3489             }
3490
3491             if (looking_at(buf, &i, "no longer examining game *")) {
3492                 if (gameMode == IcsExamining &&
3493                     atoi(star_match[0]) == ics_gamenum)
3494                   {
3495                       gameMode = IcsIdle;
3496                       ics_gamenum = -1;
3497                       ics_user_moved = FALSE;
3498                   }
3499                 continue;
3500             }
3501
3502             /* Advance leftover_start past any newlines we find,
3503                so only partial lines can get reparsed */
3504             if (looking_at(buf, &i, "\n")) {
3505                 prevColor = curColor;
3506                 if (curColor != ColorNormal) {
3507                     if (oldi > next_out) {
3508                         SendToPlayer(&buf[next_out], oldi - next_out);
3509                         next_out = oldi;
3510                     }
3511                     Colorize(ColorNormal, FALSE);
3512                     curColor = ColorNormal;
3513                 }
3514                 if (started == STARTED_BOARD) {
3515                     started = STARTED_NONE;
3516                     parse[parse_pos] = NULLCHAR;
3517                     ParseBoard12(parse);
3518                     ics_user_moved = 0;
3519
3520                     /* Send premove here */
3521                     if (appData.premove) {
3522                       char str[MSG_SIZ];
3523                       if (currentMove == 0 &&
3524                           gameMode == IcsPlayingWhite &&
3525                           appData.premoveWhite) {
3526                         sprintf(str, "%s\n", appData.premoveWhiteText);
3527                         if (appData.debugMode)
3528                           fprintf(debugFP, "Sending premove:\n");
3529                         SendToICS(str);
3530                       } else if (currentMove == 1 &&
3531                                  gameMode == IcsPlayingBlack &&
3532                                  appData.premoveBlack) {
3533                         sprintf(str, "%s\n", appData.premoveBlackText);
3534                         if (appData.debugMode)
3535                           fprintf(debugFP, "Sending premove:\n");
3536                         SendToICS(str);
3537                       } else if (gotPremove) {
3538                         gotPremove = 0;
3539                         ClearPremoveHighlights();
3540                         if (appData.debugMode)
3541                           fprintf(debugFP, "Sending premove:\n");
3542                           UserMoveEvent(premoveFromX, premoveFromY, 
3543                                         premoveToX, premoveToY, 
3544                                         premovePromoChar);
3545                       }
3546                     }
3547
3548                     /* Usually suppress following prompt */
3549                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3550                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3551                         if (looking_at(buf, &i, "*% ")) {
3552                             savingComment = FALSE;
3553                             suppressKibitz = 0;
3554                         }
3555                     }
3556                     next_out = i;
3557                 } else if (started == STARTED_HOLDINGS) {
3558                     int gamenum;
3559                     char new_piece[MSG_SIZ];
3560                     started = STARTED_NONE;
3561                     parse[parse_pos] = NULLCHAR;
3562                     if (appData.debugMode)
3563                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3564                                                         parse, currentMove);
3565                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3566                         gamenum == ics_gamenum) {
3567                         if (gameInfo.variant == VariantNormal) {
3568                           /* [HGM] We seem to switch variant during a game!
3569                            * Presumably no holdings were displayed, so we have
3570                            * to move the position two files to the right to
3571                            * create room for them!
3572                            */
3573                           VariantClass newVariant;
3574                           switch(gameInfo.boardWidth) { // base guess on board width
3575                                 case 9:  newVariant = VariantShogi; break;
3576                                 case 10: newVariant = VariantGreat; break;
3577                                 default: newVariant = VariantCrazyhouse; break;
3578                           }
3579                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3580                           /* Get a move list just to see the header, which
3581                              will tell us whether this is really bug or zh */
3582                           if (ics_getting_history == H_FALSE) {
3583                             ics_getting_history = H_REQUESTED;
3584                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3585                             SendToICS(str);
3586                           }
3587                         }
3588                         new_piece[0] = NULLCHAR;
3589                         sscanf(parse, "game %d white [%s black [%s <- %s",
3590                                &gamenum, white_holding, black_holding,
3591                                new_piece);
3592                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3593                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3594                         /* [HGM] copy holdings to board holdings area */
3595                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3596                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3597                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3598 #if ZIPPY
3599                         if (appData.zippyPlay && first.initDone) {
3600                             ZippyHoldings(white_holding, black_holding,
3601                                           new_piece);
3602                         }
3603 #endif /*ZIPPY*/
3604                         if (tinyLayout || smallLayout) {
3605                             char wh[16], bh[16];
3606                             PackHolding(wh, white_holding);
3607                             PackHolding(bh, black_holding);
3608                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3609                                     gameInfo.white, gameInfo.black);
3610                         } else {
3611                             sprintf(str, "%s [%s] vs. %s [%s]",
3612                                     gameInfo.white, white_holding,
3613                                     gameInfo.black, black_holding);
3614                         }
3615
3616                         DrawPosition(FALSE, boards[currentMove]);
3617                         DisplayTitle(str);
3618                     }
3619                     /* Suppress following prompt */
3620                     if (looking_at(buf, &i, "*% ")) {
3621                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3622                         savingComment = FALSE;
3623                         suppressKibitz = 0;
3624                     }
3625                     next_out = i;
3626                 }
3627                 continue;
3628             }
3629
3630             i++;                /* skip unparsed character and loop back */
3631         }
3632         
3633         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3634 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3635 //          SendToPlayer(&buf[next_out], i - next_out);
3636             started != STARTED_HOLDINGS && leftover_start > next_out) {
3637             SendToPlayer(&buf[next_out], leftover_start - next_out);
3638             next_out = i;
3639         }
3640         
3641         leftover_len = buf_len - leftover_start;
3642         /* if buffer ends with something we couldn't parse,
3643            reparse it after appending the next read */
3644         
3645     } else if (count == 0) {
3646         RemoveInputSource(isr);
3647         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3648     } else {
3649         DisplayFatalError(_("Error reading from ICS"), error, 1);
3650     }
3651 }
3652
3653
3654 /* Board style 12 looks like this:
3655    
3656    <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
3657    
3658  * The "<12> " is stripped before it gets to this routine.  The two
3659  * trailing 0's (flip state and clock ticking) are later addition, and
3660  * some chess servers may not have them, or may have only the first.
3661  * Additional trailing fields may be added in the future.  
3662  */
3663
3664 #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"
3665
3666 #define RELATION_OBSERVING_PLAYED    0
3667 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3668 #define RELATION_PLAYING_MYMOVE      1
3669 #define RELATION_PLAYING_NOTMYMOVE  -1
3670 #define RELATION_EXAMINING           2
3671 #define RELATION_ISOLATED_BOARD     -3
3672 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3673
3674 void
3675 ParseBoard12(string)
3676      char *string;
3677
3678     GameMode newGameMode;
3679     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3680     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3681     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3682     char to_play, board_chars[200];
3683     char move_str[500], str[500], elapsed_time[500];
3684     char black[32], white[32];
3685     Board board;
3686     int prevMove = currentMove;
3687     int ticking = 2;
3688     ChessMove moveType;
3689     int fromX, fromY, toX, toY;
3690     char promoChar;
3691     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3692     char *bookHit = NULL; // [HGM] book
3693     Boolean weird = FALSE, reqFlag = FALSE;
3694
3695     fromX = fromY = toX = toY = -1;
3696     
3697     newGame = FALSE;
3698
3699     if (appData.debugMode)
3700       fprintf(debugFP, _("Parsing board: %s\n"), string);
3701
3702     move_str[0] = NULLCHAR;
3703     elapsed_time[0] = NULLCHAR;
3704     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3705         int  i = 0, j;
3706         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3707             if(string[i] == ' ') { ranks++; files = 0; }
3708             else files++;
3709             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3710             i++;
3711         }
3712         for(j = 0; j <i; j++) board_chars[j] = string[j];
3713         board_chars[i] = '\0';
3714         string += i + 1;
3715     }
3716     n = sscanf(string, PATTERN, &to_play, &double_push,
3717                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3718                &gamenum, white, black, &relation, &basetime, &increment,
3719                &white_stren, &black_stren, &white_time, &black_time,
3720                &moveNum, str, elapsed_time, move_str, &ics_flip,
3721                &ticking);
3722
3723     if (n < 21) {
3724         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3725         DisplayError(str, 0);
3726         return;
3727     }
3728
3729     /* Convert the move number to internal form */
3730     moveNum = (moveNum - 1) * 2;
3731     if (to_play == 'B') moveNum++;
3732     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3733       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3734                         0, 1);
3735       return;
3736     }
3737     
3738     switch (relation) {
3739       case RELATION_OBSERVING_PLAYED:
3740       case RELATION_OBSERVING_STATIC:
3741         if (gamenum == -1) {
3742             /* Old ICC buglet */
3743             relation = RELATION_OBSERVING_STATIC;
3744         }
3745         newGameMode = IcsObserving;
3746         break;
3747       case RELATION_PLAYING_MYMOVE:
3748       case RELATION_PLAYING_NOTMYMOVE:
3749         newGameMode =
3750           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3751             IcsPlayingWhite : IcsPlayingBlack;
3752         break;
3753       case RELATION_EXAMINING:
3754         newGameMode = IcsExamining;
3755         break;
3756       case RELATION_ISOLATED_BOARD:
3757       default:
3758         /* Just display this board.  If user was doing something else,
3759            we will forget about it until the next board comes. */ 
3760         newGameMode = IcsIdle;
3761         break;
3762       case RELATION_STARTING_POSITION:
3763         newGameMode = gameMode;
3764         break;
3765     }
3766     
3767     /* Modify behavior for initial board display on move listing
3768        of wild games.
3769        */
3770     switch (ics_getting_history) {
3771       case H_FALSE:
3772       case H_REQUESTED:
3773         break;
3774       case H_GOT_REQ_HEADER:
3775       case H_GOT_UNREQ_HEADER:
3776         /* This is the initial position of the current game */
3777         gamenum = ics_gamenum;
3778         moveNum = 0;            /* old ICS bug workaround */
3779         if (to_play == 'B') {
3780           startedFromSetupPosition = TRUE;
3781           blackPlaysFirst = TRUE;
3782           moveNum = 1;
3783           if (forwardMostMove == 0) forwardMostMove = 1;
3784           if (backwardMostMove == 0) backwardMostMove = 1;
3785           if (currentMove == 0) currentMove = 1;
3786         }
3787         newGameMode = gameMode;
3788         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3789         break;
3790       case H_GOT_UNWANTED_HEADER:
3791         /* This is an initial board that we don't want */
3792         return;
3793       case H_GETTING_MOVES:
3794         /* Should not happen */
3795         DisplayError(_("Error gathering move list: extra board"), 0);
3796         ics_getting_history = H_FALSE;
3797         return;
3798     }
3799
3800    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3801                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3802      /* [HGM] We seem to have switched variant unexpectedly
3803       * Try to guess new variant from board size
3804       */
3805           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3806           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3807           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3808           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3809           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3810           if(!weird) newVariant = VariantNormal;
3811           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3812           /* Get a move list just to see the header, which
3813              will tell us whether this is really bug or zh */
3814           if (ics_getting_history == H_FALSE) {
3815             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3816             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3817             SendToICS(str);
3818           }
3819     }
3820     
3821     /* Take action if this is the first board of a new game, or of a
3822        different game than is currently being displayed.  */
3823     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3824         relation == RELATION_ISOLATED_BOARD) {
3825         
3826         /* Forget the old game and get the history (if any) of the new one */
3827         if (gameMode != BeginningOfGame) {
3828           Reset(TRUE, TRUE);
3829         }
3830         newGame = TRUE;
3831         if (appData.autoRaiseBoard) BoardToTop();
3832         prevMove = -3;
3833         if (gamenum == -1) {
3834             newGameMode = IcsIdle;
3835         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3836                    appData.getMoveList && !reqFlag) {
3837             /* Need to get game history */
3838             ics_getting_history = H_REQUESTED;
3839             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3840             SendToICS(str);
3841         }
3842         
3843         /* Initially flip the board to have black on the bottom if playing
3844            black or if the ICS flip flag is set, but let the user change
3845            it with the Flip View button. */
3846         flipView = appData.autoFlipView ? 
3847           (newGameMode == IcsPlayingBlack) || ics_flip :
3848           appData.flipView;
3849         
3850         /* Done with values from previous mode; copy in new ones */
3851         gameMode = newGameMode;
3852         ModeHighlight();
3853         ics_gamenum = gamenum;
3854         if (gamenum == gs_gamenum) {
3855             int klen = strlen(gs_kind);
3856             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3857             sprintf(str, "ICS %s", gs_kind);
3858             gameInfo.event = StrSave(str);
3859         } else {
3860             gameInfo.event = StrSave("ICS game");
3861         }
3862         gameInfo.site = StrSave(appData.icsHost);
3863         gameInfo.date = PGNDate();
3864         gameInfo.round = StrSave("-");
3865         gameInfo.white = StrSave(white);
3866         gameInfo.black = StrSave(black);
3867         timeControl = basetime * 60 * 1000;
3868         timeControl_2 = 0;
3869         timeIncrement = increment * 1000;
3870         movesPerSession = 0;
3871         gameInfo.timeControl = TimeControlTagValue();
3872         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3873   if (appData.debugMode) {
3874     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3875     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3876     setbuf(debugFP, NULL);
3877   }
3878
3879         gameInfo.outOfBook = NULL;
3880         
3881         /* Do we have the ratings? */
3882         if (strcmp(player1Name, white) == 0 &&
3883             strcmp(player2Name, black) == 0) {
3884             if (appData.debugMode)
3885               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3886                       player1Rating, player2Rating);
3887             gameInfo.whiteRating = player1Rating;
3888             gameInfo.blackRating = player2Rating;
3889         } else if (strcmp(player2Name, white) == 0 &&
3890                    strcmp(player1Name, black) == 0) {
3891             if (appData.debugMode)
3892               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3893                       player2Rating, player1Rating);
3894             gameInfo.whiteRating = player2Rating;
3895             gameInfo.blackRating = player1Rating;
3896         }
3897         player1Name[0] = player2Name[0] = NULLCHAR;
3898
3899         /* Silence shouts if requested */
3900         if (appData.quietPlay &&
3901             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3902             SendToICS(ics_prefix);
3903             SendToICS("set shout 0\n");
3904         }
3905     }
3906     
3907     /* Deal with midgame name changes */
3908     if (!newGame) {
3909         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3910             if (gameInfo.white) free(gameInfo.white);
3911             gameInfo.white = StrSave(white);
3912         }
3913         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3914             if (gameInfo.black) free(gameInfo.black);
3915             gameInfo.black = StrSave(black);
3916         }
3917     }
3918     
3919     /* Throw away game result if anything actually changes in examine mode */
3920     if (gameMode == IcsExamining && !newGame) {
3921         gameInfo.result = GameUnfinished;
3922         if (gameInfo.resultDetails != NULL) {
3923             free(gameInfo.resultDetails);
3924             gameInfo.resultDetails = NULL;
3925         }
3926     }
3927     
3928     /* In pausing && IcsExamining mode, we ignore boards coming
3929        in if they are in a different variation than we are. */
3930     if (pauseExamInvalid) return;
3931     if (pausing && gameMode == IcsExamining) {
3932         if (moveNum <= pauseExamForwardMostMove) {
3933             pauseExamInvalid = TRUE;
3934             forwardMostMove = pauseExamForwardMostMove;
3935             return;
3936         }
3937     }
3938     
3939   if (appData.debugMode) {
3940     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3941   }
3942     /* Parse the board */
3943     for (k = 0; k < ranks; k++) {
3944       for (j = 0; j < files; j++)
3945         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3946       if(gameInfo.holdingsWidth > 1) {
3947            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3948            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3949       }
3950     }
3951     CopyBoard(boards[moveNum], board);
3952     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3953     if (moveNum == 0) {
3954         startedFromSetupPosition =
3955           !CompareBoards(board, initialPosition);
3956         if(startedFromSetupPosition)
3957             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3958     }
3959
3960     /* [HGM] Set castling rights. Take the outermost Rooks,
3961        to make it also work for FRC opening positions. Note that board12
3962        is really defective for later FRC positions, as it has no way to
3963        indicate which Rook can castle if they are on the same side of King.
3964        For the initial position we grant rights to the outermost Rooks,
3965        and remember thos rights, and we then copy them on positions
3966        later in an FRC game. This means WB might not recognize castlings with
3967        Rooks that have moved back to their original position as illegal,
3968        but in ICS mode that is not its job anyway.
3969     */
3970     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3971     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3972
3973         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3974             if(board[0][i] == WhiteRook) j = i;
3975         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3976         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3977             if(board[0][i] == WhiteRook) j = i;
3978         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3979         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3980             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3981         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3982         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3983             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3984         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3985
3986         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3987         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3988             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3989         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3990             if(board[BOARD_HEIGHT-1][k] == bKing)
3991                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3992         if(gameInfo.variant == VariantTwoKings) {
3993             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3994             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3995             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3996         }
3997     } else { int r;
3998         r = boards[moveNum][CASTLING][0] = initialRights[0];
3999         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4000         r = boards[moveNum][CASTLING][1] = initialRights[1];
4001         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4002         r = boards[moveNum][CASTLING][3] = initialRights[3];
4003         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4004         r = boards[moveNum][CASTLING][4] = initialRights[4];
4005         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4006         /* wildcastle kludge: always assume King has rights */
4007         r = boards[moveNum][CASTLING][2] = initialRights[2];
4008         r = boards[moveNum][CASTLING][5] = initialRights[5];
4009     }
4010     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4011     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4012
4013     
4014     if (ics_getting_history == H_GOT_REQ_HEADER ||
4015         ics_getting_history == H_GOT_UNREQ_HEADER) {
4016         /* This was an initial position from a move list, not
4017            the current position */
4018         return;
4019     }
4020     
4021     /* Update currentMove and known move number limits */
4022     newMove = newGame || moveNum > forwardMostMove;
4023
4024     if (newGame) {
4025         forwardMostMove = backwardMostMove = currentMove = moveNum;
4026         if (gameMode == IcsExamining && moveNum == 0) {
4027           /* Workaround for ICS limitation: we are not told the wild
4028              type when starting to examine a game.  But if we ask for
4029              the move list, the move list header will tell us */
4030             ics_getting_history = H_REQUESTED;
4031             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4032             SendToICS(str);
4033         }
4034     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4035                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4036 #if ZIPPY
4037         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4038         /* [HGM] applied this also to an engine that is silently watching        */
4039         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4040             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4041             gameInfo.variant == currentlyInitializedVariant) {
4042           takeback = forwardMostMove - moveNum;
4043           for (i = 0; i < takeback; i++) {
4044             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4045             SendToProgram("undo\n", &first);
4046           }
4047         }
4048 #endif
4049
4050         forwardMostMove = moveNum;
4051         if (!pausing || currentMove > forwardMostMove)
4052           currentMove = forwardMostMove;
4053     } else {
4054         /* New part of history that is not contiguous with old part */ 
4055         if (pausing && gameMode == IcsExamining) {
4056             pauseExamInvalid = TRUE;
4057             forwardMostMove = pauseExamForwardMostMove;
4058             return;
4059         }
4060         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4061 #if ZIPPY
4062             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4063                 // [HGM] when we will receive the move list we now request, it will be
4064                 // fed to the engine from the first move on. So if the engine is not
4065                 // in the initial position now, bring it there.
4066                 InitChessProgram(&first, 0);
4067             }
4068 #endif
4069             ics_getting_history = H_REQUESTED;
4070             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4071             SendToICS(str);
4072         }
4073         forwardMostMove = backwardMostMove = currentMove = moveNum;
4074     }
4075     
4076     /* Update the clocks */
4077     if (strchr(elapsed_time, '.')) {
4078       /* Time is in ms */
4079       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4080       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4081     } else {
4082       /* Time is in seconds */
4083       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4084       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4085     }
4086       
4087
4088 #if ZIPPY
4089     if (appData.zippyPlay && newGame &&
4090         gameMode != IcsObserving && gameMode != IcsIdle &&
4091         gameMode != IcsExamining)
4092       ZippyFirstBoard(moveNum, basetime, increment);
4093 #endif
4094     
4095     /* Put the move on the move list, first converting
4096        to canonical algebraic form. */
4097     if (moveNum > 0) {
4098   if (appData.debugMode) {
4099     if (appData.debugMode) { int f = forwardMostMove;
4100         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4101                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4102                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4103     }
4104     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4105     fprintf(debugFP, "moveNum = %d\n", moveNum);
4106     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4107     setbuf(debugFP, NULL);
4108   }
4109         if (moveNum <= backwardMostMove) {
4110             /* We don't know what the board looked like before
4111                this move.  Punt. */
4112             strcpy(parseList[moveNum - 1], move_str);
4113             strcat(parseList[moveNum - 1], " ");
4114             strcat(parseList[moveNum - 1], elapsed_time);
4115             moveList[moveNum - 1][0] = NULLCHAR;
4116         } else if (strcmp(move_str, "none") == 0) {
4117             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4118             /* Again, we don't know what the board looked like;
4119                this is really the start of the game. */
4120             parseList[moveNum - 1][0] = NULLCHAR;
4121             moveList[moveNum - 1][0] = NULLCHAR;
4122             backwardMostMove = moveNum;
4123             startedFromSetupPosition = TRUE;
4124             fromX = fromY = toX = toY = -1;
4125         } else {
4126           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4127           //                 So we parse the long-algebraic move string in stead of the SAN move
4128           int valid; char buf[MSG_SIZ], *prom;
4129
4130           // str looks something like "Q/a1-a2"; kill the slash
4131           if(str[1] == '/') 
4132                 sprintf(buf, "%c%s", str[0], str+2);
4133           else  strcpy(buf, str); // might be castling
4134           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4135                 strcat(buf, prom); // long move lacks promo specification!
4136           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4137                 if(appData.debugMode) 
4138                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4139                 strcpy(move_str, buf);
4140           }
4141           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4142                                 &fromX, &fromY, &toX, &toY, &promoChar)
4143                || ParseOneMove(buf, moveNum - 1, &moveType,
4144                                 &fromX, &fromY, &toX, &toY, &promoChar);
4145           // end of long SAN patch
4146           if (valid) {
4147             (void) CoordsToAlgebraic(boards[moveNum - 1],
4148                                      PosFlags(moveNum - 1),
4149                                      fromY, fromX, toY, toX, promoChar,
4150                                      parseList[moveNum-1]);
4151             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4152               case MT_NONE:
4153               case MT_STALEMATE:
4154               default:
4155                 break;
4156               case MT_CHECK:
4157                 if(gameInfo.variant != VariantShogi)
4158                     strcat(parseList[moveNum - 1], "+");
4159                 break;
4160               case MT_CHECKMATE:
4161               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4162                 strcat(parseList[moveNum - 1], "#");
4163                 break;
4164             }
4165             strcat(parseList[moveNum - 1], " ");
4166             strcat(parseList[moveNum - 1], elapsed_time);
4167             /* currentMoveString is set as a side-effect of ParseOneMove */
4168             strcpy(moveList[moveNum - 1], currentMoveString);
4169             strcat(moveList[moveNum - 1], "\n");
4170           } else {
4171             /* Move from ICS was illegal!?  Punt. */
4172   if (appData.debugMode) {
4173     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4174     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4175   }
4176             strcpy(parseList[moveNum - 1], move_str);
4177             strcat(parseList[moveNum - 1], " ");
4178             strcat(parseList[moveNum - 1], elapsed_time);
4179             moveList[moveNum - 1][0] = NULLCHAR;
4180             fromX = fromY = toX = toY = -1;
4181           }
4182         }
4183   if (appData.debugMode) {
4184     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4185     setbuf(debugFP, NULL);
4186   }
4187
4188 #if ZIPPY
4189         /* Send move to chess program (BEFORE animating it). */
4190         if (appData.zippyPlay && !newGame && newMove && 
4191            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4192
4193             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4194                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4195                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4196                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4197                             move_str);
4198                     DisplayError(str, 0);
4199                 } else {
4200                     if (first.sendTime) {
4201                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4202                     }
4203                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4204                     if (firstMove && !bookHit) {
4205                         firstMove = FALSE;
4206                         if (first.useColors) {
4207                           SendToProgram(gameMode == IcsPlayingWhite ?
4208                                         "white\ngo\n" :
4209                                         "black\ngo\n", &first);
4210                         } else {
4211                           SendToProgram("go\n", &first);
4212                         }
4213                         first.maybeThinking = TRUE;
4214                     }
4215                 }
4216             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4217               if (moveList[moveNum - 1][0] == NULLCHAR) {
4218                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4219                 DisplayError(str, 0);
4220               } else {
4221                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4222                 SendMoveToProgram(moveNum - 1, &first);
4223               }
4224             }
4225         }
4226 #endif
4227     }
4228
4229     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4230         /* If move comes from a remote source, animate it.  If it
4231            isn't remote, it will have already been animated. */
4232         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4233             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4234         }
4235         if (!pausing && appData.highlightLastMove) {
4236             SetHighlights(fromX, fromY, toX, toY);
4237         }
4238     }
4239     
4240     /* Start the clocks */
4241     whiteFlag = blackFlag = FALSE;
4242     appData.clockMode = !(basetime == 0 && increment == 0);
4243     if (ticking == 0) {
4244       ics_clock_paused = TRUE;
4245       StopClocks();
4246     } else if (ticking == 1) {
4247       ics_clock_paused = FALSE;
4248     }
4249     if (gameMode == IcsIdle ||
4250         relation == RELATION_OBSERVING_STATIC ||
4251         relation == RELATION_EXAMINING ||
4252         ics_clock_paused)
4253       DisplayBothClocks();
4254     else
4255       StartClocks();
4256     
4257     /* Display opponents and material strengths */
4258     if (gameInfo.variant != VariantBughouse &&
4259         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4260         if (tinyLayout || smallLayout) {
4261             if(gameInfo.variant == VariantNormal)
4262                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4263                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4264                     basetime, increment);
4265             else
4266                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4267                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4268                     basetime, increment, (int) gameInfo.variant);
4269         } else {
4270             if(gameInfo.variant == VariantNormal)
4271                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4272                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4273                     basetime, increment);
4274             else
4275                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4276                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4277                     basetime, increment, VariantName(gameInfo.variant));
4278         }
4279         DisplayTitle(str);
4280   if (appData.debugMode) {
4281     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4282   }
4283     }
4284
4285
4286     /* Display the board */
4287     if (!pausing && !appData.noGUI) {
4288       
4289       if (appData.premove)
4290           if (!gotPremove || 
4291              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4292              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4293               ClearPremoveHighlights();
4294
4295       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4296       DrawPosition(j, boards[currentMove]);
4297
4298       DisplayMove(moveNum - 1);
4299       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4300             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4301               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4302         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4303       }
4304     }
4305
4306     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4307 #if ZIPPY
4308     if(bookHit) { // [HGM] book: simulate book reply
4309         static char bookMove[MSG_SIZ]; // a bit generous?
4310
4311         programStats.nodes = programStats.depth = programStats.time = 
4312         programStats.score = programStats.got_only_move = 0;
4313         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4314
4315         strcpy(bookMove, "move ");
4316         strcat(bookMove, bookHit);
4317         HandleMachineMove(bookMove, &first);
4318     }
4319 #endif
4320 }
4321
4322 void
4323 GetMoveListEvent()
4324 {
4325     char buf[MSG_SIZ];
4326     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4327         ics_getting_history = H_REQUESTED;
4328         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4329         SendToICS(buf);
4330     }
4331 }
4332
4333 void
4334 AnalysisPeriodicEvent(force)
4335      int force;
4336 {
4337     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4338          && !force) || !appData.periodicUpdates)
4339       return;
4340
4341     /* Send . command to Crafty to collect stats */
4342     SendToProgram(".\n", &first);
4343
4344     /* Don't send another until we get a response (this makes
4345        us stop sending to old Crafty's which don't understand
4346        the "." command (sending illegal cmds resets node count & time,
4347        which looks bad)) */
4348     programStats.ok_to_send = 0;
4349 }
4350
4351 void ics_update_width(new_width)
4352         int new_width;
4353 {
4354         ics_printf("set width %d\n", new_width);
4355 }
4356
4357 void
4358 SendMoveToProgram(moveNum, cps)
4359      int moveNum;
4360      ChessProgramState *cps;
4361 {
4362     char buf[MSG_SIZ];
4363
4364     if (cps->useUsermove) {
4365       SendToProgram("usermove ", cps);
4366     }
4367     if (cps->useSAN) {
4368       char *space;
4369       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4370         int len = space - parseList[moveNum];
4371         memcpy(buf, parseList[moveNum], len);
4372         buf[len++] = '\n';
4373         buf[len] = NULLCHAR;
4374       } else {
4375         sprintf(buf, "%s\n", parseList[moveNum]);
4376       }
4377       SendToProgram(buf, cps);
4378     } else {
4379       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4380         AlphaRank(moveList[moveNum], 4);
4381         SendToProgram(moveList[moveNum], cps);
4382         AlphaRank(moveList[moveNum], 4); // and back
4383       } else
4384       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4385        * the engine. It would be nice to have a better way to identify castle 
4386        * moves here. */
4387       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4388                                                                          && cps->useOOCastle) {
4389         int fromX = moveList[moveNum][0] - AAA; 
4390         int fromY = moveList[moveNum][1] - ONE;
4391         int toX = moveList[moveNum][2] - AAA; 
4392         int toY = moveList[moveNum][3] - ONE;
4393         if((boards[moveNum][fromY][fromX] == WhiteKing 
4394             && boards[moveNum][toY][toX] == WhiteRook)
4395            || (boards[moveNum][fromY][fromX] == BlackKing 
4396                && boards[moveNum][toY][toX] == BlackRook)) {
4397           if(toX > fromX) SendToProgram("O-O\n", cps);
4398           else SendToProgram("O-O-O\n", cps);
4399         }
4400         else SendToProgram(moveList[moveNum], cps);
4401       }
4402       else SendToProgram(moveList[moveNum], cps);
4403       /* End of additions by Tord */
4404     }
4405
4406     /* [HGM] setting up the opening has brought engine in force mode! */
4407     /*       Send 'go' if we are in a mode where machine should play. */
4408     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4409         (gameMode == TwoMachinesPlay   ||
4410 #ifdef ZIPPY
4411          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4412 #endif
4413          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4414         SendToProgram("go\n", cps);
4415   if (appData.debugMode) {
4416     fprintf(debugFP, "(extra)\n");
4417   }
4418     }
4419     setboardSpoiledMachineBlack = 0;
4420 }
4421
4422 void
4423 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4424      ChessMove moveType;
4425      int fromX, fromY, toX, toY;
4426 {
4427     char user_move[MSG_SIZ];
4428
4429     switch (moveType) {
4430       default:
4431         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4432                 (int)moveType, fromX, fromY, toX, toY);
4433         DisplayError(user_move + strlen("say "), 0);
4434         break;
4435       case WhiteKingSideCastle:
4436       case BlackKingSideCastle:
4437       case WhiteQueenSideCastleWild:
4438       case BlackQueenSideCastleWild:
4439       /* PUSH Fabien */
4440       case WhiteHSideCastleFR:
4441       case BlackHSideCastleFR:
4442       /* POP Fabien */
4443         sprintf(user_move, "o-o\n");
4444         break;
4445       case WhiteQueenSideCastle:
4446       case BlackQueenSideCastle:
4447       case WhiteKingSideCastleWild:
4448       case BlackKingSideCastleWild:
4449       /* PUSH Fabien */
4450       case WhiteASideCastleFR:
4451       case BlackASideCastleFR:
4452       /* POP Fabien */
4453         sprintf(user_move, "o-o-o\n");
4454         break;
4455       case WhitePromotionQueen:
4456       case BlackPromotionQueen:
4457       case WhitePromotionRook:
4458       case BlackPromotionRook:
4459       case WhitePromotionBishop:
4460       case BlackPromotionBishop:
4461       case WhitePromotionKnight:
4462       case BlackPromotionKnight:
4463       case WhitePromotionKing:
4464       case BlackPromotionKing:
4465       case WhitePromotionChancellor:
4466       case BlackPromotionChancellor:
4467       case WhitePromotionArchbishop:
4468       case BlackPromotionArchbishop:
4469         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4470             sprintf(user_move, "%c%c%c%c=%c\n",
4471                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4472                 PieceToChar(WhiteFerz));
4473         else if(gameInfo.variant == VariantGreat)
4474             sprintf(user_move, "%c%c%c%c=%c\n",
4475                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4476                 PieceToChar(WhiteMan));
4477         else
4478             sprintf(user_move, "%c%c%c%c=%c\n",
4479                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4480                 PieceToChar(PromoPiece(moveType)));
4481         break;
4482       case WhiteDrop:
4483       case BlackDrop:
4484         sprintf(user_move, "%c@%c%c\n",
4485                 ToUpper(PieceToChar((ChessSquare) fromX)),
4486                 AAA + toX, ONE + toY);
4487         break;
4488       case NormalMove:
4489       case WhiteCapturesEnPassant:
4490       case BlackCapturesEnPassant:
4491       case IllegalMove:  /* could be a variant we don't quite understand */
4492         sprintf(user_move, "%c%c%c%c\n",
4493                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4494         break;
4495     }
4496     SendToICS(user_move);
4497     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4498         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4499 }
4500
4501 void
4502 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4503      int rf, ff, rt, ft;
4504      char promoChar;
4505      char move[7];
4506 {
4507     if (rf == DROP_RANK) {
4508         sprintf(move, "%c@%c%c\n",
4509                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4510     } else {
4511         if (promoChar == 'x' || promoChar == NULLCHAR) {
4512             sprintf(move, "%c%c%c%c\n",
4513                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4514         } else {
4515             sprintf(move, "%c%c%c%c%c\n",
4516                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4517         }
4518     }
4519 }
4520
4521 void
4522 ProcessICSInitScript(f)
4523      FILE *f;
4524 {
4525     char buf[MSG_SIZ];
4526
4527     while (fgets(buf, MSG_SIZ, f)) {
4528         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4529     }
4530
4531     fclose(f);
4532 }
4533
4534
4535 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4536 void
4537 AlphaRank(char *move, int n)
4538 {
4539 //    char *p = move, c; int x, y;
4540
4541     if (appData.debugMode) {
4542         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4543     }
4544
4545     if(move[1]=='*' && 
4546        move[2]>='0' && move[2]<='9' &&
4547        move[3]>='a' && move[3]<='x'    ) {
4548         move[1] = '@';
4549         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4550         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4551     } else
4552     if(move[0]>='0' && move[0]<='9' &&
4553        move[1]>='a' && move[1]<='x' &&
4554        move[2]>='0' && move[2]<='9' &&
4555        move[3]>='a' && move[3]<='x'    ) {
4556         /* input move, Shogi -> normal */
4557         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4558         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4559         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4560         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4561     } else
4562     if(move[1]=='@' &&
4563        move[3]>='0' && move[3]<='9' &&
4564        move[2]>='a' && move[2]<='x'    ) {
4565         move[1] = '*';
4566         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4567         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4568     } else
4569     if(
4570        move[0]>='a' && move[0]<='x' &&
4571        move[3]>='0' && move[3]<='9' &&
4572        move[2]>='a' && move[2]<='x'    ) {
4573          /* output move, normal -> Shogi */
4574         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4575         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4576         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4577         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4578         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4579     }
4580     if (appData.debugMode) {
4581         fprintf(debugFP, "   out = '%s'\n", move);
4582     }
4583 }
4584
4585 /* Parser for moves from gnuchess, ICS, or user typein box */
4586 Boolean
4587 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4588      char *move;
4589      int moveNum;
4590      ChessMove *moveType;
4591      int *fromX, *fromY, *toX, *toY;
4592      char *promoChar;
4593 {       
4594     if (appData.debugMode) {
4595         fprintf(debugFP, "move to parse: %s\n", move);
4596     }
4597     *moveType = yylexstr(moveNum, move);
4598
4599     switch (*moveType) {
4600       case WhitePromotionChancellor:
4601       case BlackPromotionChancellor:
4602       case WhitePromotionArchbishop:
4603       case BlackPromotionArchbishop:
4604       case WhitePromotionQueen:
4605       case BlackPromotionQueen:
4606       case WhitePromotionRook:
4607       case BlackPromotionRook:
4608       case WhitePromotionBishop:
4609       case BlackPromotionBishop:
4610       case WhitePromotionKnight:
4611       case BlackPromotionKnight:
4612       case WhitePromotionKing:
4613       case BlackPromotionKing:
4614       case NormalMove:
4615       case WhiteCapturesEnPassant:
4616       case BlackCapturesEnPassant:
4617       case WhiteKingSideCastle:
4618       case WhiteQueenSideCastle:
4619       case BlackKingSideCastle:
4620       case BlackQueenSideCastle:
4621       case WhiteKingSideCastleWild:
4622       case WhiteQueenSideCastleWild:
4623       case BlackKingSideCastleWild:
4624       case BlackQueenSideCastleWild:
4625       /* Code added by Tord: */
4626       case WhiteHSideCastleFR:
4627       case WhiteASideCastleFR:
4628       case BlackHSideCastleFR:
4629       case BlackASideCastleFR:
4630       /* End of code added by Tord */
4631       case IllegalMove:         /* bug or odd chess variant */
4632         *fromX = currentMoveString[0] - AAA;
4633         *fromY = currentMoveString[1] - ONE;
4634         *toX = currentMoveString[2] - AAA;
4635         *toY = currentMoveString[3] - ONE;
4636         *promoChar = currentMoveString[4];
4637         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4638             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4639     if (appData.debugMode) {
4640         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4641     }
4642             *fromX = *fromY = *toX = *toY = 0;
4643             return FALSE;
4644         }
4645         if (appData.testLegality) {
4646           return (*moveType != IllegalMove);
4647         } else {
4648           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4649                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4650         }
4651
4652       case WhiteDrop:
4653       case BlackDrop:
4654         *fromX = *moveType == WhiteDrop ?
4655           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4656           (int) CharToPiece(ToLower(currentMoveString[0]));
4657         *fromY = DROP_RANK;
4658         *toX = currentMoveString[2] - AAA;
4659         *toY = currentMoveString[3] - ONE;
4660         *promoChar = NULLCHAR;
4661         return TRUE;
4662
4663       case AmbiguousMove:
4664       case ImpossibleMove:
4665       case (ChessMove) 0:       /* end of file */
4666       case ElapsedTime:
4667       case Comment:
4668       case PGNTag:
4669       case NAG:
4670       case WhiteWins:
4671       case BlackWins:
4672       case GameIsDrawn:
4673       default:
4674     if (appData.debugMode) {
4675         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4676     }
4677         /* bug? */
4678         *fromX = *fromY = *toX = *toY = 0;
4679         *promoChar = NULLCHAR;
4680         return FALSE;
4681     }
4682 }
4683
4684
4685 void
4686 ParsePV(char *pv)
4687 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4688   int fromX, fromY, toX, toY; char promoChar;
4689   ChessMove moveType;
4690   Boolean valid;
4691   int nr = 0;
4692
4693   endPV = forwardMostMove;
4694   do {
4695     while(*pv == ' ') pv++;
4696     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4697     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4698 if(appData.debugMode){
4699 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4700 }
4701     if(!valid && nr == 0 &&
4702        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4703         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4704     }
4705     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4706     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4707     nr++;
4708     if(endPV+1 > framePtr) break; // no space, truncate
4709     if(!valid) break;
4710     endPV++;
4711     CopyBoard(boards[endPV], boards[endPV-1]);
4712     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4713     moveList[endPV-1][0] = fromX + AAA;
4714     moveList[endPV-1][1] = fromY + ONE;
4715     moveList[endPV-1][2] = toX + AAA;
4716     moveList[endPV-1][3] = toY + ONE;
4717     parseList[endPV-1][0] = NULLCHAR;
4718   } while(valid);
4719   currentMove = endPV;
4720   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4721   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4722                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4723   DrawPosition(TRUE, boards[currentMove]);
4724 }
4725
4726 static int lastX, lastY;
4727
4728 Boolean
4729 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4730 {
4731         int startPV;
4732
4733         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4734         lastX = x; lastY = y;
4735         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4736         startPV = index;
4737       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4738       index = startPV;
4739         while(buf[index] && buf[index] != '\n') index++;
4740         buf[index] = 0;
4741         ParsePV(buf+startPV);
4742         *start = startPV; *end = index-1;
4743         return TRUE;
4744 }
4745
4746 Boolean
4747 LoadPV(int x, int y)
4748 { // called on right mouse click to load PV
4749   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4750   lastX = x; lastY = y;
4751   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4752   return TRUE;
4753 }
4754
4755 void
4756 UnLoadPV()
4757 {
4758   if(endPV < 0) return;
4759   endPV = -1;
4760   currentMove = forwardMostMove;
4761   ClearPremoveHighlights();
4762   DrawPosition(TRUE, boards[currentMove]);
4763 }
4764
4765 void
4766 MovePV(int x, int y, int h)
4767 { // step through PV based on mouse coordinates (called on mouse move)
4768   int margin = h>>3, step = 0;
4769
4770   if(endPV < 0) return;
4771   // we must somehow check if right button is still down (might be released off board!)
4772   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4773   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4774   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4775   if(!step) return;
4776   lastX = x; lastY = y;
4777   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4778   currentMove += step;
4779   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4780   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4781                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4782   DrawPosition(FALSE, boards[currentMove]);
4783 }
4784
4785
4786 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4787 // All positions will have equal probability, but the current method will not provide a unique
4788 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4789 #define DARK 1
4790 #define LITE 2
4791 #define ANY 3
4792
4793 int squaresLeft[4];
4794 int piecesLeft[(int)BlackPawn];
4795 int seed, nrOfShuffles;
4796
4797 void GetPositionNumber()
4798 {       // sets global variable seed
4799         int i;
4800
4801         seed = appData.defaultFrcPosition;
4802         if(seed < 0) { // randomize based on time for negative FRC position numbers
4803                 for(i=0; i<50; i++) seed += random();
4804                 seed = random() ^ random() >> 8 ^ random() << 8;
4805                 if(seed<0) seed = -seed;
4806         }
4807 }
4808
4809 int put(Board board, int pieceType, int rank, int n, int shade)
4810 // put the piece on the (n-1)-th empty squares of the given shade
4811 {
4812         int i;
4813
4814         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4815                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4816                         board[rank][i] = (ChessSquare) pieceType;
4817                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4818                         squaresLeft[ANY]--;
4819                         piecesLeft[pieceType]--; 
4820                         return i;
4821                 }
4822         }
4823         return -1;
4824 }
4825
4826
4827 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4828 // calculate where the next piece goes, (any empty square), and put it there
4829 {
4830         int i;
4831
4832         i = seed % squaresLeft[shade];
4833         nrOfShuffles *= squaresLeft[shade];
4834         seed /= squaresLeft[shade];
4835         put(board, pieceType, rank, i, shade);
4836 }
4837
4838 void AddTwoPieces(Board board, int pieceType, int rank)
4839 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4840 {
4841         int i, n=squaresLeft[ANY], j=n-1, k;
4842
4843         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4844         i = seed % k;  // pick one
4845         nrOfShuffles *= k;
4846         seed /= k;
4847         while(i >= j) i -= j--;
4848         j = n - 1 - j; i += j;
4849         put(board, pieceType, rank, j, ANY);
4850         put(board, pieceType, rank, i, ANY);
4851 }
4852
4853 void SetUpShuffle(Board board, int number)
4854 {
4855         int i, p, first=1;
4856
4857         GetPositionNumber(); nrOfShuffles = 1;
4858
4859         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4860         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4861         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4862
4863         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4864
4865         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4866             p = (int) board[0][i];
4867             if(p < (int) BlackPawn) piecesLeft[p] ++;
4868             board[0][i] = EmptySquare;
4869         }
4870
4871         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4872             // shuffles restricted to allow normal castling put KRR first
4873             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4874                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4875             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4876                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4877             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4878                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4879             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4880                 put(board, WhiteRook, 0, 0, ANY);
4881             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4882         }
4883
4884         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4885             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4886             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4887                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4888                 while(piecesLeft[p] >= 2) {
4889                     AddOnePiece(board, p, 0, LITE);
4890                     AddOnePiece(board, p, 0, DARK);
4891                 }
4892                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4893             }
4894
4895         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4896             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4897             // but we leave King and Rooks for last, to possibly obey FRC restriction
4898             if(p == (int)WhiteRook) continue;
4899             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4900             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4901         }
4902
4903         // now everything is placed, except perhaps King (Unicorn) and Rooks
4904
4905         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4906             // Last King gets castling rights
4907             while(piecesLeft[(int)WhiteUnicorn]) {
4908                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4909                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4910             }
4911
4912             while(piecesLeft[(int)WhiteKing]) {
4913                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4914                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4915             }
4916
4917
4918         } else {
4919             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4920             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4921         }
4922
4923         // Only Rooks can be left; simply place them all
4924         while(piecesLeft[(int)WhiteRook]) {
4925                 i = put(board, WhiteRook, 0, 0, ANY);
4926                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4927                         if(first) {
4928                                 first=0;
4929                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4930                         }
4931                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4932                 }
4933         }
4934         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4935             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4936         }
4937
4938         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4939 }
4940
4941 int SetCharTable( char *table, const char * map )
4942 /* [HGM] moved here from winboard.c because of its general usefulness */
4943 /*       Basically a safe strcpy that uses the last character as King */
4944 {
4945     int result = FALSE; int NrPieces;
4946
4947     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4948                     && NrPieces >= 12 && !(NrPieces&1)) {
4949         int i; /* [HGM] Accept even length from 12 to 34 */
4950
4951         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4952         for( i=0; i<NrPieces/2-1; i++ ) {
4953             table[i] = map[i];
4954             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4955         }
4956         table[(int) WhiteKing]  = map[NrPieces/2-1];
4957         table[(int) BlackKing]  = map[NrPieces-1];
4958
4959         result = TRUE;
4960     }
4961
4962     return result;
4963 }
4964
4965 void Prelude(Board board)
4966 {       // [HGM] superchess: random selection of exo-pieces
4967         int i, j, k; ChessSquare p; 
4968         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4969
4970         GetPositionNumber(); // use FRC position number
4971
4972         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4973             SetCharTable(pieceToChar, appData.pieceToCharTable);
4974             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4975                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4976         }
4977
4978         j = seed%4;                 seed /= 4; 
4979         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4980         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4981         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4982         j = seed%3 + (seed%3 >= j); seed /= 3; 
4983         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4984         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4985         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4986         j = seed%3;                 seed /= 3; 
4987         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4988         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4989         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4990         j = seed%2 + (seed%2 >= j); seed /= 2; 
4991         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4992         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4993         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4994         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4995         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4996         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4997         put(board, exoPieces[0],    0, 0, ANY);
4998         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4999 }
5000
5001 void
5002 InitPosition(redraw)
5003      int redraw;
5004 {
5005     ChessSquare (* pieces)[BOARD_FILES];
5006     int i, j, pawnRow, overrule,
5007     oldx = gameInfo.boardWidth,
5008     oldy = gameInfo.boardHeight,
5009     oldh = gameInfo.holdingsWidth,
5010     oldv = gameInfo.variant;
5011
5012     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5013
5014     /* [AS] Initialize pv info list [HGM] and game status */
5015     {
5016         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5017             pvInfoList[i].depth = 0;
5018             boards[i][EP_STATUS] = EP_NONE;
5019             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5020         }
5021
5022         initialRulePlies = 0; /* 50-move counter start */
5023
5024         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5025         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5026     }
5027
5028     
5029     /* [HGM] logic here is completely changed. In stead of full positions */
5030     /* the initialized data only consist of the two backranks. The switch */
5031     /* selects which one we will use, which is than copied to the Board   */
5032     /* initialPosition, which for the rest is initialized by Pawns and    */
5033     /* empty squares. This initial position is then copied to boards[0],  */
5034     /* possibly after shuffling, so that it remains available.            */
5035
5036     gameInfo.holdingsWidth = 0; /* default board sizes */
5037     gameInfo.boardWidth    = 8;
5038     gameInfo.boardHeight   = 8;
5039     gameInfo.holdingsSize  = 0;
5040     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5041     for(i=0; i<BOARD_FILES-2; i++)
5042       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5043     initialPosition[EP_STATUS] = EP_NONE;
5044     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5045
5046     switch (gameInfo.variant) {
5047     case VariantFischeRandom:
5048       shuffleOpenings = TRUE;
5049     default:
5050       pieces = FIDEArray;
5051       break;
5052     case VariantShatranj:
5053       pieces = ShatranjArray;
5054       nrCastlingRights = 0;
5055       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5056       break;
5057     case VariantMakruk:
5058       pieces = makrukArray;
5059       nrCastlingRights = 0;
5060       startedFromSetupPosition = TRUE;
5061       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5062       break;
5063     case VariantTwoKings:
5064       pieces = twoKingsArray;
5065       break;
5066     case VariantCapaRandom:
5067       shuffleOpenings = TRUE;
5068     case VariantCapablanca:
5069       pieces = CapablancaArray;
5070       gameInfo.boardWidth = 10;
5071       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5072       break;
5073     case VariantGothic:
5074       pieces = GothicArray;
5075       gameInfo.boardWidth = 10;
5076       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5077       break;
5078     case VariantJanus:
5079       pieces = JanusArray;
5080       gameInfo.boardWidth = 10;
5081       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5082       nrCastlingRights = 6;
5083         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5084         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5085         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5086         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5087         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5088         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5089       break;
5090     case VariantFalcon:
5091       pieces = FalconArray;
5092       gameInfo.boardWidth = 10;
5093       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5094       break;
5095     case VariantXiangqi:
5096       pieces = XiangqiArray;
5097       gameInfo.boardWidth  = 9;
5098       gameInfo.boardHeight = 10;
5099       nrCastlingRights = 0;
5100       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5101       break;
5102     case VariantShogi:
5103       pieces = ShogiArray;
5104       gameInfo.boardWidth  = 9;
5105       gameInfo.boardHeight = 9;
5106       gameInfo.holdingsSize = 7;
5107       nrCastlingRights = 0;
5108       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5109       break;
5110     case VariantCourier:
5111       pieces = CourierArray;
5112       gameInfo.boardWidth  = 12;
5113       nrCastlingRights = 0;
5114       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5115       break;
5116     case VariantKnightmate:
5117       pieces = KnightmateArray;
5118       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5119       break;
5120     case VariantFairy:
5121       pieces = fairyArray;
5122       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5123       break;
5124     case VariantGreat:
5125       pieces = GreatArray;
5126       gameInfo.boardWidth = 10;
5127       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5128       gameInfo.holdingsSize = 8;
5129       break;
5130     case VariantSuper:
5131       pieces = FIDEArray;
5132       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5133       gameInfo.holdingsSize = 8;
5134       startedFromSetupPosition = TRUE;
5135       break;
5136     case VariantCrazyhouse:
5137     case VariantBughouse:
5138       pieces = FIDEArray;
5139       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5140       gameInfo.holdingsSize = 5;
5141       break;
5142     case VariantWildCastle:
5143       pieces = FIDEArray;
5144       /* !!?shuffle with kings guaranteed to be on d or e file */
5145       shuffleOpenings = 1;
5146       break;
5147     case VariantNoCastle:
5148       pieces = FIDEArray;
5149       nrCastlingRights = 0;
5150       /* !!?unconstrained back-rank shuffle */
5151       shuffleOpenings = 1;
5152       break;
5153     }
5154
5155     overrule = 0;
5156     if(appData.NrFiles >= 0) {
5157         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5158         gameInfo.boardWidth = appData.NrFiles;
5159     }
5160     if(appData.NrRanks >= 0) {
5161         gameInfo.boardHeight = appData.NrRanks;
5162     }
5163     if(appData.holdingsSize >= 0) {
5164         i = appData.holdingsSize;
5165         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5166         gameInfo.holdingsSize = i;
5167     }
5168     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5169     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5170         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5171
5172     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5173     if(pawnRow < 1) pawnRow = 1;
5174     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5175
5176     /* User pieceToChar list overrules defaults */
5177     if(appData.pieceToCharTable != NULL)
5178         SetCharTable(pieceToChar, appData.pieceToCharTable);
5179
5180     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5181
5182         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5183             s = (ChessSquare) 0; /* account holding counts in guard band */
5184         for( i=0; i<BOARD_HEIGHT; i++ )
5185             initialPosition[i][j] = s;
5186
5187         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5188         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5189         initialPosition[pawnRow][j] = WhitePawn;
5190         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5191         if(gameInfo.variant == VariantXiangqi) {
5192             if(j&1) {
5193                 initialPosition[pawnRow][j] = 
5194                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5195                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5196                    initialPosition[2][j] = WhiteCannon;
5197                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5198                 }
5199             }
5200         }
5201         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5202     }
5203     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5204
5205             j=BOARD_LEFT+1;
5206             initialPosition[1][j] = WhiteBishop;
5207             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5208             j=BOARD_RGHT-2;
5209             initialPosition[1][j] = WhiteRook;
5210             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5211     }
5212
5213     if( nrCastlingRights == -1) {
5214         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5215         /*       This sets default castling rights from none to normal corners   */
5216         /* Variants with other castling rights must set them themselves above    */
5217         nrCastlingRights = 6;
5218        
5219         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5220         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5221         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5222         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5223         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5224         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5225      }
5226
5227      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5228      if(gameInfo.variant == VariantGreat) { // promotion commoners
5229         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5230         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5231         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5232         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5233      }
5234   if (appData.debugMode) {
5235     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5236   }
5237     if(shuffleOpenings) {
5238         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5239         startedFromSetupPosition = TRUE;
5240     }
5241     if(startedFromPositionFile) {
5242       /* [HGM] loadPos: use PositionFile for every new game */
5243       CopyBoard(initialPosition, filePosition);
5244       for(i=0; i<nrCastlingRights; i++)
5245           initialRights[i] = filePosition[CASTLING][i];
5246       startedFromSetupPosition = TRUE;
5247     }
5248
5249     CopyBoard(boards[0], initialPosition);
5250
5251     if(oldx != gameInfo.boardWidth ||
5252        oldy != gameInfo.boardHeight ||
5253        oldh != gameInfo.holdingsWidth
5254 #ifdef GOTHIC
5255        || oldv == VariantGothic ||        // For licensing popups
5256        gameInfo.variant == VariantGothic
5257 #endif
5258 #ifdef FALCON
5259        || oldv == VariantFalcon ||
5260        gameInfo.variant == VariantFalcon
5261 #endif
5262                                          )
5263             InitDrawingSizes(-2 ,0);
5264
5265     if (redraw)
5266       DrawPosition(TRUE, boards[currentMove]);
5267 }
5268
5269 void
5270 SendBoard(cps, moveNum)
5271      ChessProgramState *cps;
5272      int moveNum;
5273 {
5274     char message[MSG_SIZ];
5275     
5276     if (cps->useSetboard) {
5277       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5278       sprintf(message, "setboard %s\n", fen);
5279       SendToProgram(message, cps);
5280       free(fen);
5281
5282     } else {
5283       ChessSquare *bp;
5284       int i, j;
5285       /* Kludge to set black to move, avoiding the troublesome and now
5286        * deprecated "black" command.
5287        */
5288       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5289
5290       SendToProgram("edit\n", cps);
5291       SendToProgram("#\n", cps);
5292       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5293         bp = &boards[moveNum][i][BOARD_LEFT];
5294         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5295           if ((int) *bp < (int) BlackPawn) {
5296             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5297                     AAA + j, ONE + i);
5298             if(message[0] == '+' || message[0] == '~') {
5299                 sprintf(message, "%c%c%c+\n",
5300                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5301                         AAA + j, ONE + i);
5302             }
5303             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5304                 message[1] = BOARD_RGHT   - 1 - j + '1';
5305                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5306             }
5307             SendToProgram(message, cps);
5308           }
5309         }
5310       }
5311     
5312       SendToProgram("c\n", cps);
5313       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5314         bp = &boards[moveNum][i][BOARD_LEFT];
5315         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5316           if (((int) *bp != (int) EmptySquare)
5317               && ((int) *bp >= (int) BlackPawn)) {
5318             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5319                     AAA + j, ONE + i);
5320             if(message[0] == '+' || message[0] == '~') {
5321                 sprintf(message, "%c%c%c+\n",
5322                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5323                         AAA + j, ONE + i);
5324             }
5325             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5326                 message[1] = BOARD_RGHT   - 1 - j + '1';
5327                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5328             }
5329             SendToProgram(message, cps);
5330           }
5331         }
5332       }
5333     
5334       SendToProgram(".\n", cps);
5335     }
5336     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5337 }
5338
5339 int
5340 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5341 {
5342     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5343     /* [HGM] add Shogi promotions */
5344     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5345     ChessSquare piece;
5346     ChessMove moveType;
5347     Boolean premove;
5348
5349     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5350     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5351
5352     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5353       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5354         return FALSE;
5355
5356     piece = boards[currentMove][fromY][fromX];
5357     if(gameInfo.variant == VariantShogi) {
5358         promotionZoneSize = 3;
5359         highestPromotingPiece = (int)WhiteFerz;
5360     } else if(gameInfo.variant == VariantMakruk) {
5361         promotionZoneSize = 3;
5362     }
5363
5364     // next weed out all moves that do not touch the promotion zone at all
5365     if((int)piece >= BlackPawn) {
5366         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5367              return FALSE;
5368         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5369     } else {
5370         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5371            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5372     }
5373
5374     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5375
5376     // weed out mandatory Shogi promotions
5377     if(gameInfo.variant == VariantShogi) {
5378         if(piece >= BlackPawn) {
5379             if(toY == 0 && piece == BlackPawn ||
5380                toY == 0 && piece == BlackQueen ||
5381                toY <= 1 && piece == BlackKnight) {
5382                 *promoChoice = '+';
5383                 return FALSE;
5384             }
5385         } else {
5386             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5387                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5388                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5389                 *promoChoice = '+';
5390                 return FALSE;
5391             }
5392         }
5393     }
5394
5395     // weed out obviously illegal Pawn moves
5396     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5397         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5398         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5399         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5400         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5401         // note we are not allowed to test for valid (non-)capture, due to premove
5402     }
5403
5404     // we either have a choice what to promote to, or (in Shogi) whether to promote
5405     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5406         *promoChoice = PieceToChar(BlackFerz);  // no choice
5407         return FALSE;
5408     }
5409     if(appData.alwaysPromoteToQueen) { // predetermined
5410         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5411              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5412         else *promoChoice = PieceToChar(BlackQueen);
5413         return FALSE;
5414     }
5415
5416     // suppress promotion popup on illegal moves that are not premoves
5417     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5418               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5419     if(appData.testLegality && !premove) {
5420         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5421                         fromY, fromX, toY, toX, NULLCHAR);
5422         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5423            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5424             return FALSE;
5425     }
5426
5427     return TRUE;
5428 }
5429
5430 int
5431 InPalace(row, column)
5432      int row, column;
5433 {   /* [HGM] for Xiangqi */
5434     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5435          column < (BOARD_WIDTH + 4)/2 &&
5436          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5437     return FALSE;
5438 }
5439
5440 int
5441 PieceForSquare (x, y)
5442      int x;
5443      int y;
5444 {
5445   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5446      return -1;
5447   else
5448      return boards[currentMove][y][x];
5449 }
5450
5451 int
5452 OKToStartUserMove(x, y)
5453      int x, y;
5454 {
5455     ChessSquare from_piece;
5456     int white_piece;
5457
5458     if (matchMode) return FALSE;
5459     if (gameMode == EditPosition) return TRUE;
5460
5461     if (x >= 0 && y >= 0)
5462       from_piece = boards[currentMove][y][x];
5463     else
5464       from_piece = EmptySquare;
5465
5466     if (from_piece == EmptySquare) return FALSE;
5467
5468     white_piece = (int)from_piece >= (int)WhitePawn &&
5469       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5470
5471     switch (gameMode) {
5472       case PlayFromGameFile:
5473       case AnalyzeFile:
5474       case TwoMachinesPlay:
5475       case EndOfGame:
5476         return FALSE;
5477
5478       case IcsObserving:
5479       case IcsIdle:
5480         return FALSE;
5481
5482       case MachinePlaysWhite:
5483       case IcsPlayingBlack:
5484         if (appData.zippyPlay) return FALSE;
5485         if (white_piece) {
5486             DisplayMoveError(_("You are playing Black"));
5487             return FALSE;
5488         }
5489         break;
5490
5491       case MachinePlaysBlack:
5492       case IcsPlayingWhite:
5493         if (appData.zippyPlay) return FALSE;
5494         if (!white_piece) {
5495             DisplayMoveError(_("You are playing White"));
5496             return FALSE;
5497         }
5498         break;
5499
5500       case EditGame:
5501         if (!white_piece && WhiteOnMove(currentMove)) {
5502             DisplayMoveError(_("It is White's turn"));
5503             return FALSE;
5504         }           
5505         if (white_piece && !WhiteOnMove(currentMove)) {
5506             DisplayMoveError(_("It is Black's turn"));
5507             return FALSE;
5508         }           
5509         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5510             /* Editing correspondence game history */
5511             /* Could disallow this or prompt for confirmation */
5512             cmailOldMove = -1;
5513         }
5514         break;
5515
5516       case BeginningOfGame:
5517         if (appData.icsActive) return FALSE;
5518         if (!appData.noChessProgram) {
5519             if (!white_piece) {
5520                 DisplayMoveError(_("You are playing White"));
5521                 return FALSE;
5522             }
5523         }
5524         break;
5525         
5526       case Training:
5527         if (!white_piece && WhiteOnMove(currentMove)) {
5528             DisplayMoveError(_("It is White's turn"));
5529             return FALSE;
5530         }           
5531         if (white_piece && !WhiteOnMove(currentMove)) {
5532             DisplayMoveError(_("It is Black's turn"));
5533             return FALSE;
5534         }           
5535         break;
5536
5537       default:
5538       case IcsExamining:
5539         break;
5540     }
5541     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5542         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5543         && gameMode != AnalyzeFile && gameMode != Training) {
5544         DisplayMoveError(_("Displayed position is not current"));
5545         return FALSE;
5546     }
5547     return TRUE;
5548 }
5549
5550 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5551 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5552 int lastLoadGameUseList = FALSE;
5553 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5554 ChessMove lastLoadGameStart = (ChessMove) 0;
5555
5556 ChessMove
5557 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5558      int fromX, fromY, toX, toY;
5559      int promoChar;
5560      Boolean captureOwn;
5561 {
5562     ChessMove moveType;
5563     ChessSquare pdown, pup;
5564
5565     /* Check if the user is playing in turn.  This is complicated because we
5566        let the user "pick up" a piece before it is his turn.  So the piece he
5567        tried to pick up may have been captured by the time he puts it down!
5568        Therefore we use the color the user is supposed to be playing in this
5569        test, not the color of the piece that is currently on the starting
5570        square---except in EditGame mode, where the user is playing both
5571        sides; fortunately there the capture race can't happen.  (It can
5572        now happen in IcsExamining mode, but that's just too bad.  The user
5573        will get a somewhat confusing message in that case.)
5574        */
5575
5576     switch (gameMode) {
5577       case PlayFromGameFile:
5578       case AnalyzeFile:
5579       case TwoMachinesPlay:
5580       case EndOfGame:
5581       case IcsObserving:
5582       case IcsIdle:
5583         /* We switched into a game mode where moves are not accepted,
5584            perhaps while the mouse button was down. */
5585         return ImpossibleMove;
5586
5587       case MachinePlaysWhite:
5588         /* User is moving for Black */
5589         if (WhiteOnMove(currentMove)) {
5590             DisplayMoveError(_("It is White's turn"));
5591             return ImpossibleMove;
5592         }
5593         break;
5594
5595       case MachinePlaysBlack:
5596         /* User is moving for White */
5597         if (!WhiteOnMove(currentMove)) {
5598             DisplayMoveError(_("It is Black's turn"));
5599             return ImpossibleMove;
5600         }
5601         break;
5602
5603       case EditGame:
5604       case IcsExamining:
5605       case BeginningOfGame:
5606       case AnalyzeMode:
5607       case Training:
5608         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5609             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5610             /* User is moving for Black */
5611             if (WhiteOnMove(currentMove)) {
5612                 DisplayMoveError(_("It is White's turn"));
5613                 return ImpossibleMove;
5614             }
5615         } else {
5616             /* User is moving for White */
5617             if (!WhiteOnMove(currentMove)) {
5618                 DisplayMoveError(_("It is Black's turn"));
5619                 return ImpossibleMove;
5620             }
5621         }
5622         break;
5623
5624       case IcsPlayingBlack:
5625         /* User is moving for Black */
5626         if (WhiteOnMove(currentMove)) {
5627             if (!appData.premove) {
5628                 DisplayMoveError(_("It is White's turn"));
5629             } else if (toX >= 0 && toY >= 0) {
5630                 premoveToX = toX;
5631                 premoveToY = toY;
5632                 premoveFromX = fromX;
5633                 premoveFromY = fromY;
5634                 premovePromoChar = promoChar;
5635                 gotPremove = 1;
5636                 if (appData.debugMode) 
5637                     fprintf(debugFP, "Got premove: fromX %d,"
5638                             "fromY %d, toX %d, toY %d\n",
5639                             fromX, fromY, toX, toY);
5640             }
5641             return ImpossibleMove;
5642         }
5643         break;
5644
5645       case IcsPlayingWhite:
5646         /* User is moving for White */
5647         if (!WhiteOnMove(currentMove)) {
5648             if (!appData.premove) {
5649                 DisplayMoveError(_("It is Black's turn"));
5650             } else if (toX >= 0 && toY >= 0) {
5651                 premoveToX = toX;
5652                 premoveToY = toY;
5653                 premoveFromX = fromX;
5654                 premoveFromY = fromY;
5655                 premovePromoChar = promoChar;
5656                 gotPremove = 1;
5657                 if (appData.debugMode) 
5658                     fprintf(debugFP, "Got premove: fromX %d,"
5659                             "fromY %d, toX %d, toY %d\n",
5660                             fromX, fromY, toX, toY);
5661             }
5662             return ImpossibleMove;
5663         }
5664         break;
5665
5666       default:
5667         break;
5668
5669       case EditPosition:
5670         /* EditPosition, empty square, or different color piece;
5671            click-click move is possible */
5672         if (toX == -2 || toY == -2) {
5673             boards[0][fromY][fromX] = EmptySquare;
5674             return AmbiguousMove;
5675         } else if (toX >= 0 && toY >= 0) {
5676             boards[0][toY][toX] = boards[0][fromY][fromX];
5677             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5678                 if(boards[0][fromY][0] != EmptySquare) {
5679                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5680                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5681                 }
5682             } else
5683             if(fromX == BOARD_RGHT+1) {
5684                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5685                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5686                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5687                 }
5688             } else
5689             boards[0][fromY][fromX] = EmptySquare;
5690             return AmbiguousMove;
5691         }
5692         return ImpossibleMove;
5693     }
5694
5695     if(toX < 0 || toY < 0) return ImpossibleMove;
5696     pdown = boards[currentMove][fromY][fromX];
5697     pup = boards[currentMove][toY][toX];
5698
5699     /* [HGM] If move started in holdings, it means a drop */
5700     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5701          if( pup != EmptySquare ) return ImpossibleMove;
5702          if(appData.testLegality) {
5703              /* it would be more logical if LegalityTest() also figured out
5704               * which drops are legal. For now we forbid pawns on back rank.
5705               * Shogi is on its own here...
5706               */
5707              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5708                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5709                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5710          }
5711          return WhiteDrop; /* Not needed to specify white or black yet */
5712     }
5713
5714     /* [HGM] always test for legality, to get promotion info */
5715     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5716                                          fromY, fromX, toY, toX, promoChar);
5717     /* [HGM] but possibly ignore an IllegalMove result */
5718     if (appData.testLegality) {
5719         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5720             DisplayMoveError(_("Illegal move"));
5721             return ImpossibleMove;
5722         }
5723     }
5724
5725     return moveType;
5726     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5727        function is made into one that returns an OK move type if FinishMove
5728        should be called. This to give the calling driver routine the
5729        opportunity to finish the userMove input with a promotion popup,
5730        without bothering the user with this for invalid or illegal moves */
5731
5732 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5733 }
5734
5735 /* Common tail of UserMoveEvent and DropMenuEvent */
5736 int
5737 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5738      ChessMove moveType;
5739      int fromX, fromY, toX, toY;
5740      /*char*/int promoChar;
5741 {
5742     char *bookHit = 0;
5743
5744     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5745         // [HGM] superchess: suppress promotions to non-available piece
5746         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5747         if(WhiteOnMove(currentMove)) {
5748             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5749         } else {
5750             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5751         }
5752     }
5753
5754     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5755        move type in caller when we know the move is a legal promotion */
5756     if(moveType == NormalMove && promoChar)
5757         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5758
5759     /* [HGM] convert drag-and-drop piece drops to standard form */
5760     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5761          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5762            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5763                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5764            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5765            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5766            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5767            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5768          fromY = DROP_RANK;
5769     }
5770
5771     /* [HGM] <popupFix> The following if has been moved here from
5772        UserMoveEvent(). Because it seemed to belong here (why not allow
5773        piece drops in training games?), and because it can only be
5774        performed after it is known to what we promote. */
5775     if (gameMode == Training) {
5776       /* compare the move played on the board to the next move in the
5777        * game. If they match, display the move and the opponent's response. 
5778        * If they don't match, display an error message.
5779        */
5780       int saveAnimate;
5781       Board testBoard;
5782       CopyBoard(testBoard, boards[currentMove]);
5783       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5784
5785       if (CompareBoards(testBoard, boards[currentMove+1])) {
5786         ForwardInner(currentMove+1);
5787
5788         /* Autoplay the opponent's response.
5789          * if appData.animate was TRUE when Training mode was entered,
5790          * the response will be animated.
5791          */
5792         saveAnimate = appData.animate;
5793         appData.animate = animateTraining;
5794         ForwardInner(currentMove+1);
5795         appData.animate = saveAnimate;
5796
5797         /* check for the end of the game */
5798         if (currentMove >= forwardMostMove) {
5799           gameMode = PlayFromGameFile;
5800           ModeHighlight();
5801           SetTrainingModeOff();
5802           DisplayInformation(_("End of game"));
5803         }
5804       } else {
5805         DisplayError(_("Incorrect move"), 0);
5806       }
5807       return 1;
5808     }
5809
5810   /* Ok, now we know that the move is good, so we can kill
5811      the previous line in Analysis Mode */
5812   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5813                                 && currentMove < forwardMostMove) {
5814     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5815   }
5816
5817   /* If we need the chess program but it's dead, restart it */
5818   ResurrectChessProgram();
5819
5820   /* A user move restarts a paused game*/
5821   if (pausing)
5822     PauseEvent();
5823
5824   thinkOutput[0] = NULLCHAR;
5825
5826   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5827
5828   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5829
5830   if (gameMode == BeginningOfGame) {
5831     if (appData.noChessProgram) {
5832       gameMode = EditGame;
5833       SetGameInfo();
5834     } else {
5835       char buf[MSG_SIZ];
5836       gameMode = MachinePlaysBlack;
5837       StartClocks();
5838       SetGameInfo();
5839       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5840       DisplayTitle(buf);
5841       if (first.sendName) {
5842         sprintf(buf, "name %s\n", gameInfo.white);
5843         SendToProgram(buf, &first);
5844       }
5845       StartClocks();
5846     }
5847     ModeHighlight();
5848   }
5849
5850   /* Relay move to ICS or chess engine */
5851   if (appData.icsActive) {
5852     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5853         gameMode == IcsExamining) {
5854       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5855         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5856         SendToICS("draw ");
5857         SendMoveToICS(moveType, fromX, fromY, toX, toY);
5858       }
5859       // also send plain move, in case ICS does not understand atomic claims
5860       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5861       ics_user_moved = 1;
5862     }
5863   } else {
5864     if (first.sendTime && (gameMode == BeginningOfGame ||
5865                            gameMode == MachinePlaysWhite ||
5866                            gameMode == MachinePlaysBlack)) {
5867       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5868     }
5869     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5870          // [HGM] book: if program might be playing, let it use book
5871         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5872         first.maybeThinking = TRUE;
5873     } else SendMoveToProgram(forwardMostMove-1, &first);
5874     if (currentMove == cmailOldMove + 1) {
5875       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5876     }
5877   }
5878
5879   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5880
5881   switch (gameMode) {
5882   case EditGame:
5883     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5884     case MT_NONE:
5885     case MT_CHECK:
5886       break;
5887     case MT_CHECKMATE:
5888     case MT_STAINMATE:
5889       if (WhiteOnMove(currentMove)) {
5890         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5891       } else {
5892         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5893       }
5894       break;
5895     case MT_STALEMATE:
5896       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5897       break;
5898     }
5899     break;
5900     
5901   case MachinePlaysBlack:
5902   case MachinePlaysWhite:
5903     /* disable certain menu options while machine is thinking */
5904     SetMachineThinkingEnables();
5905     break;
5906
5907   default:
5908     break;
5909   }
5910
5911   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5912         
5913   if(bookHit) { // [HGM] book: simulate book reply
5914         static char bookMove[MSG_SIZ]; // a bit generous?
5915
5916         programStats.nodes = programStats.depth = programStats.time = 
5917         programStats.score = programStats.got_only_move = 0;
5918         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5919
5920         strcpy(bookMove, "move ");
5921         strcat(bookMove, bookHit);
5922         HandleMachineMove(bookMove, &first);
5923   }
5924   return 1;
5925 }
5926
5927 void
5928 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5929      int fromX, fromY, toX, toY;
5930      int promoChar;
5931 {
5932     /* [HGM] This routine was added to allow calling of its two logical
5933        parts from other modules in the old way. Before, UserMoveEvent()
5934        automatically called FinishMove() if the move was OK, and returned
5935        otherwise. I separated the two, in order to make it possible to
5936        slip a promotion popup in between. But that it always needs two
5937        calls, to the first part, (now called UserMoveTest() ), and to
5938        FinishMove if the first part succeeded. Calls that do not need
5939        to do anything in between, can call this routine the old way. 
5940     */
5941     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5942 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5943     if(moveType == AmbiguousMove)
5944         DrawPosition(FALSE, boards[currentMove]);
5945     else if(moveType != ImpossibleMove && moveType != Comment)
5946         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5947 }
5948
5949 void
5950 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5951      Board board;
5952      int flags;
5953      ChessMove kind;
5954      int rf, ff, rt, ft;
5955      VOIDSTAR closure;
5956 {
5957     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5958     Markers *m = (Markers *) closure;
5959     if(rf == fromY && ff == fromX)
5960         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5961                          || kind == WhiteCapturesEnPassant
5962                          || kind == BlackCapturesEnPassant);
5963     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5964 }
5965
5966 void
5967 MarkTargetSquares(int clear)
5968 {
5969   int x, y;
5970   if(!appData.markers || !appData.highlightDragging || 
5971      !appData.testLegality || gameMode == EditPosition) return;
5972   if(clear) {
5973     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5974   } else {
5975     int capt = 0;
5976     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5977     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5978       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5979       if(capt)
5980       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5981     }
5982   }
5983   DrawPosition(TRUE, NULL);
5984 }
5985
5986 void LeftClick(ClickType clickType, int xPix, int yPix)
5987 {
5988     int x, y;
5989     Boolean saveAnimate;
5990     static int second = 0, promotionChoice = 0;
5991     char promoChoice = NULLCHAR;
5992
5993     if(appData.seekGraph && appData.icsActive && loggedOn &&
5994         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
5995         SeekGraphClick(clickType, xPix, yPix, 0);
5996         return;
5997     }
5998
5999     if (clickType == Press) ErrorPopDown();
6000     MarkTargetSquares(1);
6001
6002     x = EventToSquare(xPix, BOARD_WIDTH);
6003     y = EventToSquare(yPix, BOARD_HEIGHT);
6004     if (!flipView && y >= 0) {
6005         y = BOARD_HEIGHT - 1 - y;
6006     }
6007     if (flipView && x >= 0) {
6008         x = BOARD_WIDTH - 1 - x;
6009     }
6010
6011     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6012         if(clickType == Release) return; // ignore upclick of click-click destination
6013         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6014         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6015         if(gameInfo.holdingsWidth && 
6016                 (WhiteOnMove(currentMove) 
6017                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6018                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6019             // click in right holdings, for determining promotion piece
6020             ChessSquare p = boards[currentMove][y][x];
6021             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6022             if(p != EmptySquare) {
6023                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6024                 fromX = fromY = -1;
6025                 return;
6026             }
6027         }
6028         DrawPosition(FALSE, boards[currentMove]);
6029         return;
6030     }
6031
6032     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6033     if(clickType == Press
6034             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6035               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6036               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6037         return;
6038
6039     if (fromX == -1) {
6040         if (clickType == Press) {
6041             /* First square */
6042             if (OKToStartUserMove(x, y)) {
6043                 fromX = x;
6044                 fromY = y;
6045                 second = 0;
6046                 MarkTargetSquares(0);
6047                 DragPieceBegin(xPix, yPix);
6048                 if (appData.highlightDragging) {
6049                     SetHighlights(x, y, -1, -1);
6050                 }
6051             }
6052         }
6053         return;
6054     }
6055
6056     /* fromX != -1 */
6057     if (clickType == Press && gameMode != EditPosition) {
6058         ChessSquare fromP;
6059         ChessSquare toP;
6060         int frc;
6061
6062         // ignore off-board to clicks
6063         if(y < 0 || x < 0) return;
6064
6065         /* Check if clicking again on the same color piece */
6066         fromP = boards[currentMove][fromY][fromX];
6067         toP = boards[currentMove][y][x];
6068         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6069         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6070              WhitePawn <= toP && toP <= WhiteKing &&
6071              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6072              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6073             (BlackPawn <= fromP && fromP <= BlackKing && 
6074              BlackPawn <= toP && toP <= BlackKing &&
6075              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6076              !(fromP == BlackKing && toP == BlackRook && frc))) {
6077             /* Clicked again on same color piece -- changed his mind */
6078             second = (x == fromX && y == fromY);
6079             if (appData.highlightDragging) {
6080                 SetHighlights(x, y, -1, -1);
6081             } else {
6082                 ClearHighlights();
6083             }
6084             if (OKToStartUserMove(x, y)) {
6085                 fromX = x;
6086                 fromY = y;
6087                 MarkTargetSquares(0);
6088                 DragPieceBegin(xPix, yPix);
6089             }
6090             return;
6091         }
6092         // ignore clicks on holdings
6093         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6094     }
6095
6096     if (clickType == Release && x == fromX && y == fromY) {
6097         DragPieceEnd(xPix, yPix);
6098         if (appData.animateDragging) {
6099             /* Undo animation damage if any */
6100             DrawPosition(FALSE, NULL);
6101         }
6102         if (second) {
6103             /* Second up/down in same square; just abort move */
6104             second = 0;
6105             fromX = fromY = -1;
6106             ClearHighlights();
6107             gotPremove = 0;
6108             ClearPremoveHighlights();
6109         } else {
6110             /* First upclick in same square; start click-click mode */
6111             SetHighlights(x, y, -1, -1);
6112         }
6113         return;
6114     }
6115
6116     /* we now have a different from- and (possibly off-board) to-square */
6117     /* Completed move */
6118     toX = x;
6119     toY = y;
6120     saveAnimate = appData.animate;
6121     if (clickType == Press) {
6122         /* Finish clickclick move */
6123         if (appData.animate || appData.highlightLastMove) {
6124             SetHighlights(fromX, fromY, toX, toY);
6125         } else {
6126             ClearHighlights();
6127         }
6128     } else {
6129         /* Finish drag move */
6130         if (appData.highlightLastMove) {
6131             SetHighlights(fromX, fromY, toX, toY);
6132         } else {
6133             ClearHighlights();
6134         }
6135         DragPieceEnd(xPix, yPix);
6136         /* Don't animate move and drag both */
6137         appData.animate = FALSE;
6138     }
6139
6140     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6141     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6142         ChessSquare piece = boards[currentMove][fromY][fromX];
6143         if(gameMode == EditPosition && piece != EmptySquare &&
6144            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6145             int n;
6146              
6147             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6148                 n = PieceToNumber(piece - (int)BlackPawn);
6149                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6150                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6151                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6152             } else
6153             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6154                 n = PieceToNumber(piece);
6155                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6156                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6157                 boards[currentMove][n][BOARD_WIDTH-2]++;
6158             }
6159             boards[currentMove][fromY][fromX] = EmptySquare;
6160         }
6161         ClearHighlights();
6162         fromX = fromY = -1;
6163         DrawPosition(TRUE, boards[currentMove]);
6164         return;
6165     }
6166
6167     // off-board moves should not be highlighted
6168     if(x < 0 || x < 0) ClearHighlights();
6169
6170     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6171         SetHighlights(fromX, fromY, toX, toY);
6172         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6173             // [HGM] super: promotion to captured piece selected from holdings
6174             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6175             promotionChoice = TRUE;
6176             // kludge follows to temporarily execute move on display, without promoting yet
6177             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6178             boards[currentMove][toY][toX] = p;
6179             DrawPosition(FALSE, boards[currentMove]);
6180             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6181             boards[currentMove][toY][toX] = q;
6182             DisplayMessage("Click in holdings to choose piece", "");
6183             return;
6184         }
6185         PromotionPopUp();
6186     } else {
6187         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6188         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6189         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6190         fromX = fromY = -1;
6191     }
6192     appData.animate = saveAnimate;
6193     if (appData.animate || appData.animateDragging) {
6194         /* Undo animation damage if needed */
6195         DrawPosition(FALSE, NULL);
6196     }
6197 }
6198
6199 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6200 {   // front-end-free part taken out of PieceMenuPopup
6201     int whichMenu; int xSqr, ySqr;
6202
6203     if(seekGraphUp) { // [HGM] seekgraph
6204         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6205         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6206         return -2;
6207     }
6208
6209     xSqr = EventToSquare(x, BOARD_WIDTH);
6210     ySqr = EventToSquare(y, BOARD_HEIGHT);
6211     if (action == Release) UnLoadPV(); // [HGM] pv
6212     if (action != Press) return -2; // return code to be ignored
6213     switch (gameMode) {
6214       case IcsExamining:
6215         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6216       case EditPosition:
6217         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6218         if (xSqr < 0 || ySqr < 0) return -1;\r
6219         whichMenu = 0; // edit-position menu
6220         break;
6221       case IcsObserving:
6222         if(!appData.icsEngineAnalyze) return -1;
6223       case IcsPlayingWhite:
6224       case IcsPlayingBlack:
6225         if(!appData.zippyPlay) goto noZip;
6226       case AnalyzeMode:
6227       case AnalyzeFile:
6228       case MachinePlaysWhite:
6229       case MachinePlaysBlack:
6230       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6231         if (!appData.dropMenu) {
6232           LoadPV(x, y);
6233           return 2; // flag front-end to grab mouse events
6234         }
6235         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6236            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6237       case EditGame:
6238       noZip:
6239         if (xSqr < 0 || ySqr < 0) return -1;
6240         if (!appData.dropMenu || appData.testLegality &&
6241             gameInfo.variant != VariantBughouse &&
6242             gameInfo.variant != VariantCrazyhouse) return -1;
6243         whichMenu = 1; // drop menu
6244         break;
6245       default:
6246         return -1;
6247     }
6248
6249     if (((*fromX = xSqr) < 0) ||
6250         ((*fromY = ySqr) < 0)) {
6251         *fromX = *fromY = -1;
6252         return -1;
6253     }
6254     if (flipView)
6255       *fromX = BOARD_WIDTH - 1 - *fromX;
6256     else
6257       *fromY = BOARD_HEIGHT - 1 - *fromY;
6258
6259     return whichMenu;
6260 }
6261
6262 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6263 {
6264 //    char * hint = lastHint;
6265     FrontEndProgramStats stats;
6266
6267     stats.which = cps == &first ? 0 : 1;
6268     stats.depth = cpstats->depth;
6269     stats.nodes = cpstats->nodes;
6270     stats.score = cpstats->score;
6271     stats.time = cpstats->time;
6272     stats.pv = cpstats->movelist;
6273     stats.hint = lastHint;
6274     stats.an_move_index = 0;
6275     stats.an_move_count = 0;
6276
6277     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6278         stats.hint = cpstats->move_name;
6279         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6280         stats.an_move_count = cpstats->nr_moves;
6281     }
6282
6283     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6284
6285     SetProgramStats( &stats );
6286 }
6287
6288 int
6289 Adjudicate(ChessProgramState *cps)
6290 {       // [HGM] some adjudications useful with buggy engines
6291         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6292         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6293         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6294         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6295         int k, count = 0; static int bare = 1;
6296         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6297         Boolean canAdjudicate = !appData.icsActive;
6298
6299         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6300         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6301             if( appData.testLegality )
6302             {   /* [HGM] Some more adjudications for obstinate engines */
6303                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6304                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6305                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6306                 static int moveCount = 6;
6307                 ChessMove result;
6308                 char *reason = NULL;
6309
6310                 /* Count what is on board. */
6311                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6312                 {   ChessSquare p = boards[forwardMostMove][i][j];
6313                     int m=i;
6314
6315                     switch((int) p)
6316                     {   /* count B,N,R and other of each side */
6317                         case WhiteKing:
6318                         case BlackKing:
6319                              NrK++; break; // [HGM] atomic: count Kings
6320                         case WhiteKnight:
6321                              NrWN++; break;
6322                         case WhiteBishop:
6323                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6324                              bishopsColor |= 1 << ((i^j)&1);
6325                              NrWB++; break;
6326                         case BlackKnight:
6327                              NrBN++; break;
6328                         case BlackBishop:
6329                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6330                              bishopsColor |= 1 << ((i^j)&1);
6331                              NrBB++; break;
6332                         case WhiteRook:
6333                              NrWR++; break;
6334                         case BlackRook:
6335                              NrBR++; break;
6336                         case WhiteQueen:
6337                              NrWQ++; break;
6338                         case BlackQueen:
6339                              NrBQ++; break;
6340                         case EmptySquare: 
6341                              break;
6342                         case BlackPawn:
6343                              m = 7-i;
6344                         case WhitePawn:
6345                              PawnAdvance += m; NrPawns++;
6346                     }
6347                     NrPieces += (p != EmptySquare);
6348                     NrW += ((int)p < (int)BlackPawn);
6349                     if(gameInfo.variant == VariantXiangqi && 
6350                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6351                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6352                         NrW -= ((int)p < (int)BlackPawn);
6353                     }
6354                 }
6355
6356                 /* Some material-based adjudications that have to be made before stalemate test */
6357                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6358                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6359                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6360                      if(canAdjudicate && appData.checkMates) {
6361                          if(engineOpponent)
6362                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6363                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6364                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6365                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6366                          return 1;
6367                      }
6368                 }
6369
6370                 /* Bare King in Shatranj (loses) or Losers (wins) */
6371                 if( NrW == 1 || NrPieces - NrW == 1) {
6372                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6373                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6374                      if(canAdjudicate && appData.checkMates) {
6375                          if(engineOpponent)
6376                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6377                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6378                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6379                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6380                          return 1;
6381                      }
6382                   } else
6383                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6384                   {    /* bare King */
6385                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6386                         if(canAdjudicate && appData.checkMates) {
6387                             /* but only adjudicate if adjudication enabled */
6388                             if(engineOpponent)
6389                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6390                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6391                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6392                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6393                             return 1;
6394                         }
6395                   }
6396                 } else bare = 1;
6397
6398
6399             // don't wait for engine to announce game end if we can judge ourselves
6400             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6401               case MT_CHECK:
6402                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6403                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6404                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6405                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6406                             checkCnt++;
6407                         if(checkCnt >= 2) {
6408                             reason = "Xboard adjudication: 3rd check";
6409                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6410                             break;
6411                         }
6412                     }
6413                 }
6414               case MT_NONE:
6415               default:
6416                 break;
6417               case MT_STALEMATE:
6418               case MT_STAINMATE:
6419                 reason = "Xboard adjudication: Stalemate";
6420                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6421                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6422                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6423                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6424                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6425                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6426                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6427                                                                         EP_CHECKMATE : EP_WINS);
6428                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6429                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6430                 }
6431                 break;
6432               case MT_CHECKMATE:
6433                 reason = "Xboard adjudication: Checkmate";
6434                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6435                 break;
6436             }
6437
6438                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6439                     case EP_STALEMATE:
6440                         result = GameIsDrawn; break;
6441                     case EP_CHECKMATE:
6442                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6443                     case EP_WINS:
6444                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6445                     default:
6446                         result = (ChessMove) 0;
6447                 }
6448                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6449                     if(engineOpponent)
6450                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6451                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6452                     GameEnds( result, reason, GE_XBOARD );
6453                     return 1;
6454                 }
6455
6456                 /* Next absolutely insufficient mating material. */
6457                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6458                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6459                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6460                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6461                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6462
6463                      /* always flag draws, for judging claims */
6464                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6465
6466                      if(canAdjudicate && appData.materialDraws) {
6467                          /* but only adjudicate them if adjudication enabled */
6468                          if(engineOpponent) {
6469                            SendToProgram("force\n", engineOpponent); // suppress reply
6470                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6471                          }
6472                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6473                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6474                          return 1;
6475                      }
6476                 }
6477
6478                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6479                 if(NrPieces == 4 && 
6480                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6481                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6482                    || NrWN==2 || NrBN==2     /* KNNK */
6483                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6484                   ) ) {
6485                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6486                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6487                           if(engineOpponent) {
6488                             SendToProgram("force\n", engineOpponent); // suppress reply
6489                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6490                           }
6491                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6492                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6493                           return 1;
6494                      }
6495                 } else moveCount = 6;
6496             }
6497         }
6498           
6499         if (appData.debugMode) { int i;
6500             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6501                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6502                     appData.drawRepeats);
6503             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6504               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6505             
6506         }
6507
6508         // Repetition draws and 50-move rule can be applied independently of legality testing
6509
6510                 /* Check for rep-draws */
6511                 count = 0;
6512                 for(k = forwardMostMove-2;
6513                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6514                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6515                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6516                     k-=2)
6517                 {   int rights=0;
6518                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6519                         /* compare castling rights */
6520                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6521                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6522                                 rights++; /* King lost rights, while rook still had them */
6523                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6524                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6525                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6526                                    rights++; /* but at least one rook lost them */
6527                         }
6528                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6529                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6530                                 rights++; 
6531                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6532                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6533                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6534                                    rights++;
6535                         }
6536                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6537                             && appData.drawRepeats > 1) {
6538                              /* adjudicate after user-specified nr of repeats */
6539                              if(engineOpponent) {
6540                                SendToProgram("force\n", engineOpponent); // suppress reply
6541                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6542                              }
6543                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6544                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6545                                 // [HGM] xiangqi: check for forbidden perpetuals
6546                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6547                                 for(m=forwardMostMove; m>k; m-=2) {
6548                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6549                                         ourPerpetual = 0; // the current mover did not always check
6550                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6551                                         hisPerpetual = 0; // the opponent did not always check
6552                                 }
6553                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6554                                                                         ourPerpetual, hisPerpetual);
6555                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6556                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6557                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6558                                     return 1;
6559                                 }
6560                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6561                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6562                                 // Now check for perpetual chases
6563                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6564                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6565                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6566                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6567                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6568                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6569                                         return 1;
6570                                     }
6571                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6572                                         break; // Abort repetition-checking loop.
6573                                 }
6574                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6575                              }
6576                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6577                              return 1;
6578                         }
6579                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6580                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6581                     }
6582                 }
6583
6584                 /* Now we test for 50-move draws. Determine ply count */
6585                 count = forwardMostMove;
6586                 /* look for last irreversble move */
6587                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6588                     count--;
6589                 /* if we hit starting position, add initial plies */
6590                 if( count == backwardMostMove )
6591                     count -= initialRulePlies;
6592                 count = forwardMostMove - count; 
6593                 if( count >= 100)
6594                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6595                          /* this is used to judge if draw claims are legal */
6596                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6597                          if(engineOpponent) {
6598                            SendToProgram("force\n", engineOpponent); // suppress reply
6599                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6600                          }
6601                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6602                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6603                          return 1;
6604                 }
6605
6606                 /* if draw offer is pending, treat it as a draw claim
6607                  * when draw condition present, to allow engines a way to
6608                  * claim draws before making their move to avoid a race
6609                  * condition occurring after their move
6610                  */
6611                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6612                          char *p = NULL;
6613                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6614                              p = "Draw claim: 50-move rule";
6615                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6616                              p = "Draw claim: 3-fold repetition";
6617                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6618                              p = "Draw claim: insufficient mating material";
6619                          if( p != NULL && canAdjudicate) {
6620                              if(engineOpponent) {
6621                                SendToProgram("force\n", engineOpponent); // suppress reply
6622                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6623                              }
6624                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6625                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6626                              return 1;
6627                          }
6628                 }
6629
6630                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6631                     if(engineOpponent) {
6632                       SendToProgram("force\n", engineOpponent); // suppress reply
6633                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6634                     }
6635                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6636                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6637                     return 1;
6638                 }
6639         return 0;
6640 }
6641
6642 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6643 {   // [HGM] book: this routine intercepts moves to simulate book replies
6644     char *bookHit = NULL;
6645
6646     //first determine if the incoming move brings opponent into his book
6647     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6648         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6649     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6650     if(bookHit != NULL && !cps->bookSuspend) {
6651         // make sure opponent is not going to reply after receiving move to book position
6652         SendToProgram("force\n", cps);
6653         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6654     }
6655     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6656     // now arrange restart after book miss
6657     if(bookHit) {
6658         // after a book hit we never send 'go', and the code after the call to this routine
6659         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6660         char buf[MSG_SIZ];
6661         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6662         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6663         SendToProgram(buf, cps);
6664         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6665     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6666         SendToProgram("go\n", cps);
6667         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6668     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6669         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6670             SendToProgram("go\n", cps); 
6671         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6672     }
6673     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6674 }
6675
6676 char *savedMessage;
6677 ChessProgramState *savedState;
6678 void DeferredBookMove(void)
6679 {
6680         if(savedState->lastPing != savedState->lastPong)
6681                     ScheduleDelayedEvent(DeferredBookMove, 10);
6682         else
6683         HandleMachineMove(savedMessage, savedState);
6684 }
6685
6686 void
6687 HandleMachineMove(message, cps)
6688      char *message;
6689      ChessProgramState *cps;
6690 {
6691     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6692     char realname[MSG_SIZ];
6693     int fromX, fromY, toX, toY;
6694     ChessMove moveType;
6695     char promoChar;
6696     char *p;
6697     int machineWhite;
6698     char *bookHit;
6699
6700     cps->userError = 0;
6701
6702 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6703     /*
6704      * Kludge to ignore BEL characters
6705      */
6706     while (*message == '\007') message++;
6707
6708     /*
6709      * [HGM] engine debug message: ignore lines starting with '#' character
6710      */
6711     if(cps->debug && *message == '#') return;
6712
6713     /*
6714      * Look for book output
6715      */
6716     if (cps == &first && bookRequested) {
6717         if (message[0] == '\t' || message[0] == ' ') {
6718             /* Part of the book output is here; append it */
6719             strcat(bookOutput, message);
6720             strcat(bookOutput, "  \n");
6721             return;
6722         } else if (bookOutput[0] != NULLCHAR) {
6723             /* All of book output has arrived; display it */
6724             char *p = bookOutput;
6725             while (*p != NULLCHAR) {
6726                 if (*p == '\t') *p = ' ';
6727                 p++;
6728             }
6729             DisplayInformation(bookOutput);
6730             bookRequested = FALSE;
6731             /* Fall through to parse the current output */
6732         }
6733     }
6734
6735     /*
6736      * Look for machine move.
6737      */
6738     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6739         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6740     {
6741         /* This method is only useful on engines that support ping */
6742         if (cps->lastPing != cps->lastPong) {
6743           if (gameMode == BeginningOfGame) {
6744             /* Extra move from before last new; ignore */
6745             if (appData.debugMode) {
6746                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6747             }
6748           } else {
6749             if (appData.debugMode) {
6750                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6751                         cps->which, gameMode);
6752             }
6753
6754             SendToProgram("undo\n", cps);
6755           }
6756           return;
6757         }
6758
6759         switch (gameMode) {
6760           case BeginningOfGame:
6761             /* Extra move from before last reset; ignore */
6762             if (appData.debugMode) {
6763                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6764             }
6765             return;
6766
6767           case EndOfGame:
6768           case IcsIdle:
6769           default:
6770             /* Extra move after we tried to stop.  The mode test is
6771                not a reliable way of detecting this problem, but it's
6772                the best we can do on engines that don't support ping.
6773             */
6774             if (appData.debugMode) {
6775                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6776                         cps->which, gameMode);
6777             }
6778             SendToProgram("undo\n", cps);
6779             return;
6780
6781           case MachinePlaysWhite:
6782           case IcsPlayingWhite:
6783             machineWhite = TRUE;
6784             break;
6785
6786           case MachinePlaysBlack:
6787           case IcsPlayingBlack:
6788             machineWhite = FALSE;
6789             break;
6790
6791           case TwoMachinesPlay:
6792             machineWhite = (cps->twoMachinesColor[0] == 'w');
6793             break;
6794         }
6795         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6796             if (appData.debugMode) {
6797                 fprintf(debugFP,
6798                         "Ignoring move out of turn by %s, gameMode %d"
6799                         ", forwardMost %d\n",
6800                         cps->which, gameMode, forwardMostMove);
6801             }
6802             return;
6803         }
6804
6805     if (appData.debugMode) { int f = forwardMostMove;
6806         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6807                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6808                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6809     }
6810         if(cps->alphaRank) AlphaRank(machineMove, 4);
6811         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6812                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6813             /* Machine move could not be parsed; ignore it. */
6814             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6815                     machineMove, cps->which);
6816             DisplayError(buf1, 0);
6817             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6818                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6819             if (gameMode == TwoMachinesPlay) {
6820               GameEnds(machineWhite ? BlackWins : WhiteWins,
6821                        buf1, GE_XBOARD);
6822             }
6823             return;
6824         }
6825
6826         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6827         /* So we have to redo legality test with true e.p. status here,  */
6828         /* to make sure an illegal e.p. capture does not slip through,   */
6829         /* to cause a forfeit on a justified illegal-move complaint      */
6830         /* of the opponent.                                              */
6831         if( gameMode==TwoMachinesPlay && appData.testLegality
6832             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6833                                                               ) {
6834            ChessMove moveType;
6835            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6836                              fromY, fromX, toY, toX, promoChar);
6837             if (appData.debugMode) {
6838                 int i;
6839                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6840                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6841                 fprintf(debugFP, "castling rights\n");
6842             }
6843             if(moveType == IllegalMove) {
6844                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6845                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6846                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6847                            buf1, GE_XBOARD);
6848                 return;
6849            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6850            /* [HGM] Kludge to handle engines that send FRC-style castling
6851               when they shouldn't (like TSCP-Gothic) */
6852            switch(moveType) {
6853              case WhiteASideCastleFR:
6854              case BlackASideCastleFR:
6855                toX+=2;
6856                currentMoveString[2]++;
6857                break;
6858              case WhiteHSideCastleFR:
6859              case BlackHSideCastleFR:
6860                toX--;
6861                currentMoveString[2]--;
6862                break;
6863              default: ; // nothing to do, but suppresses warning of pedantic compilers
6864            }
6865         }
6866         hintRequested = FALSE;
6867         lastHint[0] = NULLCHAR;
6868         bookRequested = FALSE;
6869         /* Program may be pondering now */
6870         cps->maybeThinking = TRUE;
6871         if (cps->sendTime == 2) cps->sendTime = 1;
6872         if (cps->offeredDraw) cps->offeredDraw--;
6873
6874         /* currentMoveString is set as a side-effect of ParseOneMove */
6875         strcpy(machineMove, currentMoveString);
6876         strcat(machineMove, "\n");
6877         strcpy(moveList[forwardMostMove], machineMove);
6878
6879         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6880
6881         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6882         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6883             int count = 0;
6884
6885             while( count < adjudicateLossPlies ) {
6886                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6887
6888                 if( count & 1 ) {
6889                     score = -score; /* Flip score for winning side */
6890                 }
6891
6892                 if( score > adjudicateLossThreshold ) {
6893                     break;
6894                 }
6895
6896                 count++;
6897             }
6898
6899             if( count >= adjudicateLossPlies ) {
6900                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6901
6902                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6903                     "Xboard adjudication", 
6904                     GE_XBOARD );
6905
6906                 return;
6907             }
6908         }
6909
6910         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6911
6912 #if ZIPPY
6913         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6914             first.initDone) {
6915           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6916                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6917                 SendToICS("draw ");
6918                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6919           }
6920           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6921           ics_user_moved = 1;
6922           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6923                 char buf[3*MSG_SIZ];
6924
6925                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6926                         programStats.score / 100.,
6927                         programStats.depth,
6928                         programStats.time / 100.,
6929                         (unsigned int)programStats.nodes,
6930                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6931                         programStats.movelist);
6932                 SendToICS(buf);
6933 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6934           }
6935         }
6936 #endif
6937
6938         /* [AS] Save move info and clear stats for next move */
6939         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
6940         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
6941         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6942         ClearProgramStats();
6943         thinkOutput[0] = NULLCHAR;
6944         hiddenThinkOutputState = 0;
6945
6946         bookHit = NULL;
6947         if (gameMode == TwoMachinesPlay) {
6948             /* [HGM] relaying draw offers moved to after reception of move */
6949             /* and interpreting offer as claim if it brings draw condition */
6950             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6951                 SendToProgram("draw\n", cps->other);
6952             }
6953             if (cps->other->sendTime) {
6954                 SendTimeRemaining(cps->other,
6955                                   cps->other->twoMachinesColor[0] == 'w');
6956             }
6957             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6958             if (firstMove && !bookHit) {
6959                 firstMove = FALSE;
6960                 if (cps->other->useColors) {
6961                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6962                 }
6963                 SendToProgram("go\n", cps->other);
6964             }
6965             cps->other->maybeThinking = TRUE;
6966         }
6967
6968         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6969         
6970         if (!pausing && appData.ringBellAfterMoves) {
6971             RingBell();
6972         }
6973
6974         /* 
6975          * Reenable menu items that were disabled while
6976          * machine was thinking
6977          */
6978         if (gameMode != TwoMachinesPlay)
6979             SetUserThinkingEnables();
6980
6981         // [HGM] book: after book hit opponent has received move and is now in force mode
6982         // force the book reply into it, and then fake that it outputted this move by jumping
6983         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6984         if(bookHit) {
6985                 static char bookMove[MSG_SIZ]; // a bit generous?
6986
6987                 strcpy(bookMove, "move ");
6988                 strcat(bookMove, bookHit);
6989                 message = bookMove;
6990                 cps = cps->other;
6991                 programStats.nodes = programStats.depth = programStats.time = 
6992                 programStats.score = programStats.got_only_move = 0;
6993                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6994
6995                 if(cps->lastPing != cps->lastPong) {
6996                     savedMessage = message; // args for deferred call
6997                     savedState = cps;
6998                     ScheduleDelayedEvent(DeferredBookMove, 10);
6999                     return;
7000                 }
7001                 goto FakeBookMove;
7002         }
7003
7004         return;
7005     }
7006
7007     /* Set special modes for chess engines.  Later something general
7008      *  could be added here; for now there is just one kludge feature,
7009      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7010      *  when "xboard" is given as an interactive command.
7011      */
7012     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7013         cps->useSigint = FALSE;
7014         cps->useSigterm = FALSE;
7015     }
7016     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7017       ParseFeatures(message+8, cps);
7018       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7019     }
7020
7021     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7022      * want this, I was asked to put it in, and obliged.
7023      */
7024     if (!strncmp(message, "setboard ", 9)) {
7025         Board initial_position;
7026
7027         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7028
7029         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7030             DisplayError(_("Bad FEN received from engine"), 0);
7031             return ;
7032         } else {
7033            Reset(TRUE, FALSE);
7034            CopyBoard(boards[0], initial_position);
7035            initialRulePlies = FENrulePlies;
7036            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7037            else gameMode = MachinePlaysBlack;                 
7038            DrawPosition(FALSE, boards[currentMove]);
7039         }
7040         return;
7041     }
7042
7043     /*
7044      * Look for communication commands
7045      */
7046     if (!strncmp(message, "telluser ", 9)) {
7047         DisplayNote(message + 9);
7048         return;
7049     }
7050     if (!strncmp(message, "tellusererror ", 14)) {
7051         cps->userError = 1;
7052         DisplayError(message + 14, 0);
7053         return;
7054     }
7055     if (!strncmp(message, "tellopponent ", 13)) {
7056       if (appData.icsActive) {
7057         if (loggedOn) {
7058           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7059           SendToICS(buf1);
7060         }
7061       } else {
7062         DisplayNote(message + 13);
7063       }
7064       return;
7065     }
7066     if (!strncmp(message, "tellothers ", 11)) {
7067       if (appData.icsActive) {
7068         if (loggedOn) {
7069           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7070           SendToICS(buf1);
7071         }
7072       }
7073       return;
7074     }
7075     if (!strncmp(message, "tellall ", 8)) {
7076       if (appData.icsActive) {
7077         if (loggedOn) {
7078           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7079           SendToICS(buf1);
7080         }
7081       } else {
7082         DisplayNote(message + 8);
7083       }
7084       return;
7085     }
7086     if (strncmp(message, "warning", 7) == 0) {
7087         /* Undocumented feature, use tellusererror in new code */
7088         DisplayError(message, 0);
7089         return;
7090     }
7091     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7092         strcpy(realname, cps->tidy);
7093         strcat(realname, " query");
7094         AskQuestion(realname, buf2, buf1, cps->pr);
7095         return;
7096     }
7097     /* Commands from the engine directly to ICS.  We don't allow these to be 
7098      *  sent until we are logged on. Crafty kibitzes have been known to 
7099      *  interfere with the login process.
7100      */
7101     if (loggedOn) {
7102         if (!strncmp(message, "tellics ", 8)) {
7103             SendToICS(message + 8);
7104             SendToICS("\n");
7105             return;
7106         }
7107         if (!strncmp(message, "tellicsnoalias ", 15)) {
7108             SendToICS(ics_prefix);
7109             SendToICS(message + 15);
7110             SendToICS("\n");
7111             return;
7112         }
7113         /* The following are for backward compatibility only */
7114         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7115             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7116             SendToICS(ics_prefix);
7117             SendToICS(message);
7118             SendToICS("\n");
7119             return;
7120         }
7121     }
7122     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7123         return;
7124     }
7125     /*
7126      * If the move is illegal, cancel it and redraw the board.
7127      * Also deal with other error cases.  Matching is rather loose
7128      * here to accommodate engines written before the spec.
7129      */
7130     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7131         strncmp(message, "Error", 5) == 0) {
7132         if (StrStr(message, "name") || 
7133             StrStr(message, "rating") || StrStr(message, "?") ||
7134             StrStr(message, "result") || StrStr(message, "board") ||
7135             StrStr(message, "bk") || StrStr(message, "computer") ||
7136             StrStr(message, "variant") || StrStr(message, "hint") ||
7137             StrStr(message, "random") || StrStr(message, "depth") ||
7138             StrStr(message, "accepted")) {
7139             return;
7140         }
7141         if (StrStr(message, "protover")) {
7142           /* Program is responding to input, so it's apparently done
7143              initializing, and this error message indicates it is
7144              protocol version 1.  So we don't need to wait any longer
7145              for it to initialize and send feature commands. */
7146           FeatureDone(cps, 1);
7147           cps->protocolVersion = 1;
7148           return;
7149         }
7150         cps->maybeThinking = FALSE;
7151
7152         if (StrStr(message, "draw")) {
7153             /* Program doesn't have "draw" command */
7154             cps->sendDrawOffers = 0;
7155             return;
7156         }
7157         if (cps->sendTime != 1 &&
7158             (StrStr(message, "time") || StrStr(message, "otim"))) {
7159           /* Program apparently doesn't have "time" or "otim" command */
7160           cps->sendTime = 0;
7161           return;
7162         }
7163         if (StrStr(message, "analyze")) {
7164             cps->analysisSupport = FALSE;
7165             cps->analyzing = FALSE;
7166             Reset(FALSE, TRUE);
7167             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7168             DisplayError(buf2, 0);
7169             return;
7170         }
7171         if (StrStr(message, "(no matching move)st")) {
7172           /* Special kludge for GNU Chess 4 only */
7173           cps->stKludge = TRUE;
7174           SendTimeControl(cps, movesPerSession, timeControl,
7175                           timeIncrement, appData.searchDepth,
7176                           searchTime);
7177           return;
7178         }
7179         if (StrStr(message, "(no matching move)sd")) {
7180           /* Special kludge for GNU Chess 4 only */
7181           cps->sdKludge = TRUE;
7182           SendTimeControl(cps, movesPerSession, timeControl,
7183                           timeIncrement, appData.searchDepth,
7184                           searchTime);
7185           return;
7186         }
7187         if (!StrStr(message, "llegal")) {
7188             return;
7189         }
7190         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7191             gameMode == IcsIdle) return;
7192         if (forwardMostMove <= backwardMostMove) return;
7193         if (pausing) PauseEvent();
7194       if(appData.forceIllegal) {
7195             // [HGM] illegal: machine refused move; force position after move into it
7196           SendToProgram("force\n", cps);
7197           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7198                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7199                 // when black is to move, while there might be nothing on a2 or black
7200                 // might already have the move. So send the board as if white has the move.
7201                 // But first we must change the stm of the engine, as it refused the last move
7202                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7203                 if(WhiteOnMove(forwardMostMove)) {
7204                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7205                     SendBoard(cps, forwardMostMove); // kludgeless board
7206                 } else {
7207                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7208                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7209                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7210                 }
7211           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7212             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7213                  gameMode == TwoMachinesPlay)
7214               SendToProgram("go\n", cps);
7215             return;
7216       } else
7217         if (gameMode == PlayFromGameFile) {
7218             /* Stop reading this game file */
7219             gameMode = EditGame;
7220             ModeHighlight();
7221         }
7222         currentMove = --forwardMostMove;
7223         DisplayMove(currentMove-1); /* before DisplayMoveError */
7224         SwitchClocks();
7225         DisplayBothClocks();
7226         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7227                 parseList[currentMove], cps->which);
7228         DisplayMoveError(buf1);
7229         DrawPosition(FALSE, boards[currentMove]);
7230
7231         /* [HGM] illegal-move claim should forfeit game when Xboard */
7232         /* only passes fully legal moves                            */
7233         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7234             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7235                                 "False illegal-move claim", GE_XBOARD );
7236         }
7237         return;
7238     }
7239     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7240         /* Program has a broken "time" command that
7241            outputs a string not ending in newline.
7242            Don't use it. */
7243         cps->sendTime = 0;
7244     }
7245     
7246     /*
7247      * If chess program startup fails, exit with an error message.
7248      * Attempts to recover here are futile.
7249      */
7250     if ((StrStr(message, "unknown host") != NULL)
7251         || (StrStr(message, "No remote directory") != NULL)
7252         || (StrStr(message, "not found") != NULL)
7253         || (StrStr(message, "No such file") != NULL)
7254         || (StrStr(message, "can't alloc") != NULL)
7255         || (StrStr(message, "Permission denied") != NULL)) {
7256
7257         cps->maybeThinking = FALSE;
7258         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7259                 cps->which, cps->program, cps->host, message);
7260         RemoveInputSource(cps->isr);
7261         DisplayFatalError(buf1, 0, 1);
7262         return;
7263     }
7264     
7265     /* 
7266      * Look for hint output
7267      */
7268     if (sscanf(message, "Hint: %s", buf1) == 1) {
7269         if (cps == &first && hintRequested) {
7270             hintRequested = FALSE;
7271             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7272                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7273                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7274                                     PosFlags(forwardMostMove),
7275                                     fromY, fromX, toY, toX, promoChar, buf1);
7276                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7277                 DisplayInformation(buf2);
7278             } else {
7279                 /* Hint move could not be parsed!? */
7280               snprintf(buf2, sizeof(buf2),
7281                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7282                         buf1, cps->which);
7283                 DisplayError(buf2, 0);
7284             }
7285         } else {
7286             strcpy(lastHint, buf1);
7287         }
7288         return;
7289     }
7290
7291     /*
7292      * Ignore other messages if game is not in progress
7293      */
7294     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7295         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7296
7297     /*
7298      * look for win, lose, draw, or draw offer
7299      */
7300     if (strncmp(message, "1-0", 3) == 0) {
7301         char *p, *q, *r = "";
7302         p = strchr(message, '{');
7303         if (p) {
7304             q = strchr(p, '}');
7305             if (q) {
7306                 *q = NULLCHAR;
7307                 r = p + 1;
7308             }
7309         }
7310         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7311         return;
7312     } else if (strncmp(message, "0-1", 3) == 0) {
7313         char *p, *q, *r = "";
7314         p = strchr(message, '{');
7315         if (p) {
7316             q = strchr(p, '}');
7317             if (q) {
7318                 *q = NULLCHAR;
7319                 r = p + 1;
7320             }
7321         }
7322         /* Kludge for Arasan 4.1 bug */
7323         if (strcmp(r, "Black resigns") == 0) {
7324             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7325             return;
7326         }
7327         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7328         return;
7329     } else if (strncmp(message, "1/2", 3) == 0) {
7330         char *p, *q, *r = "";
7331         p = strchr(message, '{');
7332         if (p) {
7333             q = strchr(p, '}');
7334             if (q) {
7335                 *q = NULLCHAR;
7336                 r = p + 1;
7337             }
7338         }
7339             
7340         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7341         return;
7342
7343     } else if (strncmp(message, "White resign", 12) == 0) {
7344         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7345         return;
7346     } else if (strncmp(message, "Black resign", 12) == 0) {
7347         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7348         return;
7349     } else if (strncmp(message, "White matches", 13) == 0 ||
7350                strncmp(message, "Black matches", 13) == 0   ) {
7351         /* [HGM] ignore GNUShogi noises */
7352         return;
7353     } else if (strncmp(message, "White", 5) == 0 &&
7354                message[5] != '(' &&
7355                StrStr(message, "Black") == NULL) {
7356         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7357         return;
7358     } else if (strncmp(message, "Black", 5) == 0 &&
7359                message[5] != '(') {
7360         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7361         return;
7362     } else if (strcmp(message, "resign") == 0 ||
7363                strcmp(message, "computer resigns") == 0) {
7364         switch (gameMode) {
7365           case MachinePlaysBlack:
7366           case IcsPlayingBlack:
7367             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7368             break;
7369           case MachinePlaysWhite:
7370           case IcsPlayingWhite:
7371             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7372             break;
7373           case TwoMachinesPlay:
7374             if (cps->twoMachinesColor[0] == 'w')
7375               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7376             else
7377               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7378             break;
7379           default:
7380             /* can't happen */
7381             break;
7382         }
7383         return;
7384     } else if (strncmp(message, "opponent mates", 14) == 0) {
7385         switch (gameMode) {
7386           case MachinePlaysBlack:
7387           case IcsPlayingBlack:
7388             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7389             break;
7390           case MachinePlaysWhite:
7391           case IcsPlayingWhite:
7392             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7393             break;
7394           case TwoMachinesPlay:
7395             if (cps->twoMachinesColor[0] == 'w')
7396               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7397             else
7398               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7399             break;
7400           default:
7401             /* can't happen */
7402             break;
7403         }
7404         return;
7405     } else if (strncmp(message, "computer mates", 14) == 0) {
7406         switch (gameMode) {
7407           case MachinePlaysBlack:
7408           case IcsPlayingBlack:
7409             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7410             break;
7411           case MachinePlaysWhite:
7412           case IcsPlayingWhite:
7413             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7414             break;
7415           case TwoMachinesPlay:
7416             if (cps->twoMachinesColor[0] == 'w')
7417               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7418             else
7419               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7420             break;
7421           default:
7422             /* can't happen */
7423             break;
7424         }
7425         return;
7426     } else if (strncmp(message, "checkmate", 9) == 0) {
7427         if (WhiteOnMove(forwardMostMove)) {
7428             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7429         } else {
7430             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7431         }
7432         return;
7433     } else if (strstr(message, "Draw") != NULL ||
7434                strstr(message, "game is a draw") != NULL) {
7435         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7436         return;
7437     } else if (strstr(message, "offer") != NULL &&
7438                strstr(message, "draw") != NULL) {
7439 #if ZIPPY
7440         if (appData.zippyPlay && first.initDone) {
7441             /* Relay offer to ICS */
7442             SendToICS(ics_prefix);
7443             SendToICS("draw\n");
7444         }
7445 #endif
7446         cps->offeredDraw = 2; /* valid until this engine moves twice */
7447         if (gameMode == TwoMachinesPlay) {
7448             if (cps->other->offeredDraw) {
7449                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7450             /* [HGM] in two-machine mode we delay relaying draw offer      */
7451             /* until after we also have move, to see if it is really claim */
7452             }
7453         } else if (gameMode == MachinePlaysWhite ||
7454                    gameMode == MachinePlaysBlack) {
7455           if (userOfferedDraw) {
7456             DisplayInformation(_("Machine accepts your draw offer"));
7457             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7458           } else {
7459             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7460           }
7461         }
7462     }
7463
7464     
7465     /*
7466      * Look for thinking output
7467      */
7468     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7469           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7470                                 ) {
7471         int plylev, mvleft, mvtot, curscore, time;
7472         char mvname[MOVE_LEN];
7473         u64 nodes; // [DM]
7474         char plyext;
7475         int ignore = FALSE;
7476         int prefixHint = FALSE;
7477         mvname[0] = NULLCHAR;
7478
7479         switch (gameMode) {
7480           case MachinePlaysBlack:
7481           case IcsPlayingBlack:
7482             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7483             break;
7484           case MachinePlaysWhite:
7485           case IcsPlayingWhite:
7486             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7487             break;
7488           case AnalyzeMode:
7489           case AnalyzeFile:
7490             break;
7491           case IcsObserving: /* [DM] icsEngineAnalyze */
7492             if (!appData.icsEngineAnalyze) ignore = TRUE;
7493             break;
7494           case TwoMachinesPlay:
7495             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7496                 ignore = TRUE;
7497             }
7498             break;
7499           default:
7500             ignore = TRUE;
7501             break;
7502         }
7503
7504         if (!ignore) {
7505             buf1[0] = NULLCHAR;
7506             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7507                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7508
7509                 if (plyext != ' ' && plyext != '\t') {
7510                     time *= 100;
7511                 }
7512
7513                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7514                 if( cps->scoreIsAbsolute && 
7515                     ( gameMode == MachinePlaysBlack ||
7516                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7517                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7518                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7519                      !WhiteOnMove(currentMove)
7520                     ) )
7521                 {
7522                     curscore = -curscore;
7523                 }
7524
7525
7526                 programStats.depth = plylev;
7527                 programStats.nodes = nodes;
7528                 programStats.time = time;
7529                 programStats.score = curscore;
7530                 programStats.got_only_move = 0;
7531
7532                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7533                         int ticklen;
7534
7535                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7536                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7537                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7538                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7539                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7540                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7541                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7542                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7543                 }
7544
7545                 /* Buffer overflow protection */
7546                 if (buf1[0] != NULLCHAR) {
7547                     if (strlen(buf1) >= sizeof(programStats.movelist)
7548                         && appData.debugMode) {
7549                         fprintf(debugFP,
7550                                 "PV is too long; using the first %u bytes.\n",
7551                                 (unsigned) sizeof(programStats.movelist) - 1);
7552                     }
7553
7554                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7555                 } else {
7556                     sprintf(programStats.movelist, " no PV\n");
7557                 }
7558
7559                 if (programStats.seen_stat) {
7560                     programStats.ok_to_send = 1;
7561                 }
7562
7563                 if (strchr(programStats.movelist, '(') != NULL) {
7564                     programStats.line_is_book = 1;
7565                     programStats.nr_moves = 0;
7566                     programStats.moves_left = 0;
7567                 } else {
7568                     programStats.line_is_book = 0;
7569                 }
7570
7571                 SendProgramStatsToFrontend( cps, &programStats );
7572
7573                 /* 
7574                     [AS] Protect the thinkOutput buffer from overflow... this
7575                     is only useful if buf1 hasn't overflowed first!
7576                 */
7577                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7578                         plylev, 
7579                         (gameMode == TwoMachinesPlay ?
7580                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7581                         ((double) curscore) / 100.0,
7582                         prefixHint ? lastHint : "",
7583                         prefixHint ? " " : "" );
7584
7585                 if( buf1[0] != NULLCHAR ) {
7586                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7587
7588                     if( strlen(buf1) > max_len ) {
7589                         if( appData.debugMode) {
7590                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7591                         }
7592                         buf1[max_len+1] = '\0';
7593                     }
7594
7595                     strcat( thinkOutput, buf1 );
7596                 }
7597
7598                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7599                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7600                     DisplayMove(currentMove - 1);
7601                 }
7602                 return;
7603
7604             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7605                 /* crafty (9.25+) says "(only move) <move>"
7606                  * if there is only 1 legal move
7607                  */
7608                 sscanf(p, "(only move) %s", buf1);
7609                 sprintf(thinkOutput, "%s (only move)", buf1);
7610                 sprintf(programStats.movelist, "%s (only move)", buf1);
7611                 programStats.depth = 1;
7612                 programStats.nr_moves = 1;
7613                 programStats.moves_left = 1;
7614                 programStats.nodes = 1;
7615                 programStats.time = 1;
7616                 programStats.got_only_move = 1;
7617
7618                 /* Not really, but we also use this member to
7619                    mean "line isn't going to change" (Crafty
7620                    isn't searching, so stats won't change) */
7621                 programStats.line_is_book = 1;
7622
7623                 SendProgramStatsToFrontend( cps, &programStats );
7624                 
7625                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7626                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7627                     DisplayMove(currentMove - 1);
7628                 }
7629                 return;
7630             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7631                               &time, &nodes, &plylev, &mvleft,
7632                               &mvtot, mvname) >= 5) {
7633                 /* The stat01: line is from Crafty (9.29+) in response
7634                    to the "." command */
7635                 programStats.seen_stat = 1;
7636                 cps->maybeThinking = TRUE;
7637
7638                 if (programStats.got_only_move || !appData.periodicUpdates)
7639                   return;
7640
7641                 programStats.depth = plylev;
7642                 programStats.time = time;
7643                 programStats.nodes = nodes;
7644                 programStats.moves_left = mvleft;
7645                 programStats.nr_moves = mvtot;
7646                 strcpy(programStats.move_name, mvname);
7647                 programStats.ok_to_send = 1;
7648                 programStats.movelist[0] = '\0';
7649
7650                 SendProgramStatsToFrontend( cps, &programStats );
7651
7652                 return;
7653
7654             } else if (strncmp(message,"++",2) == 0) {
7655                 /* Crafty 9.29+ outputs this */
7656                 programStats.got_fail = 2;
7657                 return;
7658
7659             } else if (strncmp(message,"--",2) == 0) {
7660                 /* Crafty 9.29+ outputs this */
7661                 programStats.got_fail = 1;
7662                 return;
7663
7664             } else if (thinkOutput[0] != NULLCHAR &&
7665                        strncmp(message, "    ", 4) == 0) {
7666                 unsigned message_len;
7667
7668                 p = message;
7669                 while (*p && *p == ' ') p++;
7670
7671                 message_len = strlen( p );
7672
7673                 /* [AS] Avoid buffer overflow */
7674                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7675                     strcat(thinkOutput, " ");
7676                     strcat(thinkOutput, p);
7677                 }
7678
7679                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7680                     strcat(programStats.movelist, " ");
7681                     strcat(programStats.movelist, p);
7682                 }
7683
7684                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7685                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7686                     DisplayMove(currentMove - 1);
7687                 }
7688                 return;
7689             }
7690         }
7691         else {
7692             buf1[0] = NULLCHAR;
7693
7694             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7695                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7696             {
7697                 ChessProgramStats cpstats;
7698
7699                 if (plyext != ' ' && plyext != '\t') {
7700                     time *= 100;
7701                 }
7702
7703                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7704                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7705                     curscore = -curscore;
7706                 }
7707
7708                 cpstats.depth = plylev;
7709                 cpstats.nodes = nodes;
7710                 cpstats.time = time;
7711                 cpstats.score = curscore;
7712                 cpstats.got_only_move = 0;
7713                 cpstats.movelist[0] = '\0';
7714
7715                 if (buf1[0] != NULLCHAR) {
7716                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7717                 }
7718
7719                 cpstats.ok_to_send = 0;
7720                 cpstats.line_is_book = 0;
7721                 cpstats.nr_moves = 0;
7722                 cpstats.moves_left = 0;
7723
7724                 SendProgramStatsToFrontend( cps, &cpstats );
7725             }
7726         }
7727     }
7728 }
7729
7730
7731 /* Parse a game score from the character string "game", and
7732    record it as the history of the current game.  The game
7733    score is NOT assumed to start from the standard position. 
7734    The display is not updated in any way.
7735    */
7736 void
7737 ParseGameHistory(game)
7738      char *game;
7739 {
7740     ChessMove moveType;
7741     int fromX, fromY, toX, toY, boardIndex;
7742     char promoChar;
7743     char *p, *q;
7744     char buf[MSG_SIZ];
7745
7746     if (appData.debugMode)
7747       fprintf(debugFP, "Parsing game history: %s\n", game);
7748
7749     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7750     gameInfo.site = StrSave(appData.icsHost);
7751     gameInfo.date = PGNDate();
7752     gameInfo.round = StrSave("-");
7753
7754     /* Parse out names of players */
7755     while (*game == ' ') game++;
7756     p = buf;
7757     while (*game != ' ') *p++ = *game++;
7758     *p = NULLCHAR;
7759     gameInfo.white = StrSave(buf);
7760     while (*game == ' ') game++;
7761     p = buf;
7762     while (*game != ' ' && *game != '\n') *p++ = *game++;
7763     *p = NULLCHAR;
7764     gameInfo.black = StrSave(buf);
7765
7766     /* Parse moves */
7767     boardIndex = blackPlaysFirst ? 1 : 0;
7768     yynewstr(game);
7769     for (;;) {
7770         yyboardindex = boardIndex;
7771         moveType = (ChessMove) yylex();
7772         switch (moveType) {
7773           case IllegalMove:             /* maybe suicide chess, etc. */
7774   if (appData.debugMode) {
7775     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7776     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7777     setbuf(debugFP, NULL);
7778   }
7779           case WhitePromotionChancellor:
7780           case BlackPromotionChancellor:
7781           case WhitePromotionArchbishop:
7782           case BlackPromotionArchbishop:
7783           case WhitePromotionQueen:
7784           case BlackPromotionQueen:
7785           case WhitePromotionRook:
7786           case BlackPromotionRook:
7787           case WhitePromotionBishop:
7788           case BlackPromotionBishop:
7789           case WhitePromotionKnight:
7790           case BlackPromotionKnight:
7791           case WhitePromotionKing:
7792           case BlackPromotionKing:
7793           case NormalMove:
7794           case WhiteCapturesEnPassant:
7795           case BlackCapturesEnPassant:
7796           case WhiteKingSideCastle:
7797           case WhiteQueenSideCastle:
7798           case BlackKingSideCastle:
7799           case BlackQueenSideCastle:
7800           case WhiteKingSideCastleWild:
7801           case WhiteQueenSideCastleWild:
7802           case BlackKingSideCastleWild:
7803           case BlackQueenSideCastleWild:
7804           /* PUSH Fabien */
7805           case WhiteHSideCastleFR:
7806           case WhiteASideCastleFR:
7807           case BlackHSideCastleFR:
7808           case BlackASideCastleFR:
7809           /* POP Fabien */
7810             fromX = currentMoveString[0] - AAA;
7811             fromY = currentMoveString[1] - ONE;
7812             toX = currentMoveString[2] - AAA;
7813             toY = currentMoveString[3] - ONE;
7814             promoChar = currentMoveString[4];
7815             break;
7816           case WhiteDrop:
7817           case BlackDrop:
7818             fromX = moveType == WhiteDrop ?
7819               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7820             (int) CharToPiece(ToLower(currentMoveString[0]));
7821             fromY = DROP_RANK;
7822             toX = currentMoveString[2] - AAA;
7823             toY = currentMoveString[3] - ONE;
7824             promoChar = NULLCHAR;
7825             break;
7826           case AmbiguousMove:
7827             /* bug? */
7828             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7829   if (appData.debugMode) {
7830     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7831     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7832     setbuf(debugFP, NULL);
7833   }
7834             DisplayError(buf, 0);
7835             return;
7836           case ImpossibleMove:
7837             /* bug? */
7838             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7839   if (appData.debugMode) {
7840     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7841     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7842     setbuf(debugFP, NULL);
7843   }
7844             DisplayError(buf, 0);
7845             return;
7846           case (ChessMove) 0:   /* end of file */
7847             if (boardIndex < backwardMostMove) {
7848                 /* Oops, gap.  How did that happen? */
7849                 DisplayError(_("Gap in move list"), 0);
7850                 return;
7851             }
7852             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7853             if (boardIndex > forwardMostMove) {
7854                 forwardMostMove = boardIndex;
7855             }
7856             return;
7857           case ElapsedTime:
7858             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7859                 strcat(parseList[boardIndex-1], " ");
7860                 strcat(parseList[boardIndex-1], yy_text);
7861             }
7862             continue;
7863           case Comment:
7864           case PGNTag:
7865           case NAG:
7866           default:
7867             /* ignore */
7868             continue;
7869           case WhiteWins:
7870           case BlackWins:
7871           case GameIsDrawn:
7872           case GameUnfinished:
7873             if (gameMode == IcsExamining) {
7874                 if (boardIndex < backwardMostMove) {
7875                     /* Oops, gap.  How did that happen? */
7876                     return;
7877                 }
7878                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7879                 return;
7880             }
7881             gameInfo.result = moveType;
7882             p = strchr(yy_text, '{');
7883             if (p == NULL) p = strchr(yy_text, '(');
7884             if (p == NULL) {
7885                 p = yy_text;
7886                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7887             } else {
7888                 q = strchr(p, *p == '{' ? '}' : ')');
7889                 if (q != NULL) *q = NULLCHAR;
7890                 p++;
7891             }
7892             gameInfo.resultDetails = StrSave(p);
7893             continue;
7894         }
7895         if (boardIndex >= forwardMostMove &&
7896             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7897             backwardMostMove = blackPlaysFirst ? 1 : 0;
7898             return;
7899         }
7900         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7901                                  fromY, fromX, toY, toX, promoChar,
7902                                  parseList[boardIndex]);
7903         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7904         /* currentMoveString is set as a side-effect of yylex */
7905         strcpy(moveList[boardIndex], currentMoveString);
7906         strcat(moveList[boardIndex], "\n");
7907         boardIndex++;
7908         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7909         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7910           case MT_NONE:
7911           case MT_STALEMATE:
7912           default:
7913             break;
7914           case MT_CHECK:
7915             if(gameInfo.variant != VariantShogi)
7916                 strcat(parseList[boardIndex - 1], "+");
7917             break;
7918           case MT_CHECKMATE:
7919           case MT_STAINMATE:
7920             strcat(parseList[boardIndex - 1], "#");
7921             break;
7922         }
7923     }
7924 }
7925
7926
7927 /* Apply a move to the given board  */
7928 void
7929 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7930      int fromX, fromY, toX, toY;
7931      int promoChar;
7932      Board board;
7933 {
7934   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7935   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7936
7937     /* [HGM] compute & store e.p. status and castling rights for new position */
7938     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7939     { int i;
7940
7941       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7942       oldEP = (signed char)board[EP_STATUS];
7943       board[EP_STATUS] = EP_NONE;
7944
7945       if( board[toY][toX] != EmptySquare ) 
7946            board[EP_STATUS] = EP_CAPTURE;  
7947
7948       if( board[fromY][fromX] == WhitePawn ) {
7949            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7950                board[EP_STATUS] = EP_PAWN_MOVE;
7951            if( toY-fromY==2) {
7952                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7953                         gameInfo.variant != VariantBerolina || toX < fromX)
7954                       board[EP_STATUS] = toX | berolina;
7955                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7956                         gameInfo.variant != VariantBerolina || toX > fromX) 
7957                       board[EP_STATUS] = toX;
7958            }
7959       } else 
7960       if( board[fromY][fromX] == BlackPawn ) {
7961            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7962                board[EP_STATUS] = EP_PAWN_MOVE; 
7963            if( toY-fromY== -2) {
7964                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7965                         gameInfo.variant != VariantBerolina || toX < fromX)
7966                       board[EP_STATUS] = toX | berolina;
7967                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7968                         gameInfo.variant != VariantBerolina || toX > fromX) 
7969                       board[EP_STATUS] = toX;
7970            }
7971        }
7972
7973        for(i=0; i<nrCastlingRights; i++) {
7974            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7975               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7976              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7977        }
7978
7979     }
7980
7981   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7982   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7983        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7984          
7985   if (fromX == toX && fromY == toY) return;
7986
7987   if (fromY == DROP_RANK) {
7988         /* must be first */
7989         piece = board[toY][toX] = (ChessSquare) fromX;
7990   } else {
7991      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7992      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7993      if(gameInfo.variant == VariantKnightmate)
7994          king += (int) WhiteUnicorn - (int) WhiteKing;
7995
7996     /* Code added by Tord: */
7997     /* FRC castling assumed when king captures friendly rook. */
7998     if (board[fromY][fromX] == WhiteKing &&
7999              board[toY][toX] == WhiteRook) {
8000       board[fromY][fromX] = EmptySquare;
8001       board[toY][toX] = EmptySquare;
8002       if(toX > fromX) {
8003         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8004       } else {
8005         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8006       }
8007     } else if (board[fromY][fromX] == BlackKing &&
8008                board[toY][toX] == BlackRook) {
8009       board[fromY][fromX] = EmptySquare;
8010       board[toY][toX] = EmptySquare;
8011       if(toX > fromX) {
8012         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8013       } else {
8014         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8015       }
8016     /* End of code added by Tord */
8017
8018     } else if (board[fromY][fromX] == king
8019         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8020         && toY == fromY && toX > fromX+1) {
8021         board[fromY][fromX] = EmptySquare;
8022         board[toY][toX] = king;
8023         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8024         board[fromY][BOARD_RGHT-1] = EmptySquare;
8025     } else if (board[fromY][fromX] == king
8026         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8027                && toY == fromY && toX < fromX-1) {
8028         board[fromY][fromX] = EmptySquare;
8029         board[toY][toX] = king;
8030         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8031         board[fromY][BOARD_LEFT] = EmptySquare;
8032     } else if (board[fromY][fromX] == WhitePawn
8033                && toY >= BOARD_HEIGHT-promoRank
8034                && gameInfo.variant != VariantXiangqi
8035                ) {
8036         /* white pawn promotion */
8037         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8038         if (board[toY][toX] == EmptySquare) {
8039             board[toY][toX] = WhiteQueen;
8040         }
8041         if(gameInfo.variant==VariantBughouse ||
8042            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8043             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8044         board[fromY][fromX] = EmptySquare;
8045     } else if ((fromY == BOARD_HEIGHT-4)
8046                && (toX != fromX)
8047                && gameInfo.variant != VariantXiangqi
8048                && gameInfo.variant != VariantBerolina
8049                && (board[fromY][fromX] == WhitePawn)
8050                && (board[toY][toX] == EmptySquare)) {
8051         board[fromY][fromX] = EmptySquare;
8052         board[toY][toX] = WhitePawn;
8053         captured = board[toY - 1][toX];
8054         board[toY - 1][toX] = EmptySquare;
8055     } else if ((fromY == BOARD_HEIGHT-4)
8056                && (toX == fromX)
8057                && gameInfo.variant == VariantBerolina
8058                && (board[fromY][fromX] == WhitePawn)
8059                && (board[toY][toX] == EmptySquare)) {
8060         board[fromY][fromX] = EmptySquare;
8061         board[toY][toX] = WhitePawn;
8062         if(oldEP & EP_BEROLIN_A) {
8063                 captured = board[fromY][fromX-1];
8064                 board[fromY][fromX-1] = EmptySquare;
8065         }else{  captured = board[fromY][fromX+1];
8066                 board[fromY][fromX+1] = EmptySquare;
8067         }
8068     } else if (board[fromY][fromX] == king
8069         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8070                && toY == fromY && toX > fromX+1) {
8071         board[fromY][fromX] = EmptySquare;
8072         board[toY][toX] = king;
8073         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8074         board[fromY][BOARD_RGHT-1] = EmptySquare;
8075     } else if (board[fromY][fromX] == king
8076         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8077                && toY == fromY && toX < fromX-1) {
8078         board[fromY][fromX] = EmptySquare;
8079         board[toY][toX] = king;
8080         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8081         board[fromY][BOARD_LEFT] = EmptySquare;
8082     } else if (fromY == 7 && fromX == 3
8083                && board[fromY][fromX] == BlackKing
8084                && toY == 7 && toX == 5) {
8085         board[fromY][fromX] = EmptySquare;
8086         board[toY][toX] = BlackKing;
8087         board[fromY][7] = EmptySquare;
8088         board[toY][4] = BlackRook;
8089     } else if (fromY == 7 && fromX == 3
8090                && board[fromY][fromX] == BlackKing
8091                && toY == 7 && toX == 1) {
8092         board[fromY][fromX] = EmptySquare;
8093         board[toY][toX] = BlackKing;
8094         board[fromY][0] = EmptySquare;
8095         board[toY][2] = BlackRook;
8096     } else if (board[fromY][fromX] == BlackPawn
8097                && toY < promoRank
8098                && gameInfo.variant != VariantXiangqi
8099                ) {
8100         /* black pawn promotion */
8101         board[toY][toX] = CharToPiece(ToLower(promoChar));
8102         if (board[toY][toX] == EmptySquare) {
8103             board[toY][toX] = BlackQueen;
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 == 3)
8110                && (toX != fromX)
8111                && gameInfo.variant != VariantXiangqi
8112                && gameInfo.variant != VariantBerolina
8113                && (board[fromY][fromX] == BlackPawn)
8114                && (board[toY][toX] == EmptySquare)) {
8115         board[fromY][fromX] = EmptySquare;
8116         board[toY][toX] = BlackPawn;
8117         captured = board[toY + 1][toX];
8118         board[toY + 1][toX] = EmptySquare;
8119     } else if ((fromY == 3)
8120                && (toX == fromX)
8121                && gameInfo.variant == VariantBerolina
8122                && (board[fromY][fromX] == BlackPawn)
8123                && (board[toY][toX] == EmptySquare)) {
8124         board[fromY][fromX] = EmptySquare;
8125         board[toY][toX] = BlackPawn;
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 {
8133         board[toY][toX] = board[fromY][fromX];
8134         board[fromY][fromX] = EmptySquare;
8135     }
8136
8137     /* [HGM] now we promote for Shogi, if needed */
8138     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8139         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8140   }
8141
8142     if (gameInfo.holdingsWidth != 0) {
8143
8144       /* !!A lot more code needs to be written to support holdings  */
8145       /* [HGM] OK, so I have written it. Holdings are stored in the */
8146       /* penultimate board files, so they are automaticlly stored   */
8147       /* in the game history.                                       */
8148       if (fromY == DROP_RANK) {
8149         /* Delete from holdings, by decreasing count */
8150         /* and erasing image if necessary            */
8151         p = (int) fromX;
8152         if(p < (int) BlackPawn) { /* white drop */
8153              p -= (int)WhitePawn;
8154                  p = PieceToNumber((ChessSquare)p);
8155              if(p >= gameInfo.holdingsSize) p = 0;
8156              if(--board[p][BOARD_WIDTH-2] <= 0)
8157                   board[p][BOARD_WIDTH-1] = EmptySquare;
8158              if((int)board[p][BOARD_WIDTH-2] < 0)
8159                         board[p][BOARD_WIDTH-2] = 0;
8160         } else {                  /* black drop */
8161              p -= (int)BlackPawn;
8162                  p = PieceToNumber((ChessSquare)p);
8163              if(p >= gameInfo.holdingsSize) p = 0;
8164              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8165                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8166              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8167                         board[BOARD_HEIGHT-1-p][1] = 0;
8168         }
8169       }
8170       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8171           && gameInfo.variant != VariantBughouse        ) {
8172         /* [HGM] holdings: Add to holdings, if holdings exist */
8173         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8174                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8175                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8176         }
8177         p = (int) captured;
8178         if (p >= (int) BlackPawn) {
8179           p -= (int)BlackPawn;
8180           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8181                   /* in Shogi restore piece to its original  first */
8182                   captured = (ChessSquare) (DEMOTED captured);
8183                   p = DEMOTED p;
8184           }
8185           p = PieceToNumber((ChessSquare)p);
8186           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8187           board[p][BOARD_WIDTH-2]++;
8188           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8189         } else {
8190           p -= (int)WhitePawn;
8191           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8192                   captured = (ChessSquare) (DEMOTED captured);
8193                   p = DEMOTED p;
8194           }
8195           p = PieceToNumber((ChessSquare)p);
8196           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8197           board[BOARD_HEIGHT-1-p][1]++;
8198           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8199         }
8200       }
8201     } else if (gameInfo.variant == VariantAtomic) {
8202       if (captured != EmptySquare) {
8203         int y, x;
8204         for (y = toY-1; y <= toY+1; y++) {
8205           for (x = toX-1; x <= toX+1; x++) {
8206             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8207                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8208               board[y][x] = EmptySquare;
8209             }
8210           }
8211         }
8212         board[toY][toX] = EmptySquare;
8213       }
8214     }
8215     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8216         /* [HGM] Shogi promotions */
8217         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8218     }
8219
8220     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8221                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8222         // [HGM] superchess: take promotion piece out of holdings
8223         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8224         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8225             if(!--board[k][BOARD_WIDTH-2])
8226                 board[k][BOARD_WIDTH-1] = EmptySquare;
8227         } else {
8228             if(!--board[BOARD_HEIGHT-1-k][1])
8229                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8230         }
8231     }
8232
8233 }
8234
8235 /* Updates forwardMostMove */
8236 void
8237 MakeMove(fromX, fromY, toX, toY, promoChar)
8238      int fromX, fromY, toX, toY;
8239      int promoChar;
8240 {
8241 //    forwardMostMove++; // [HGM] bare: moved downstream
8242
8243     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8244         int timeLeft; static int lastLoadFlag=0; int king, piece;
8245         piece = boards[forwardMostMove][fromY][fromX];
8246         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8247         if(gameInfo.variant == VariantKnightmate)
8248             king += (int) WhiteUnicorn - (int) WhiteKing;
8249         if(forwardMostMove == 0) {
8250             if(blackPlaysFirst) 
8251                 fprintf(serverMoves, "%s;", second.tidy);
8252             fprintf(serverMoves, "%s;", first.tidy);
8253             if(!blackPlaysFirst) 
8254                 fprintf(serverMoves, "%s;", second.tidy);
8255         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8256         lastLoadFlag = loadFlag;
8257         // print base move
8258         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8259         // print castling suffix
8260         if( toY == fromY && piece == king ) {
8261             if(toX-fromX > 1)
8262                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8263             if(fromX-toX >1)
8264                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8265         }
8266         // e.p. suffix
8267         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8268              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8269              boards[forwardMostMove][toY][toX] == EmptySquare
8270              && fromX != toX )
8271                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8272         // promotion suffix
8273         if(promoChar != NULLCHAR)
8274                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8275         if(!loadFlag) {
8276             fprintf(serverMoves, "/%d/%d",
8277                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8278             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8279             else                      timeLeft = blackTimeRemaining/1000;
8280             fprintf(serverMoves, "/%d", timeLeft);
8281         }
8282         fflush(serverMoves);
8283     }
8284
8285     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8286       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8287                         0, 1);
8288       return;
8289     }
8290     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8291     if (commentList[forwardMostMove+1] != NULL) {
8292         free(commentList[forwardMostMove+1]);
8293         commentList[forwardMostMove+1] = NULL;
8294     }
8295     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8296     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8297     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8298     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8299     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8300     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8301     gameInfo.result = GameUnfinished;
8302     if (gameInfo.resultDetails != NULL) {
8303         free(gameInfo.resultDetails);
8304         gameInfo.resultDetails = NULL;
8305     }
8306     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8307                               moveList[forwardMostMove - 1]);
8308     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8309                              PosFlags(forwardMostMove - 1),
8310                              fromY, fromX, toY, toX, promoChar,
8311                              parseList[forwardMostMove - 1]);
8312     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8313       case MT_NONE:
8314       case MT_STALEMATE:
8315       default:
8316         break;
8317       case MT_CHECK:
8318         if(gameInfo.variant != VariantShogi)
8319             strcat(parseList[forwardMostMove - 1], "+");
8320         break;
8321       case MT_CHECKMATE:
8322       case MT_STAINMATE:
8323         strcat(parseList[forwardMostMove - 1], "#");
8324         break;
8325     }
8326     if (appData.debugMode) {
8327         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8328     }
8329
8330 }
8331
8332 /* Updates currentMove if not pausing */
8333 void
8334 ShowMove(fromX, fromY, toX, toY)
8335 {
8336     int instant = (gameMode == PlayFromGameFile) ?
8337         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8338     if(appData.noGUI) return;
8339     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8340         if (!instant) {
8341             if (forwardMostMove == currentMove + 1) {
8342                 AnimateMove(boards[forwardMostMove - 1],
8343                             fromX, fromY, toX, toY);
8344             }
8345             if (appData.highlightLastMove) {
8346                 SetHighlights(fromX, fromY, toX, toY);
8347             }
8348         }
8349         currentMove = forwardMostMove;
8350     }
8351
8352     if (instant) return;
8353
8354     DisplayMove(currentMove - 1);
8355     DrawPosition(FALSE, boards[currentMove]);
8356     DisplayBothClocks();
8357     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8358 }
8359
8360 void SendEgtPath(ChessProgramState *cps)
8361 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8362         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8363
8364         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8365
8366         while(*p) {
8367             char c, *q = name+1, *r, *s;
8368
8369             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8370             while(*p && *p != ',') *q++ = *p++;
8371             *q++ = ':'; *q = 0;
8372             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8373                 strcmp(name, ",nalimov:") == 0 ) {
8374                 // take nalimov path from the menu-changeable option first, if it is defined
8375                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8376                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8377             } else
8378             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8379                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8380                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8381                 s = r = StrStr(s, ":") + 1; // beginning of path info
8382                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8383                 c = *r; *r = 0;             // temporarily null-terminate path info
8384                     *--q = 0;               // strip of trailig ':' from name
8385                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8386                 *r = c;
8387                 SendToProgram(buf,cps);     // send egtbpath command for this format
8388             }
8389             if(*p == ',') p++; // read away comma to position for next format name
8390         }
8391 }
8392
8393 void
8394 InitChessProgram(cps, setup)
8395      ChessProgramState *cps;
8396      int setup; /* [HGM] needed to setup FRC opening position */
8397 {
8398     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8399     if (appData.noChessProgram) return;
8400     hintRequested = FALSE;
8401     bookRequested = FALSE;
8402
8403     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8404     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8405     if(cps->memSize) { /* [HGM] memory */
8406         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8407         SendToProgram(buf, cps);
8408     }
8409     SendEgtPath(cps); /* [HGM] EGT */
8410     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8411         sprintf(buf, "cores %d\n", appData.smpCores);
8412         SendToProgram(buf, cps);
8413     }
8414
8415     SendToProgram(cps->initString, cps);
8416     if (gameInfo.variant != VariantNormal &&
8417         gameInfo.variant != VariantLoadable
8418         /* [HGM] also send variant if board size non-standard */
8419         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8420                                             ) {
8421       char *v = VariantName(gameInfo.variant);
8422       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8423         /* [HGM] in protocol 1 we have to assume all variants valid */
8424         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8425         DisplayFatalError(buf, 0, 1);
8426         return;
8427       }
8428
8429       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8430       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8431       if( gameInfo.variant == VariantXiangqi )
8432            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8433       if( gameInfo.variant == VariantShogi )
8434            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8435       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8436            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8437       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8438                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8439            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8440       if( gameInfo.variant == VariantCourier )
8441            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8442       if( gameInfo.variant == VariantSuper )
8443            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8444       if( gameInfo.variant == VariantGreat )
8445            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8446
8447       if(overruled) {
8448            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8449                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8450            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8451            if(StrStr(cps->variants, b) == NULL) { 
8452                // specific sized variant not known, check if general sizing allowed
8453                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8454                    if(StrStr(cps->variants, "boardsize") == NULL) {
8455                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8456                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8457                        DisplayFatalError(buf, 0, 1);
8458                        return;
8459                    }
8460                    /* [HGM] here we really should compare with the maximum supported board size */
8461                }
8462            }
8463       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8464       sprintf(buf, "variant %s\n", b);
8465       SendToProgram(buf, cps);
8466     }
8467     currentlyInitializedVariant = gameInfo.variant;
8468
8469     /* [HGM] send opening position in FRC to first engine */
8470     if(setup) {
8471           SendToProgram("force\n", cps);
8472           SendBoard(cps, 0);
8473           /* engine is now in force mode! Set flag to wake it up after first move. */
8474           setboardSpoiledMachineBlack = 1;
8475     }
8476
8477     if (cps->sendICS) {
8478       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8479       SendToProgram(buf, cps);
8480     }
8481     cps->maybeThinking = FALSE;
8482     cps->offeredDraw = 0;
8483     if (!appData.icsActive) {
8484         SendTimeControl(cps, movesPerSession, timeControl,
8485                         timeIncrement, appData.searchDepth,
8486                         searchTime);
8487     }
8488     if (appData.showThinking 
8489         // [HGM] thinking: four options require thinking output to be sent
8490         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8491                                 ) {
8492         SendToProgram("post\n", cps);
8493     }
8494     SendToProgram("hard\n", cps);
8495     if (!appData.ponderNextMove) {
8496         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8497            it without being sure what state we are in first.  "hard"
8498            is not a toggle, so that one is OK.
8499          */
8500         SendToProgram("easy\n", cps);
8501     }
8502     if (cps->usePing) {
8503       sprintf(buf, "ping %d\n", ++cps->lastPing);
8504       SendToProgram(buf, cps);
8505     }
8506     cps->initDone = TRUE;
8507 }   
8508
8509
8510 void
8511 StartChessProgram(cps)
8512      ChessProgramState *cps;
8513 {
8514     char buf[MSG_SIZ];
8515     int err;
8516
8517     if (appData.noChessProgram) return;
8518     cps->initDone = FALSE;
8519
8520     if (strcmp(cps->host, "localhost") == 0) {
8521         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8522     } else if (*appData.remoteShell == NULLCHAR) {
8523         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8524     } else {
8525         if (*appData.remoteUser == NULLCHAR) {
8526           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8527                     cps->program);
8528         } else {
8529           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8530                     cps->host, appData.remoteUser, cps->program);
8531         }
8532         err = StartChildProcess(buf, "", &cps->pr);
8533     }
8534     
8535     if (err != 0) {
8536         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8537         DisplayFatalError(buf, err, 1);
8538         cps->pr = NoProc;
8539         cps->isr = NULL;
8540         return;
8541     }
8542     
8543     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8544     if (cps->protocolVersion > 1) {
8545       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8546       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8547       cps->comboCnt = 0;  //                and values of combo boxes
8548       SendToProgram(buf, cps);
8549     } else {
8550       SendToProgram("xboard\n", cps);
8551     }
8552 }
8553
8554
8555 void
8556 TwoMachinesEventIfReady P((void))
8557 {
8558   if (first.lastPing != first.lastPong) {
8559     DisplayMessage("", _("Waiting for first chess program"));
8560     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8561     return;
8562   }
8563   if (second.lastPing != second.lastPong) {
8564     DisplayMessage("", _("Waiting for second chess program"));
8565     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8566     return;
8567   }
8568   ThawUI();
8569   TwoMachinesEvent();
8570 }
8571
8572 void
8573 NextMatchGame P((void))
8574 {
8575     int index; /* [HGM] autoinc: step load index during match */
8576     Reset(FALSE, TRUE);
8577     if (*appData.loadGameFile != NULLCHAR) {
8578         index = appData.loadGameIndex;
8579         if(index < 0) { // [HGM] autoinc
8580             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8581             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8582         } 
8583         LoadGameFromFile(appData.loadGameFile,
8584                          index,
8585                          appData.loadGameFile, FALSE);
8586     } else if (*appData.loadPositionFile != NULLCHAR) {
8587         index = appData.loadPositionIndex;
8588         if(index < 0) { // [HGM] autoinc
8589             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8590             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8591         } 
8592         LoadPositionFromFile(appData.loadPositionFile,
8593                              index,
8594                              appData.loadPositionFile);
8595     }
8596     TwoMachinesEventIfReady();
8597 }
8598
8599 void UserAdjudicationEvent( int result )
8600 {
8601     ChessMove gameResult = GameIsDrawn;
8602
8603     if( result > 0 ) {
8604         gameResult = WhiteWins;
8605     }
8606     else if( result < 0 ) {
8607         gameResult = BlackWins;
8608     }
8609
8610     if( gameMode == TwoMachinesPlay ) {
8611         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8612     }
8613 }
8614
8615
8616 // [HGM] save: calculate checksum of game to make games easily identifiable
8617 int StringCheckSum(char *s)
8618 {
8619         int i = 0;
8620         if(s==NULL) return 0;
8621         while(*s) i = i*259 + *s++;
8622         return i;
8623 }
8624
8625 int GameCheckSum()
8626 {
8627         int i, sum=0;
8628         for(i=backwardMostMove; i<forwardMostMove; i++) {
8629                 sum += pvInfoList[i].depth;
8630                 sum += StringCheckSum(parseList[i]);
8631                 sum += StringCheckSum(commentList[i]);
8632                 sum *= 261;
8633         }
8634         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8635         return sum + StringCheckSum(commentList[i]);
8636 } // end of save patch
8637
8638 void
8639 GameEnds(result, resultDetails, whosays)
8640      ChessMove result;
8641      char *resultDetails;
8642      int whosays;
8643 {
8644     GameMode nextGameMode;
8645     int isIcsGame;
8646     char buf[MSG_SIZ];
8647
8648     if(endingGame) return; /* [HGM] crash: forbid recursion */
8649     endingGame = 1;
8650
8651     if (appData.debugMode) {
8652       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8653               result, resultDetails ? resultDetails : "(null)", whosays);
8654     }
8655
8656     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8657         /* If we are playing on ICS, the server decides when the
8658            game is over, but the engine can offer to draw, claim 
8659            a draw, or resign. 
8660          */
8661 #if ZIPPY
8662         if (appData.zippyPlay && first.initDone) {
8663             if (result == GameIsDrawn) {
8664                 /* In case draw still needs to be claimed */
8665                 SendToICS(ics_prefix);
8666                 SendToICS("draw\n");
8667             } else if (StrCaseStr(resultDetails, "resign")) {
8668                 SendToICS(ics_prefix);
8669                 SendToICS("resign\n");
8670             }
8671         }
8672 #endif
8673         endingGame = 0; /* [HGM] crash */
8674         return;
8675     }
8676
8677     /* If we're loading the game from a file, stop */
8678     if (whosays == GE_FILE) {
8679       (void) StopLoadGameTimer();
8680       gameFileFP = NULL;
8681     }
8682
8683     /* Cancel draw offers */
8684     first.offeredDraw = second.offeredDraw = 0;
8685
8686     /* If this is an ICS game, only ICS can really say it's done;
8687        if not, anyone can. */
8688     isIcsGame = (gameMode == IcsPlayingWhite || 
8689                  gameMode == IcsPlayingBlack || 
8690                  gameMode == IcsObserving    || 
8691                  gameMode == IcsExamining);
8692
8693     if (!isIcsGame || whosays == GE_ICS) {
8694         /* OK -- not an ICS game, or ICS said it was done */
8695         StopClocks();
8696         if (!isIcsGame && !appData.noChessProgram) 
8697           SetUserThinkingEnables();
8698     
8699         /* [HGM] if a machine claims the game end we verify this claim */
8700         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8701             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8702                 char claimer;
8703                 ChessMove trueResult = (ChessMove) -1;
8704
8705                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8706                                             first.twoMachinesColor[0] :
8707                                             second.twoMachinesColor[0] ;
8708
8709                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8710                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8711                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8712                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8713                 } else
8714                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8715                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8716                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8717                 } else
8718                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8719                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8720                 }
8721
8722                 // now verify win claims, but not in drop games, as we don't understand those yet
8723                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8724                                                  || gameInfo.variant == VariantGreat) &&
8725                     (result == WhiteWins && claimer == 'w' ||
8726                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8727                       if (appData.debugMode) {
8728                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8729                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8730                       }
8731                       if(result != trueResult) {
8732                               sprintf(buf, "False win claim: '%s'", resultDetails);
8733                               result = claimer == 'w' ? BlackWins : WhiteWins;
8734                               resultDetails = buf;
8735                       }
8736                 } else
8737                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8738                     && (forwardMostMove <= backwardMostMove ||
8739                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8740                         (claimer=='b')==(forwardMostMove&1))
8741                                                                                   ) {
8742                       /* [HGM] verify: draws that were not flagged are false claims */
8743                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8744                       result = claimer == 'w' ? BlackWins : WhiteWins;
8745                       resultDetails = buf;
8746                 }
8747                 /* (Claiming a loss is accepted no questions asked!) */
8748             }
8749             /* [HGM] bare: don't allow bare King to win */
8750             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8751                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8752                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8753                && result != GameIsDrawn)
8754             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8755                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8756                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8757                         if(p >= 0 && p <= (int)WhiteKing) k++;
8758                 }
8759                 if (appData.debugMode) {
8760                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8761                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8762                 }
8763                 if(k <= 1) {
8764                         result = GameIsDrawn;
8765                         sprintf(buf, "%s but bare king", resultDetails);
8766                         resultDetails = buf;
8767                 }
8768             }
8769         }
8770
8771
8772         if(serverMoves != NULL && !loadFlag) { char c = '=';
8773             if(result==WhiteWins) c = '+';
8774             if(result==BlackWins) c = '-';
8775             if(resultDetails != NULL)
8776                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8777         }
8778         if (resultDetails != NULL) {
8779             gameInfo.result = result;
8780             gameInfo.resultDetails = StrSave(resultDetails);
8781
8782             /* display last move only if game was not loaded from file */
8783             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8784                 DisplayMove(currentMove - 1);
8785     
8786             if (forwardMostMove != 0) {
8787                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8788                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8789                                                                 ) {
8790                     if (*appData.saveGameFile != NULLCHAR) {
8791                         SaveGameToFile(appData.saveGameFile, TRUE);
8792                     } else if (appData.autoSaveGames) {
8793                         AutoSaveGame();
8794                     }
8795                     if (*appData.savePositionFile != NULLCHAR) {
8796                         SavePositionToFile(appData.savePositionFile);
8797                     }
8798                 }
8799             }
8800
8801             /* Tell program how game ended in case it is learning */
8802             /* [HGM] Moved this to after saving the PGN, just in case */
8803             /* engine died and we got here through time loss. In that */
8804             /* case we will get a fatal error writing the pipe, which */
8805             /* would otherwise lose us the PGN.                       */
8806             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8807             /* output during GameEnds should never be fatal anymore   */
8808             if (gameMode == MachinePlaysWhite ||
8809                 gameMode == MachinePlaysBlack ||
8810                 gameMode == TwoMachinesPlay ||
8811                 gameMode == IcsPlayingWhite ||
8812                 gameMode == IcsPlayingBlack ||
8813                 gameMode == BeginningOfGame) {
8814                 char buf[MSG_SIZ];
8815                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8816                         resultDetails);
8817                 if (first.pr != NoProc) {
8818                     SendToProgram(buf, &first);
8819                 }
8820                 if (second.pr != NoProc &&
8821                     gameMode == TwoMachinesPlay) {
8822                     SendToProgram(buf, &second);
8823                 }
8824             }
8825         }
8826
8827         if (appData.icsActive) {
8828             if (appData.quietPlay &&
8829                 (gameMode == IcsPlayingWhite ||
8830                  gameMode == IcsPlayingBlack)) {
8831                 SendToICS(ics_prefix);
8832                 SendToICS("set shout 1\n");
8833             }
8834             nextGameMode = IcsIdle;
8835             ics_user_moved = FALSE;
8836             /* clean up premove.  It's ugly when the game has ended and the
8837              * premove highlights are still on the board.
8838              */
8839             if (gotPremove) {
8840               gotPremove = FALSE;
8841               ClearPremoveHighlights();
8842               DrawPosition(FALSE, boards[currentMove]);
8843             }
8844             if (whosays == GE_ICS) {
8845                 switch (result) {
8846                 case WhiteWins:
8847                     if (gameMode == IcsPlayingWhite)
8848                         PlayIcsWinSound();
8849                     else if(gameMode == IcsPlayingBlack)
8850                         PlayIcsLossSound();
8851                     break;
8852                 case BlackWins:
8853                     if (gameMode == IcsPlayingBlack)
8854                         PlayIcsWinSound();
8855                     else if(gameMode == IcsPlayingWhite)
8856                         PlayIcsLossSound();
8857                     break;
8858                 case GameIsDrawn:
8859                     PlayIcsDrawSound();
8860                     break;
8861                 default:
8862                     PlayIcsUnfinishedSound();
8863                 }
8864             }
8865         } else if (gameMode == EditGame ||
8866                    gameMode == PlayFromGameFile || 
8867                    gameMode == AnalyzeMode || 
8868                    gameMode == AnalyzeFile) {
8869             nextGameMode = gameMode;
8870         } else {
8871             nextGameMode = EndOfGame;
8872         }
8873         pausing = FALSE;
8874         ModeHighlight();
8875     } else {
8876         nextGameMode = gameMode;
8877     }
8878
8879     if (appData.noChessProgram) {
8880         gameMode = nextGameMode;
8881         ModeHighlight();
8882         endingGame = 0; /* [HGM] crash */
8883         return;
8884     }
8885
8886     if (first.reuse) {
8887         /* Put first chess program into idle state */
8888         if (first.pr != NoProc &&
8889             (gameMode == MachinePlaysWhite ||
8890              gameMode == MachinePlaysBlack ||
8891              gameMode == TwoMachinesPlay ||
8892              gameMode == IcsPlayingWhite ||
8893              gameMode == IcsPlayingBlack ||
8894              gameMode == BeginningOfGame)) {
8895             SendToProgram("force\n", &first);
8896             if (first.usePing) {
8897               char buf[MSG_SIZ];
8898               sprintf(buf, "ping %d\n", ++first.lastPing);
8899               SendToProgram(buf, &first);
8900             }
8901         }
8902     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8903         /* Kill off first chess program */
8904         if (first.isr != NULL)
8905           RemoveInputSource(first.isr);
8906         first.isr = NULL;
8907     
8908         if (first.pr != NoProc) {
8909             ExitAnalyzeMode();
8910             DoSleep( appData.delayBeforeQuit );
8911             SendToProgram("quit\n", &first);
8912             DoSleep( appData.delayAfterQuit );
8913             DestroyChildProcess(first.pr, first.useSigterm);
8914         }
8915         first.pr = NoProc;
8916     }
8917     if (second.reuse) {
8918         /* Put second chess program into idle state */
8919         if (second.pr != NoProc &&
8920             gameMode == TwoMachinesPlay) {
8921             SendToProgram("force\n", &second);
8922             if (second.usePing) {
8923               char buf[MSG_SIZ];
8924               sprintf(buf, "ping %d\n", ++second.lastPing);
8925               SendToProgram(buf, &second);
8926             }
8927         }
8928     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8929         /* Kill off second chess program */
8930         if (second.isr != NULL)
8931           RemoveInputSource(second.isr);
8932         second.isr = NULL;
8933     
8934         if (second.pr != NoProc) {
8935             DoSleep( appData.delayBeforeQuit );
8936             SendToProgram("quit\n", &second);
8937             DoSleep( appData.delayAfterQuit );
8938             DestroyChildProcess(second.pr, second.useSigterm);
8939         }
8940         second.pr = NoProc;
8941     }
8942
8943     if (matchMode && gameMode == TwoMachinesPlay) {
8944         switch (result) {
8945         case WhiteWins:
8946           if (first.twoMachinesColor[0] == 'w') {
8947             first.matchWins++;
8948           } else {
8949             second.matchWins++;
8950           }
8951           break;
8952         case BlackWins:
8953           if (first.twoMachinesColor[0] == 'b') {
8954             first.matchWins++;
8955           } else {
8956             second.matchWins++;
8957           }
8958           break;
8959         default:
8960           break;
8961         }
8962         if (matchGame < appData.matchGames) {
8963             char *tmp;
8964             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8965                 tmp = first.twoMachinesColor;
8966                 first.twoMachinesColor = second.twoMachinesColor;
8967                 second.twoMachinesColor = tmp;
8968             }
8969             gameMode = nextGameMode;
8970             matchGame++;
8971             if(appData.matchPause>10000 || appData.matchPause<10)
8972                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8973             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8974             endingGame = 0; /* [HGM] crash */
8975             return;
8976         } else {
8977             char buf[MSG_SIZ];
8978             gameMode = nextGameMode;
8979             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8980                     first.tidy, second.tidy,
8981                     first.matchWins, second.matchWins,
8982                     appData.matchGames - (first.matchWins + second.matchWins));
8983             DisplayFatalError(buf, 0, 0);
8984         }
8985     }
8986     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8987         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8988       ExitAnalyzeMode();
8989     gameMode = nextGameMode;
8990     ModeHighlight();
8991     endingGame = 0;  /* [HGM] crash */
8992 }
8993
8994 /* Assumes program was just initialized (initString sent).
8995    Leaves program in force mode. */
8996 void
8997 FeedMovesToProgram(cps, upto) 
8998      ChessProgramState *cps;
8999      int upto;
9000 {
9001     int i;
9002     
9003     if (appData.debugMode)
9004       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9005               startedFromSetupPosition ? "position and " : "",
9006               backwardMostMove, upto, cps->which);
9007     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9008         // [HGM] variantswitch: make engine aware of new variant
9009         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9010                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9011         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9012         SendToProgram(buf, cps);
9013         currentlyInitializedVariant = gameInfo.variant;
9014     }
9015     SendToProgram("force\n", cps);
9016     if (startedFromSetupPosition) {
9017         SendBoard(cps, backwardMostMove);
9018     if (appData.debugMode) {
9019         fprintf(debugFP, "feedMoves\n");
9020     }
9021     }
9022     for (i = backwardMostMove; i < upto; i++) {
9023         SendMoveToProgram(i, cps);
9024     }
9025 }
9026
9027
9028 void
9029 ResurrectChessProgram()
9030 {
9031      /* The chess program may have exited.
9032         If so, restart it and feed it all the moves made so far. */
9033
9034     if (appData.noChessProgram || first.pr != NoProc) return;
9035     
9036     StartChessProgram(&first);
9037     InitChessProgram(&first, FALSE);
9038     FeedMovesToProgram(&first, currentMove);
9039
9040     if (!first.sendTime) {
9041         /* can't tell gnuchess what its clock should read,
9042            so we bow to its notion. */
9043         ResetClocks();
9044         timeRemaining[0][currentMove] = whiteTimeRemaining;
9045         timeRemaining[1][currentMove] = blackTimeRemaining;
9046     }
9047
9048     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9049                 appData.icsEngineAnalyze) && first.analysisSupport) {
9050       SendToProgram("analyze\n", &first);
9051       first.analyzing = TRUE;
9052     }
9053 }
9054
9055 /*
9056  * Button procedures
9057  */
9058 void
9059 Reset(redraw, init)
9060      int redraw, init;
9061 {
9062     int i;
9063
9064     if (appData.debugMode) {
9065         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9066                 redraw, init, gameMode);
9067     }
9068     CleanupTail(); // [HGM] vari: delete any stored variations
9069     pausing = pauseExamInvalid = FALSE;
9070     startedFromSetupPosition = blackPlaysFirst = FALSE;
9071     firstMove = TRUE;
9072     whiteFlag = blackFlag = FALSE;
9073     userOfferedDraw = FALSE;
9074     hintRequested = bookRequested = FALSE;
9075     first.maybeThinking = FALSE;
9076     second.maybeThinking = FALSE;
9077     first.bookSuspend = FALSE; // [HGM] book
9078     second.bookSuspend = FALSE;
9079     thinkOutput[0] = NULLCHAR;
9080     lastHint[0] = NULLCHAR;
9081     ClearGameInfo(&gameInfo);
9082     gameInfo.variant = StringToVariant(appData.variant);
9083     ics_user_moved = ics_clock_paused = FALSE;
9084     ics_getting_history = H_FALSE;
9085     ics_gamenum = -1;
9086     white_holding[0] = black_holding[0] = NULLCHAR;
9087     ClearProgramStats();
9088     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9089     
9090     ResetFrontEnd();
9091     ClearHighlights();
9092     flipView = appData.flipView;
9093     ClearPremoveHighlights();
9094     gotPremove = FALSE;
9095     alarmSounded = FALSE;
9096
9097     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9098     if(appData.serverMovesName != NULL) {
9099         /* [HGM] prepare to make moves file for broadcasting */
9100         clock_t t = clock();
9101         if(serverMoves != NULL) fclose(serverMoves);
9102         serverMoves = fopen(appData.serverMovesName, "r");
9103         if(serverMoves != NULL) {
9104             fclose(serverMoves);
9105             /* delay 15 sec before overwriting, so all clients can see end */
9106             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9107         }
9108         serverMoves = fopen(appData.serverMovesName, "w");
9109     }
9110
9111     ExitAnalyzeMode();
9112     gameMode = BeginningOfGame;
9113     ModeHighlight();
9114     if(appData.icsActive) gameInfo.variant = VariantNormal;
9115     currentMove = forwardMostMove = backwardMostMove = 0;
9116     InitPosition(redraw);
9117     for (i = 0; i < MAX_MOVES; i++) {
9118         if (commentList[i] != NULL) {
9119             free(commentList[i]);
9120             commentList[i] = NULL;
9121         }
9122     }
9123     ResetClocks();
9124     timeRemaining[0][0] = whiteTimeRemaining;
9125     timeRemaining[1][0] = blackTimeRemaining;
9126     if (first.pr == NULL) {
9127         StartChessProgram(&first);
9128     }
9129     if (init) {
9130             InitChessProgram(&first, startedFromSetupPosition);
9131     }
9132     DisplayTitle("");
9133     DisplayMessage("", "");
9134     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9135     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9136 }
9137
9138 void
9139 AutoPlayGameLoop()
9140 {
9141     for (;;) {
9142         if (!AutoPlayOneMove())
9143           return;
9144         if (matchMode || appData.timeDelay == 0)
9145           continue;
9146         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9147           return;
9148         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9149         break;
9150     }
9151 }
9152
9153
9154 int
9155 AutoPlayOneMove()
9156 {
9157     int fromX, fromY, toX, toY;
9158
9159     if (appData.debugMode) {
9160       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9161     }
9162
9163     if (gameMode != PlayFromGameFile)
9164       return FALSE;
9165
9166     if (currentMove >= forwardMostMove) {
9167       gameMode = EditGame;
9168       ModeHighlight();
9169
9170       /* [AS] Clear current move marker at the end of a game */
9171       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9172
9173       return FALSE;
9174     }
9175     
9176     toX = moveList[currentMove][2] - AAA;
9177     toY = moveList[currentMove][3] - ONE;
9178
9179     if (moveList[currentMove][1] == '@') {
9180         if (appData.highlightLastMove) {
9181             SetHighlights(-1, -1, toX, toY);
9182         }
9183     } else {
9184         fromX = moveList[currentMove][0] - AAA;
9185         fromY = moveList[currentMove][1] - ONE;
9186
9187         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9188
9189         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9190
9191         if (appData.highlightLastMove) {
9192             SetHighlights(fromX, fromY, toX, toY);
9193         }
9194     }
9195     DisplayMove(currentMove);
9196     SendMoveToProgram(currentMove++, &first);
9197     DisplayBothClocks();
9198     DrawPosition(FALSE, boards[currentMove]);
9199     // [HGM] PV info: always display, routine tests if empty
9200     DisplayComment(currentMove - 1, commentList[currentMove]);
9201     return TRUE;
9202 }
9203
9204
9205 int
9206 LoadGameOneMove(readAhead)
9207      ChessMove readAhead;
9208 {
9209     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9210     char promoChar = NULLCHAR;
9211     ChessMove moveType;
9212     char move[MSG_SIZ];
9213     char *p, *q;
9214     
9215     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9216         gameMode != AnalyzeMode && gameMode != Training) {
9217         gameFileFP = NULL;
9218         return FALSE;
9219     }
9220     
9221     yyboardindex = forwardMostMove;
9222     if (readAhead != (ChessMove)0) {
9223       moveType = readAhead;
9224     } else {
9225       if (gameFileFP == NULL)
9226           return FALSE;
9227       moveType = (ChessMove) yylex();
9228     }
9229     
9230     done = FALSE;
9231     switch (moveType) {
9232       case Comment:
9233         if (appData.debugMode) 
9234           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9235         p = yy_text;
9236
9237         /* append the comment but don't display it */
9238         AppendComment(currentMove, p, FALSE);
9239         return TRUE;
9240
9241       case WhiteCapturesEnPassant:
9242       case BlackCapturesEnPassant:
9243       case WhitePromotionChancellor:
9244       case BlackPromotionChancellor:
9245       case WhitePromotionArchbishop:
9246       case BlackPromotionArchbishop:
9247       case WhitePromotionCentaur:
9248       case BlackPromotionCentaur:
9249       case WhitePromotionQueen:
9250       case BlackPromotionQueen:
9251       case WhitePromotionRook:
9252       case BlackPromotionRook:
9253       case WhitePromotionBishop:
9254       case BlackPromotionBishop:
9255       case WhitePromotionKnight:
9256       case BlackPromotionKnight:
9257       case WhitePromotionKing:
9258       case BlackPromotionKing:
9259       case NormalMove:
9260       case WhiteKingSideCastle:
9261       case WhiteQueenSideCastle:
9262       case BlackKingSideCastle:
9263       case BlackQueenSideCastle:
9264       case WhiteKingSideCastleWild:
9265       case WhiteQueenSideCastleWild:
9266       case BlackKingSideCastleWild:
9267       case BlackQueenSideCastleWild:
9268       /* PUSH Fabien */
9269       case WhiteHSideCastleFR:
9270       case WhiteASideCastleFR:
9271       case BlackHSideCastleFR:
9272       case BlackASideCastleFR:
9273       /* POP Fabien */
9274         if (appData.debugMode)
9275           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9276         fromX = currentMoveString[0] - AAA;
9277         fromY = currentMoveString[1] - ONE;
9278         toX = currentMoveString[2] - AAA;
9279         toY = currentMoveString[3] - ONE;
9280         promoChar = currentMoveString[4];
9281         break;
9282
9283       case WhiteDrop:
9284       case BlackDrop:
9285         if (appData.debugMode)
9286           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9287         fromX = moveType == WhiteDrop ?
9288           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9289         (int) CharToPiece(ToLower(currentMoveString[0]));
9290         fromY = DROP_RANK;
9291         toX = currentMoveString[2] - AAA;
9292         toY = currentMoveString[3] - ONE;
9293         break;
9294
9295       case WhiteWins:
9296       case BlackWins:
9297       case GameIsDrawn:
9298       case GameUnfinished:
9299         if (appData.debugMode)
9300           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9301         p = strchr(yy_text, '{');
9302         if (p == NULL) p = strchr(yy_text, '(');
9303         if (p == NULL) {
9304             p = yy_text;
9305             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9306         } else {
9307             q = strchr(p, *p == '{' ? '}' : ')');
9308             if (q != NULL) *q = NULLCHAR;
9309             p++;
9310         }
9311         GameEnds(moveType, p, GE_FILE);
9312         done = TRUE;
9313         if (cmailMsgLoaded) {
9314             ClearHighlights();
9315             flipView = WhiteOnMove(currentMove);
9316             if (moveType == GameUnfinished) flipView = !flipView;
9317             if (appData.debugMode)
9318               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9319         }
9320         break;
9321
9322       case (ChessMove) 0:       /* end of file */
9323         if (appData.debugMode)
9324           fprintf(debugFP, "Parser hit end of file\n");
9325         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9326           case MT_NONE:
9327           case MT_CHECK:
9328             break;
9329           case MT_CHECKMATE:
9330           case MT_STAINMATE:
9331             if (WhiteOnMove(currentMove)) {
9332                 GameEnds(BlackWins, "Black mates", GE_FILE);
9333             } else {
9334                 GameEnds(WhiteWins, "White mates", GE_FILE);
9335             }
9336             break;
9337           case MT_STALEMATE:
9338             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9339             break;
9340         }
9341         done = TRUE;
9342         break;
9343
9344       case MoveNumberOne:
9345         if (lastLoadGameStart == GNUChessGame) {
9346             /* GNUChessGames have numbers, but they aren't move numbers */
9347             if (appData.debugMode)
9348               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9349                       yy_text, (int) moveType);
9350             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9351         }
9352         /* else fall thru */
9353
9354       case XBoardGame:
9355       case GNUChessGame:
9356       case PGNTag:
9357         /* Reached start of next game in file */
9358         if (appData.debugMode)
9359           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9360         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9361           case MT_NONE:
9362           case MT_CHECK:
9363             break;
9364           case MT_CHECKMATE:
9365           case MT_STAINMATE:
9366             if (WhiteOnMove(currentMove)) {
9367                 GameEnds(BlackWins, "Black mates", GE_FILE);
9368             } else {
9369                 GameEnds(WhiteWins, "White mates", GE_FILE);
9370             }
9371             break;
9372           case MT_STALEMATE:
9373             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9374             break;
9375         }
9376         done = TRUE;
9377         break;
9378
9379       case PositionDiagram:     /* should not happen; ignore */
9380       case ElapsedTime:         /* ignore */
9381       case NAG:                 /* ignore */
9382         if (appData.debugMode)
9383           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9384                   yy_text, (int) moveType);
9385         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9386
9387       case IllegalMove:
9388         if (appData.testLegality) {
9389             if (appData.debugMode)
9390               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9391             sprintf(move, _("Illegal move: %d.%s%s"),
9392                     (forwardMostMove / 2) + 1,
9393                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9394             DisplayError(move, 0);
9395             done = TRUE;
9396         } else {
9397             if (appData.debugMode)
9398               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9399                       yy_text, currentMoveString);
9400             fromX = currentMoveString[0] - AAA;
9401             fromY = currentMoveString[1] - ONE;
9402             toX = currentMoveString[2] - AAA;
9403             toY = currentMoveString[3] - ONE;
9404             promoChar = currentMoveString[4];
9405         }
9406         break;
9407
9408       case AmbiguousMove:
9409         if (appData.debugMode)
9410           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9411         sprintf(move, _("Ambiguous move: %d.%s%s"),
9412                 (forwardMostMove / 2) + 1,
9413                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9414         DisplayError(move, 0);
9415         done = TRUE;
9416         break;
9417
9418       default:
9419       case ImpossibleMove:
9420         if (appData.debugMode)
9421           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9422         sprintf(move, _("Illegal move: %d.%s%s"),
9423                 (forwardMostMove / 2) + 1,
9424                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9425         DisplayError(move, 0);
9426         done = TRUE;
9427         break;
9428     }
9429
9430     if (done) {
9431         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9432             DrawPosition(FALSE, boards[currentMove]);
9433             DisplayBothClocks();
9434             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9435               DisplayComment(currentMove - 1, commentList[currentMove]);
9436         }
9437         (void) StopLoadGameTimer();
9438         gameFileFP = NULL;
9439         cmailOldMove = forwardMostMove;
9440         return FALSE;
9441     } else {
9442         /* currentMoveString is set as a side-effect of yylex */
9443         strcat(currentMoveString, "\n");
9444         strcpy(moveList[forwardMostMove], currentMoveString);
9445         
9446         thinkOutput[0] = NULLCHAR;
9447         MakeMove(fromX, fromY, toX, toY, promoChar);
9448         currentMove = forwardMostMove;
9449         return TRUE;
9450     }
9451 }
9452
9453 /* Load the nth game from the given file */
9454 int
9455 LoadGameFromFile(filename, n, title, useList)
9456      char *filename;
9457      int n;
9458      char *title;
9459      /*Boolean*/ int useList;
9460 {
9461     FILE *f;
9462     char buf[MSG_SIZ];
9463
9464     if (strcmp(filename, "-") == 0) {
9465         f = stdin;
9466         title = "stdin";
9467     } else {
9468         f = fopen(filename, "rb");
9469         if (f == NULL) {
9470           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9471             DisplayError(buf, errno);
9472             return FALSE;
9473         }
9474     }
9475     if (fseek(f, 0, 0) == -1) {
9476         /* f is not seekable; probably a pipe */
9477         useList = FALSE;
9478     }
9479     if (useList && n == 0) {
9480         int error = GameListBuild(f);
9481         if (error) {
9482             DisplayError(_("Cannot build game list"), error);
9483         } else if (!ListEmpty(&gameList) &&
9484                    ((ListGame *) gameList.tailPred)->number > 1) {
9485             GameListPopUp(f, title);
9486             return TRUE;
9487         }
9488         GameListDestroy();
9489         n = 1;
9490     }
9491     if (n == 0) n = 1;
9492     return LoadGame(f, n, title, FALSE);
9493 }
9494
9495
9496 void
9497 MakeRegisteredMove()
9498 {
9499     int fromX, fromY, toX, toY;
9500     char promoChar;
9501     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9502         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9503           case CMAIL_MOVE:
9504           case CMAIL_DRAW:
9505             if (appData.debugMode)
9506               fprintf(debugFP, "Restoring %s for game %d\n",
9507                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9508     
9509             thinkOutput[0] = NULLCHAR;
9510             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9511             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9512             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9513             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9514             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9515             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9516             MakeMove(fromX, fromY, toX, toY, promoChar);
9517             ShowMove(fromX, fromY, toX, toY);
9518               
9519             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9520               case MT_NONE:
9521               case MT_CHECK:
9522                 break;
9523                 
9524               case MT_CHECKMATE:
9525               case MT_STAINMATE:
9526                 if (WhiteOnMove(currentMove)) {
9527                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9528                 } else {
9529                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9530                 }
9531                 break;
9532                 
9533               case MT_STALEMATE:
9534                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9535                 break;
9536             }
9537
9538             break;
9539             
9540           case CMAIL_RESIGN:
9541             if (WhiteOnMove(currentMove)) {
9542                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9543             } else {
9544                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9545             }
9546             break;
9547             
9548           case CMAIL_ACCEPT:
9549             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9550             break;
9551               
9552           default:
9553             break;
9554         }
9555     }
9556
9557     return;
9558 }
9559
9560 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9561 int
9562 CmailLoadGame(f, gameNumber, title, useList)
9563      FILE *f;
9564      int gameNumber;
9565      char *title;
9566      int useList;
9567 {
9568     int retVal;
9569
9570     if (gameNumber > nCmailGames) {
9571         DisplayError(_("No more games in this message"), 0);
9572         return FALSE;
9573     }
9574     if (f == lastLoadGameFP) {
9575         int offset = gameNumber - lastLoadGameNumber;
9576         if (offset == 0) {
9577             cmailMsg[0] = NULLCHAR;
9578             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9579                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9580                 nCmailMovesRegistered--;
9581             }
9582             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9583             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9584                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9585             }
9586         } else {
9587             if (! RegisterMove()) return FALSE;
9588         }
9589     }
9590
9591     retVal = LoadGame(f, gameNumber, title, useList);
9592
9593     /* Make move registered during previous look at this game, if any */
9594     MakeRegisteredMove();
9595
9596     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9597         commentList[currentMove]
9598           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9599         DisplayComment(currentMove - 1, commentList[currentMove]);
9600     }
9601
9602     return retVal;
9603 }
9604
9605 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9606 int
9607 ReloadGame(offset)
9608      int offset;
9609 {
9610     int gameNumber = lastLoadGameNumber + offset;
9611     if (lastLoadGameFP == NULL) {
9612         DisplayError(_("No game has been loaded yet"), 0);
9613         return FALSE;
9614     }
9615     if (gameNumber <= 0) {
9616         DisplayError(_("Can't back up any further"), 0);
9617         return FALSE;
9618     }
9619     if (cmailMsgLoaded) {
9620         return CmailLoadGame(lastLoadGameFP, gameNumber,
9621                              lastLoadGameTitle, lastLoadGameUseList);
9622     } else {
9623         return LoadGame(lastLoadGameFP, gameNumber,
9624                         lastLoadGameTitle, lastLoadGameUseList);
9625     }
9626 }
9627
9628
9629
9630 /* Load the nth game from open file f */
9631 int
9632 LoadGame(f, gameNumber, title, useList)
9633      FILE *f;
9634      int gameNumber;
9635      char *title;
9636      int useList;
9637 {
9638     ChessMove cm;
9639     char buf[MSG_SIZ];
9640     int gn = gameNumber;
9641     ListGame *lg = NULL;
9642     int numPGNTags = 0;
9643     int err;
9644     GameMode oldGameMode;
9645     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9646
9647     if (appData.debugMode) 
9648         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9649
9650     if (gameMode == Training )
9651         SetTrainingModeOff();
9652
9653     oldGameMode = gameMode;
9654     if (gameMode != BeginningOfGame) {
9655       Reset(FALSE, TRUE);
9656     }
9657
9658     gameFileFP = f;
9659     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9660         fclose(lastLoadGameFP);
9661     }
9662
9663     if (useList) {
9664         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9665         
9666         if (lg) {
9667             fseek(f, lg->offset, 0);
9668             GameListHighlight(gameNumber);
9669             gn = 1;
9670         }
9671         else {
9672             DisplayError(_("Game number out of range"), 0);
9673             return FALSE;
9674         }
9675     } else {
9676         GameListDestroy();
9677         if (fseek(f, 0, 0) == -1) {
9678             if (f == lastLoadGameFP ?
9679                 gameNumber == lastLoadGameNumber + 1 :
9680                 gameNumber == 1) {
9681                 gn = 1;
9682             } else {
9683                 DisplayError(_("Can't seek on game file"), 0);
9684                 return FALSE;
9685             }
9686         }
9687     }
9688     lastLoadGameFP = f;
9689     lastLoadGameNumber = gameNumber;
9690     strcpy(lastLoadGameTitle, title);
9691     lastLoadGameUseList = useList;
9692
9693     yynewfile(f);
9694
9695     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9696       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9697                 lg->gameInfo.black);
9698             DisplayTitle(buf);
9699     } else if (*title != NULLCHAR) {
9700         if (gameNumber > 1) {
9701             sprintf(buf, "%s %d", title, gameNumber);
9702             DisplayTitle(buf);
9703         } else {
9704             DisplayTitle(title);
9705         }
9706     }
9707
9708     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9709         gameMode = PlayFromGameFile;
9710         ModeHighlight();
9711     }
9712
9713     currentMove = forwardMostMove = backwardMostMove = 0;
9714     CopyBoard(boards[0], initialPosition);
9715     StopClocks();
9716
9717     /*
9718      * Skip the first gn-1 games in the file.
9719      * Also skip over anything that precedes an identifiable 
9720      * start of game marker, to avoid being confused by 
9721      * garbage at the start of the file.  Currently 
9722      * recognized start of game markers are the move number "1",
9723      * the pattern "gnuchess .* game", the pattern
9724      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9725      * A game that starts with one of the latter two patterns
9726      * will also have a move number 1, possibly
9727      * following a position diagram.
9728      * 5-4-02: Let's try being more lenient and allowing a game to
9729      * start with an unnumbered move.  Does that break anything?
9730      */
9731     cm = lastLoadGameStart = (ChessMove) 0;
9732     while (gn > 0) {
9733         yyboardindex = forwardMostMove;
9734         cm = (ChessMove) yylex();
9735         switch (cm) {
9736           case (ChessMove) 0:
9737             if (cmailMsgLoaded) {
9738                 nCmailGames = CMAIL_MAX_GAMES - gn;
9739             } else {
9740                 Reset(TRUE, TRUE);
9741                 DisplayError(_("Game not found in file"), 0);
9742             }
9743             return FALSE;
9744
9745           case GNUChessGame:
9746           case XBoardGame:
9747             gn--;
9748             lastLoadGameStart = cm;
9749             break;
9750             
9751           case MoveNumberOne:
9752             switch (lastLoadGameStart) {
9753               case GNUChessGame:
9754               case XBoardGame:
9755               case PGNTag:
9756                 break;
9757               case MoveNumberOne:
9758               case (ChessMove) 0:
9759                 gn--;           /* count this game */
9760                 lastLoadGameStart = cm;
9761                 break;
9762               default:
9763                 /* impossible */
9764                 break;
9765             }
9766             break;
9767
9768           case PGNTag:
9769             switch (lastLoadGameStart) {
9770               case GNUChessGame:
9771               case PGNTag:
9772               case MoveNumberOne:
9773               case (ChessMove) 0:
9774                 gn--;           /* count this game */
9775                 lastLoadGameStart = cm;
9776                 break;
9777               case XBoardGame:
9778                 lastLoadGameStart = cm; /* game counted already */
9779                 break;
9780               default:
9781                 /* impossible */
9782                 break;
9783             }
9784             if (gn > 0) {
9785                 do {
9786                     yyboardindex = forwardMostMove;
9787                     cm = (ChessMove) yylex();
9788                 } while (cm == PGNTag || cm == Comment);
9789             }
9790             break;
9791
9792           case WhiteWins:
9793           case BlackWins:
9794           case GameIsDrawn:
9795             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9796                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9797                     != CMAIL_OLD_RESULT) {
9798                     nCmailResults ++ ;
9799                     cmailResult[  CMAIL_MAX_GAMES
9800                                 - gn - 1] = CMAIL_OLD_RESULT;
9801                 }
9802             }
9803             break;
9804
9805           case NormalMove:
9806             /* Only a NormalMove can be at the start of a game
9807              * without a position diagram. */
9808             if (lastLoadGameStart == (ChessMove) 0) {
9809               gn--;
9810               lastLoadGameStart = MoveNumberOne;
9811             }
9812             break;
9813
9814           default:
9815             break;
9816         }
9817     }
9818     
9819     if (appData.debugMode)
9820       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9821
9822     if (cm == XBoardGame) {
9823         /* Skip any header junk before position diagram and/or move 1 */
9824         for (;;) {
9825             yyboardindex = forwardMostMove;
9826             cm = (ChessMove) yylex();
9827
9828             if (cm == (ChessMove) 0 ||
9829                 cm == GNUChessGame || cm == XBoardGame) {
9830                 /* Empty game; pretend end-of-file and handle later */
9831                 cm = (ChessMove) 0;
9832                 break;
9833             }
9834
9835             if (cm == MoveNumberOne || cm == PositionDiagram ||
9836                 cm == PGNTag || cm == Comment)
9837               break;
9838         }
9839     } else if (cm == GNUChessGame) {
9840         if (gameInfo.event != NULL) {
9841             free(gameInfo.event);
9842         }
9843         gameInfo.event = StrSave(yy_text);
9844     }   
9845
9846     startedFromSetupPosition = FALSE;
9847     while (cm == PGNTag) {
9848         if (appData.debugMode) 
9849           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9850         err = ParsePGNTag(yy_text, &gameInfo);
9851         if (!err) numPGNTags++;
9852
9853         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9854         if(gameInfo.variant != oldVariant) {
9855             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9856             InitPosition(TRUE);
9857             oldVariant = gameInfo.variant;
9858             if (appData.debugMode) 
9859               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9860         }
9861
9862
9863         if (gameInfo.fen != NULL) {
9864           Board initial_position;
9865           startedFromSetupPosition = TRUE;
9866           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9867             Reset(TRUE, TRUE);
9868             DisplayError(_("Bad FEN position in file"), 0);
9869             return FALSE;
9870           }
9871           CopyBoard(boards[0], initial_position);
9872           if (blackPlaysFirst) {
9873             currentMove = forwardMostMove = backwardMostMove = 1;
9874             CopyBoard(boards[1], initial_position);
9875             strcpy(moveList[0], "");
9876             strcpy(parseList[0], "");
9877             timeRemaining[0][1] = whiteTimeRemaining;
9878             timeRemaining[1][1] = blackTimeRemaining;
9879             if (commentList[0] != NULL) {
9880               commentList[1] = commentList[0];
9881               commentList[0] = NULL;
9882             }
9883           } else {
9884             currentMove = forwardMostMove = backwardMostMove = 0;
9885           }
9886           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9887           {   int i;
9888               initialRulePlies = FENrulePlies;
9889               for( i=0; i< nrCastlingRights; i++ )
9890                   initialRights[i] = initial_position[CASTLING][i];
9891           }
9892           yyboardindex = forwardMostMove;
9893           free(gameInfo.fen);
9894           gameInfo.fen = NULL;
9895         }
9896
9897         yyboardindex = forwardMostMove;
9898         cm = (ChessMove) yylex();
9899
9900         /* Handle comments interspersed among the tags */
9901         while (cm == Comment) {
9902             char *p;
9903             if (appData.debugMode) 
9904               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9905             p = yy_text;
9906             AppendComment(currentMove, p, FALSE);
9907             yyboardindex = forwardMostMove;
9908             cm = (ChessMove) yylex();
9909         }
9910     }
9911
9912     /* don't rely on existence of Event tag since if game was
9913      * pasted from clipboard the Event tag may not exist
9914      */
9915     if (numPGNTags > 0){
9916         char *tags;
9917         if (gameInfo.variant == VariantNormal) {
9918           gameInfo.variant = StringToVariant(gameInfo.event);
9919         }
9920         if (!matchMode) {
9921           if( appData.autoDisplayTags ) {
9922             tags = PGNTags(&gameInfo);
9923             TagsPopUp(tags, CmailMsg());
9924             free(tags);
9925           }
9926         }
9927     } else {
9928         /* Make something up, but don't display it now */
9929         SetGameInfo();
9930         TagsPopDown();
9931     }
9932
9933     if (cm == PositionDiagram) {
9934         int i, j;
9935         char *p;
9936         Board initial_position;
9937
9938         if (appData.debugMode)
9939           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9940
9941         if (!startedFromSetupPosition) {
9942             p = yy_text;
9943             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9944               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9945                 switch (*p) {
9946                   case '[':
9947                   case '-':
9948                   case ' ':
9949                   case '\t':
9950                   case '\n':
9951                   case '\r':
9952                     break;
9953                   default:
9954                     initial_position[i][j++] = CharToPiece(*p);
9955                     break;
9956                 }
9957             while (*p == ' ' || *p == '\t' ||
9958                    *p == '\n' || *p == '\r') p++;
9959         
9960             if (strncmp(p, "black", strlen("black"))==0)
9961               blackPlaysFirst = TRUE;
9962             else
9963               blackPlaysFirst = FALSE;
9964             startedFromSetupPosition = TRUE;
9965         
9966             CopyBoard(boards[0], initial_position);
9967             if (blackPlaysFirst) {
9968                 currentMove = forwardMostMove = backwardMostMove = 1;
9969                 CopyBoard(boards[1], initial_position);
9970                 strcpy(moveList[0], "");
9971                 strcpy(parseList[0], "");
9972                 timeRemaining[0][1] = whiteTimeRemaining;
9973                 timeRemaining[1][1] = blackTimeRemaining;
9974                 if (commentList[0] != NULL) {
9975                     commentList[1] = commentList[0];
9976                     commentList[0] = NULL;
9977                 }
9978             } else {
9979                 currentMove = forwardMostMove = backwardMostMove = 0;
9980             }
9981         }
9982         yyboardindex = forwardMostMove;
9983         cm = (ChessMove) yylex();
9984     }
9985
9986     if (first.pr == NoProc) {
9987         StartChessProgram(&first);
9988     }
9989     InitChessProgram(&first, FALSE);
9990     SendToProgram("force\n", &first);
9991     if (startedFromSetupPosition) {
9992         SendBoard(&first, forwardMostMove);
9993     if (appData.debugMode) {
9994         fprintf(debugFP, "Load Game\n");
9995     }
9996         DisplayBothClocks();
9997     }      
9998
9999     /* [HGM] server: flag to write setup moves in broadcast file as one */
10000     loadFlag = appData.suppressLoadMoves;
10001
10002     while (cm == Comment) {
10003         char *p;
10004         if (appData.debugMode) 
10005           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10006         p = yy_text;
10007         AppendComment(currentMove, p, FALSE);
10008         yyboardindex = forwardMostMove;
10009         cm = (ChessMove) yylex();
10010     }
10011
10012     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10013         cm == WhiteWins || cm == BlackWins ||
10014         cm == GameIsDrawn || cm == GameUnfinished) {
10015         DisplayMessage("", _("No moves in game"));
10016         if (cmailMsgLoaded) {
10017             if (appData.debugMode)
10018               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10019             ClearHighlights();
10020             flipView = FALSE;
10021         }
10022         DrawPosition(FALSE, boards[currentMove]);
10023         DisplayBothClocks();
10024         gameMode = EditGame;
10025         ModeHighlight();
10026         gameFileFP = NULL;
10027         cmailOldMove = 0;
10028         return TRUE;
10029     }
10030
10031     // [HGM] PV info: routine tests if comment empty
10032     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10033         DisplayComment(currentMove - 1, commentList[currentMove]);
10034     }
10035     if (!matchMode && appData.timeDelay != 0) 
10036       DrawPosition(FALSE, boards[currentMove]);
10037
10038     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10039       programStats.ok_to_send = 1;
10040     }
10041
10042     /* if the first token after the PGN tags is a move
10043      * and not move number 1, retrieve it from the parser 
10044      */
10045     if (cm != MoveNumberOne)
10046         LoadGameOneMove(cm);
10047
10048     /* load the remaining moves from the file */
10049     while (LoadGameOneMove((ChessMove)0)) {
10050       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10051       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10052     }
10053
10054     /* rewind to the start of the game */
10055     currentMove = backwardMostMove;
10056
10057     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10058
10059     if (oldGameMode == AnalyzeFile ||
10060         oldGameMode == AnalyzeMode) {
10061       AnalyzeFileEvent();
10062     }
10063
10064     if (matchMode || appData.timeDelay == 0) {
10065       ToEndEvent();
10066       gameMode = EditGame;
10067       ModeHighlight();
10068     } else if (appData.timeDelay > 0) {
10069       AutoPlayGameLoop();
10070     }
10071
10072     if (appData.debugMode) 
10073         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10074
10075     loadFlag = 0; /* [HGM] true game starts */
10076     return TRUE;
10077 }
10078
10079 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10080 int
10081 ReloadPosition(offset)
10082      int offset;
10083 {
10084     int positionNumber = lastLoadPositionNumber + offset;
10085     if (lastLoadPositionFP == NULL) {
10086         DisplayError(_("No position has been loaded yet"), 0);
10087         return FALSE;
10088     }
10089     if (positionNumber <= 0) {
10090         DisplayError(_("Can't back up any further"), 0);
10091         return FALSE;
10092     }
10093     return LoadPosition(lastLoadPositionFP, positionNumber,
10094                         lastLoadPositionTitle);
10095 }
10096
10097 /* Load the nth position from the given file */
10098 int
10099 LoadPositionFromFile(filename, n, title)
10100      char *filename;
10101      int n;
10102      char *title;
10103 {
10104     FILE *f;
10105     char buf[MSG_SIZ];
10106
10107     if (strcmp(filename, "-") == 0) {
10108         return LoadPosition(stdin, n, "stdin");
10109     } else {
10110         f = fopen(filename, "rb");
10111         if (f == NULL) {
10112             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10113             DisplayError(buf, errno);
10114             return FALSE;
10115         } else {
10116             return LoadPosition(f, n, title);
10117         }
10118     }
10119 }
10120
10121 /* Load the nth position from the given open file, and close it */
10122 int
10123 LoadPosition(f, positionNumber, title)
10124      FILE *f;
10125      int positionNumber;
10126      char *title;
10127 {
10128     char *p, line[MSG_SIZ];
10129     Board initial_position;
10130     int i, j, fenMode, pn;
10131     
10132     if (gameMode == Training )
10133         SetTrainingModeOff();
10134
10135     if (gameMode != BeginningOfGame) {
10136         Reset(FALSE, TRUE);
10137     }
10138     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10139         fclose(lastLoadPositionFP);
10140     }
10141     if (positionNumber == 0) positionNumber = 1;
10142     lastLoadPositionFP = f;
10143     lastLoadPositionNumber = positionNumber;
10144     strcpy(lastLoadPositionTitle, title);
10145     if (first.pr == NoProc) {
10146       StartChessProgram(&first);
10147       InitChessProgram(&first, FALSE);
10148     }    
10149     pn = positionNumber;
10150     if (positionNumber < 0) {
10151         /* Negative position number means to seek to that byte offset */
10152         if (fseek(f, -positionNumber, 0) == -1) {
10153             DisplayError(_("Can't seek on position file"), 0);
10154             return FALSE;
10155         };
10156         pn = 1;
10157     } else {
10158         if (fseek(f, 0, 0) == -1) {
10159             if (f == lastLoadPositionFP ?
10160                 positionNumber == lastLoadPositionNumber + 1 :
10161                 positionNumber == 1) {
10162                 pn = 1;
10163             } else {
10164                 DisplayError(_("Can't seek on position file"), 0);
10165                 return FALSE;
10166             }
10167         }
10168     }
10169     /* See if this file is FEN or old-style xboard */
10170     if (fgets(line, MSG_SIZ, f) == NULL) {
10171         DisplayError(_("Position not found in file"), 0);
10172         return FALSE;
10173     }
10174     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10175     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10176
10177     if (pn >= 2) {
10178         if (fenMode || line[0] == '#') pn--;
10179         while (pn > 0) {
10180             /* skip positions before number pn */
10181             if (fgets(line, MSG_SIZ, f) == NULL) {
10182                 Reset(TRUE, TRUE);
10183                 DisplayError(_("Position not found in file"), 0);
10184                 return FALSE;
10185             }
10186             if (fenMode || line[0] == '#') pn--;
10187         }
10188     }
10189
10190     if (fenMode) {
10191         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10192             DisplayError(_("Bad FEN position in file"), 0);
10193             return FALSE;
10194         }
10195     } else {
10196         (void) fgets(line, MSG_SIZ, f);
10197         (void) fgets(line, MSG_SIZ, f);
10198     
10199         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10200             (void) fgets(line, MSG_SIZ, f);
10201             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10202                 if (*p == ' ')
10203                   continue;
10204                 initial_position[i][j++] = CharToPiece(*p);
10205             }
10206         }
10207     
10208         blackPlaysFirst = FALSE;
10209         if (!feof(f)) {
10210             (void) fgets(line, MSG_SIZ, f);
10211             if (strncmp(line, "black", strlen("black"))==0)
10212               blackPlaysFirst = TRUE;
10213         }
10214     }
10215     startedFromSetupPosition = TRUE;
10216     
10217     SendToProgram("force\n", &first);
10218     CopyBoard(boards[0], initial_position);
10219     if (blackPlaysFirst) {
10220         currentMove = forwardMostMove = backwardMostMove = 1;
10221         strcpy(moveList[0], "");
10222         strcpy(parseList[0], "");
10223         CopyBoard(boards[1], initial_position);
10224         DisplayMessage("", _("Black to play"));
10225     } else {
10226         currentMove = forwardMostMove = backwardMostMove = 0;
10227         DisplayMessage("", _("White to play"));
10228     }
10229     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10230     SendBoard(&first, forwardMostMove);
10231     if (appData.debugMode) {
10232 int i, j;
10233   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10234   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10235         fprintf(debugFP, "Load Position\n");
10236     }
10237
10238     if (positionNumber > 1) {
10239         sprintf(line, "%s %d", title, positionNumber);
10240         DisplayTitle(line);
10241     } else {
10242         DisplayTitle(title);
10243     }
10244     gameMode = EditGame;
10245     ModeHighlight();
10246     ResetClocks();
10247     timeRemaining[0][1] = whiteTimeRemaining;
10248     timeRemaining[1][1] = blackTimeRemaining;
10249     DrawPosition(FALSE, boards[currentMove]);
10250    
10251     return TRUE;
10252 }
10253
10254
10255 void
10256 CopyPlayerNameIntoFileName(dest, src)
10257      char **dest, *src;
10258 {
10259     while (*src != NULLCHAR && *src != ',') {
10260         if (*src == ' ') {
10261             *(*dest)++ = '_';
10262             src++;
10263         } else {
10264             *(*dest)++ = *src++;
10265         }
10266     }
10267 }
10268
10269 char *DefaultFileName(ext)
10270      char *ext;
10271 {
10272     static char def[MSG_SIZ];
10273     char *p;
10274
10275     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10276         p = def;
10277         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10278         *p++ = '-';
10279         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10280         *p++ = '.';
10281         strcpy(p, ext);
10282     } else {
10283         def[0] = NULLCHAR;
10284     }
10285     return def;
10286 }
10287
10288 /* Save the current game to the given file */
10289 int
10290 SaveGameToFile(filename, append)
10291      char *filename;
10292      int append;
10293 {
10294     FILE *f;
10295     char buf[MSG_SIZ];
10296
10297     if (strcmp(filename, "-") == 0) {
10298         return SaveGame(stdout, 0, NULL);
10299     } else {
10300         f = fopen(filename, append ? "a" : "w");
10301         if (f == NULL) {
10302             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10303             DisplayError(buf, errno);
10304             return FALSE;
10305         } else {
10306             return SaveGame(f, 0, NULL);
10307         }
10308     }
10309 }
10310
10311 char *
10312 SavePart(str)
10313      char *str;
10314 {
10315     static char buf[MSG_SIZ];
10316     char *p;
10317     
10318     p = strchr(str, ' ');
10319     if (p == NULL) return str;
10320     strncpy(buf, str, p - str);
10321     buf[p - str] = NULLCHAR;
10322     return buf;
10323 }
10324
10325 #define PGN_MAX_LINE 75
10326
10327 #define PGN_SIDE_WHITE  0
10328 #define PGN_SIDE_BLACK  1
10329
10330 /* [AS] */
10331 static int FindFirstMoveOutOfBook( int side )
10332 {
10333     int result = -1;
10334
10335     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10336         int index = backwardMostMove;
10337         int has_book_hit = 0;
10338
10339         if( (index % 2) != side ) {
10340             index++;
10341         }
10342
10343         while( index < forwardMostMove ) {
10344             /* Check to see if engine is in book */
10345             int depth = pvInfoList[index].depth;
10346             int score = pvInfoList[index].score;
10347             int in_book = 0;
10348
10349             if( depth <= 2 ) {
10350                 in_book = 1;
10351             }
10352             else if( score == 0 && depth == 63 ) {
10353                 in_book = 1; /* Zappa */
10354             }
10355             else if( score == 2 && depth == 99 ) {
10356                 in_book = 1; /* Abrok */
10357             }
10358
10359             has_book_hit += in_book;
10360
10361             if( ! in_book ) {
10362                 result = index;
10363
10364                 break;
10365             }
10366
10367             index += 2;
10368         }
10369     }
10370
10371     return result;
10372 }
10373
10374 /* [AS] */
10375 void GetOutOfBookInfo( char * buf )
10376 {
10377     int oob[2];
10378     int i;
10379     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10380
10381     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10382     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10383
10384     *buf = '\0';
10385
10386     if( oob[0] >= 0 || oob[1] >= 0 ) {
10387         for( i=0; i<2; i++ ) {
10388             int idx = oob[i];
10389
10390             if( idx >= 0 ) {
10391                 if( i > 0 && oob[0] >= 0 ) {
10392                     strcat( buf, "   " );
10393                 }
10394
10395                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10396                 sprintf( buf+strlen(buf), "%s%.2f", 
10397                     pvInfoList[idx].score >= 0 ? "+" : "",
10398                     pvInfoList[idx].score / 100.0 );
10399             }
10400         }
10401     }
10402 }
10403
10404 /* Save game in PGN style and close the file */
10405 int
10406 SaveGamePGN(f)
10407      FILE *f;
10408 {
10409     int i, offset, linelen, newblock;
10410     time_t tm;
10411 //    char *movetext;
10412     char numtext[32];
10413     int movelen, numlen, blank;
10414     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10415
10416     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10417     
10418     tm = time((time_t *) NULL);
10419     
10420     PrintPGNTags(f, &gameInfo);
10421     
10422     if (backwardMostMove > 0 || startedFromSetupPosition) {
10423         char *fen = PositionToFEN(backwardMostMove, NULL);
10424         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10425         fprintf(f, "\n{--------------\n");
10426         PrintPosition(f, backwardMostMove);
10427         fprintf(f, "--------------}\n");
10428         free(fen);
10429     }
10430     else {
10431         /* [AS] Out of book annotation */
10432         if( appData.saveOutOfBookInfo ) {
10433             char buf[64];
10434
10435             GetOutOfBookInfo( buf );
10436
10437             if( buf[0] != '\0' ) {
10438                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10439             }
10440         }
10441
10442         fprintf(f, "\n");
10443     }
10444
10445     i = backwardMostMove;
10446     linelen = 0;
10447     newblock = TRUE;
10448
10449     while (i < forwardMostMove) {
10450         /* Print comments preceding this move */
10451         if (commentList[i] != NULL) {
10452             if (linelen > 0) fprintf(f, "\n");
10453             fprintf(f, "%s", commentList[i]);
10454             linelen = 0;
10455             newblock = TRUE;
10456         }
10457
10458         /* Format move number */
10459         if ((i % 2) == 0) {
10460             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10461         } else {
10462             if (newblock) {
10463                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10464             } else {
10465                 numtext[0] = NULLCHAR;
10466             }
10467         }
10468         numlen = strlen(numtext);
10469         newblock = FALSE;
10470
10471         /* Print move number */
10472         blank = linelen > 0 && numlen > 0;
10473         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10474             fprintf(f, "\n");
10475             linelen = 0;
10476             blank = 0;
10477         }
10478         if (blank) {
10479             fprintf(f, " ");
10480             linelen++;
10481         }
10482         fprintf(f, "%s", numtext);
10483         linelen += numlen;
10484
10485         /* Get move */
10486         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10487         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10488
10489         /* Print move */
10490         blank = linelen > 0 && movelen > 0;
10491         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10492             fprintf(f, "\n");
10493             linelen = 0;
10494             blank = 0;
10495         }
10496         if (blank) {
10497             fprintf(f, " ");
10498             linelen++;
10499         }
10500         fprintf(f, "%s", move_buffer);
10501         linelen += movelen;
10502
10503         /* [AS] Add PV info if present */
10504         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10505             /* [HGM] add time */
10506             char buf[MSG_SIZ]; int seconds;
10507
10508             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10509
10510             if( seconds <= 0) buf[0] = 0; else
10511             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10512                 seconds = (seconds + 4)/10; // round to full seconds
10513                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10514                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10515             }
10516
10517             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10518                 pvInfoList[i].score >= 0 ? "+" : "",
10519                 pvInfoList[i].score / 100.0,
10520                 pvInfoList[i].depth,
10521                 buf );
10522
10523             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10524
10525             /* Print score/depth */
10526             blank = linelen > 0 && movelen > 0;
10527             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10528                 fprintf(f, "\n");
10529                 linelen = 0;
10530                 blank = 0;
10531             }
10532             if (blank) {
10533                 fprintf(f, " ");
10534                 linelen++;
10535             }
10536             fprintf(f, "%s", move_buffer);
10537             linelen += movelen;
10538         }
10539
10540         i++;
10541     }
10542     
10543     /* Start a new line */
10544     if (linelen > 0) fprintf(f, "\n");
10545
10546     /* Print comments after last move */
10547     if (commentList[i] != NULL) {
10548         fprintf(f, "%s\n", commentList[i]);
10549     }
10550
10551     /* Print result */
10552     if (gameInfo.resultDetails != NULL &&
10553         gameInfo.resultDetails[0] != NULLCHAR) {
10554         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10555                 PGNResult(gameInfo.result));
10556     } else {
10557         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10558     }
10559
10560     fclose(f);
10561     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10562     return TRUE;
10563 }
10564
10565 /* Save game in old style and close the file */
10566 int
10567 SaveGameOldStyle(f)
10568      FILE *f;
10569 {
10570     int i, offset;
10571     time_t tm;
10572     
10573     tm = time((time_t *) NULL);
10574     
10575     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10576     PrintOpponents(f);
10577     
10578     if (backwardMostMove > 0 || startedFromSetupPosition) {
10579         fprintf(f, "\n[--------------\n");
10580         PrintPosition(f, backwardMostMove);
10581         fprintf(f, "--------------]\n");
10582     } else {
10583         fprintf(f, "\n");
10584     }
10585
10586     i = backwardMostMove;
10587     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10588
10589     while (i < forwardMostMove) {
10590         if (commentList[i] != NULL) {
10591             fprintf(f, "[%s]\n", commentList[i]);
10592         }
10593
10594         if ((i % 2) == 1) {
10595             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10596             i++;
10597         } else {
10598             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10599             i++;
10600             if (commentList[i] != NULL) {
10601                 fprintf(f, "\n");
10602                 continue;
10603             }
10604             if (i >= forwardMostMove) {
10605                 fprintf(f, "\n");
10606                 break;
10607             }
10608             fprintf(f, "%s\n", parseList[i]);
10609             i++;
10610         }
10611     }
10612     
10613     if (commentList[i] != NULL) {
10614         fprintf(f, "[%s]\n", commentList[i]);
10615     }
10616
10617     /* This isn't really the old style, but it's close enough */
10618     if (gameInfo.resultDetails != NULL &&
10619         gameInfo.resultDetails[0] != NULLCHAR) {
10620         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10621                 gameInfo.resultDetails);
10622     } else {
10623         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10624     }
10625
10626     fclose(f);
10627     return TRUE;
10628 }
10629
10630 /* Save the current game to open file f and close the file */
10631 int
10632 SaveGame(f, dummy, dummy2)
10633      FILE *f;
10634      int dummy;
10635      char *dummy2;
10636 {
10637     if (gameMode == EditPosition) EditPositionDone(TRUE);
10638     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10639     if (appData.oldSaveStyle)
10640       return SaveGameOldStyle(f);
10641     else
10642       return SaveGamePGN(f);
10643 }
10644
10645 /* Save the current position to the given file */
10646 int
10647 SavePositionToFile(filename)
10648      char *filename;
10649 {
10650     FILE *f;
10651     char buf[MSG_SIZ];
10652
10653     if (strcmp(filename, "-") == 0) {
10654         return SavePosition(stdout, 0, NULL);
10655     } else {
10656         f = fopen(filename, "a");
10657         if (f == NULL) {
10658             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10659             DisplayError(buf, errno);
10660             return FALSE;
10661         } else {
10662             SavePosition(f, 0, NULL);
10663             return TRUE;
10664         }
10665     }
10666 }
10667
10668 /* Save the current position to the given open file and close the file */
10669 int
10670 SavePosition(f, dummy, dummy2)
10671      FILE *f;
10672      int dummy;
10673      char *dummy2;
10674 {
10675     time_t tm;
10676     char *fen;
10677     
10678     if (gameMode == EditPosition) EditPositionDone(TRUE);
10679     if (appData.oldSaveStyle) {
10680         tm = time((time_t *) NULL);
10681     
10682         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10683         PrintOpponents(f);
10684         fprintf(f, "[--------------\n");
10685         PrintPosition(f, currentMove);
10686         fprintf(f, "--------------]\n");
10687     } else {
10688         fen = PositionToFEN(currentMove, NULL);
10689         fprintf(f, "%s\n", fen);
10690         free(fen);
10691     }
10692     fclose(f);
10693     return TRUE;
10694 }
10695
10696 void
10697 ReloadCmailMsgEvent(unregister)
10698      int unregister;
10699 {
10700 #if !WIN32
10701     static char *inFilename = NULL;
10702     static char *outFilename;
10703     int i;
10704     struct stat inbuf, outbuf;
10705     int status;
10706     
10707     /* Any registered moves are unregistered if unregister is set, */
10708     /* i.e. invoked by the signal handler */
10709     if (unregister) {
10710         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10711             cmailMoveRegistered[i] = FALSE;
10712             if (cmailCommentList[i] != NULL) {
10713                 free(cmailCommentList[i]);
10714                 cmailCommentList[i] = NULL;
10715             }
10716         }
10717         nCmailMovesRegistered = 0;
10718     }
10719
10720     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10721         cmailResult[i] = CMAIL_NOT_RESULT;
10722     }
10723     nCmailResults = 0;
10724
10725     if (inFilename == NULL) {
10726         /* Because the filenames are static they only get malloced once  */
10727         /* and they never get freed                                      */
10728         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10729         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10730
10731         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10732         sprintf(outFilename, "%s.out", appData.cmailGameName);
10733     }
10734     
10735     status = stat(outFilename, &outbuf);
10736     if (status < 0) {
10737         cmailMailedMove = FALSE;
10738     } else {
10739         status = stat(inFilename, &inbuf);
10740         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10741     }
10742     
10743     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10744        counts the games, notes how each one terminated, etc.
10745        
10746        It would be nice to remove this kludge and instead gather all
10747        the information while building the game list.  (And to keep it
10748        in the game list nodes instead of having a bunch of fixed-size
10749        parallel arrays.)  Note this will require getting each game's
10750        termination from the PGN tags, as the game list builder does
10751        not process the game moves.  --mann
10752        */
10753     cmailMsgLoaded = TRUE;
10754     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10755     
10756     /* Load first game in the file or popup game menu */
10757     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10758
10759 #endif /* !WIN32 */
10760     return;
10761 }
10762
10763 int
10764 RegisterMove()
10765 {
10766     FILE *f;
10767     char string[MSG_SIZ];
10768
10769     if (   cmailMailedMove
10770         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10771         return TRUE;            /* Allow free viewing  */
10772     }
10773
10774     /* Unregister move to ensure that we don't leave RegisterMove        */
10775     /* with the move registered when the conditions for registering no   */
10776     /* longer hold                                                       */
10777     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10778         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10779         nCmailMovesRegistered --;
10780
10781         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10782           {
10783               free(cmailCommentList[lastLoadGameNumber - 1]);
10784               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10785           }
10786     }
10787
10788     if (cmailOldMove == -1) {
10789         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10790         return FALSE;
10791     }
10792
10793     if (currentMove > cmailOldMove + 1) {
10794         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10795         return FALSE;
10796     }
10797
10798     if (currentMove < cmailOldMove) {
10799         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10800         return FALSE;
10801     }
10802
10803     if (forwardMostMove > currentMove) {
10804         /* Silently truncate extra moves */
10805         TruncateGame();
10806     }
10807
10808     if (   (currentMove == cmailOldMove + 1)
10809         || (   (currentMove == cmailOldMove)
10810             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10811                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10812         if (gameInfo.result != GameUnfinished) {
10813             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10814         }
10815
10816         if (commentList[currentMove] != NULL) {
10817             cmailCommentList[lastLoadGameNumber - 1]
10818               = StrSave(commentList[currentMove]);
10819         }
10820         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10821
10822         if (appData.debugMode)
10823           fprintf(debugFP, "Saving %s for game %d\n",
10824                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10825
10826         sprintf(string,
10827                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10828         
10829         f = fopen(string, "w");
10830         if (appData.oldSaveStyle) {
10831             SaveGameOldStyle(f); /* also closes the file */
10832             
10833             sprintf(string, "%s.pos.out", appData.cmailGameName);
10834             f = fopen(string, "w");
10835             SavePosition(f, 0, NULL); /* also closes the file */
10836         } else {
10837             fprintf(f, "{--------------\n");
10838             PrintPosition(f, currentMove);
10839             fprintf(f, "--------------}\n\n");
10840             
10841             SaveGame(f, 0, NULL); /* also closes the file*/
10842         }
10843         
10844         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10845         nCmailMovesRegistered ++;
10846     } else if (nCmailGames == 1) {
10847         DisplayError(_("You have not made a move yet"), 0);
10848         return FALSE;
10849     }
10850
10851     return TRUE;
10852 }
10853
10854 void
10855 MailMoveEvent()
10856 {
10857 #if !WIN32
10858     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10859     FILE *commandOutput;
10860     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10861     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10862     int nBuffers;
10863     int i;
10864     int archived;
10865     char *arcDir;
10866
10867     if (! cmailMsgLoaded) {
10868         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10869         return;
10870     }
10871
10872     if (nCmailGames == nCmailResults) {
10873         DisplayError(_("No unfinished games"), 0);
10874         return;
10875     }
10876
10877 #if CMAIL_PROHIBIT_REMAIL
10878     if (cmailMailedMove) {
10879         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);
10880         DisplayError(msg, 0);
10881         return;
10882     }
10883 #endif
10884
10885     if (! (cmailMailedMove || RegisterMove())) return;
10886     
10887     if (   cmailMailedMove
10888         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10889         sprintf(string, partCommandString,
10890                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10891         commandOutput = popen(string, "r");
10892
10893         if (commandOutput == NULL) {
10894             DisplayError(_("Failed to invoke cmail"), 0);
10895         } else {
10896             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10897                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10898             }
10899             if (nBuffers > 1) {
10900                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10901                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10902                 nBytes = MSG_SIZ - 1;
10903             } else {
10904                 (void) memcpy(msg, buffer, nBytes);
10905             }
10906             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10907
10908             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10909                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10910
10911                 archived = TRUE;
10912                 for (i = 0; i < nCmailGames; i ++) {
10913                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10914                         archived = FALSE;
10915                     }
10916                 }
10917                 if (   archived
10918                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10919                         != NULL)) {
10920                     sprintf(buffer, "%s/%s.%s.archive",
10921                             arcDir,
10922                             appData.cmailGameName,
10923                             gameInfo.date);
10924                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10925                     cmailMsgLoaded = FALSE;
10926                 }
10927             }
10928
10929             DisplayInformation(msg);
10930             pclose(commandOutput);
10931         }
10932     } else {
10933         if ((*cmailMsg) != '\0') {
10934             DisplayInformation(cmailMsg);
10935         }
10936     }
10937
10938     return;
10939 #endif /* !WIN32 */
10940 }
10941
10942 char *
10943 CmailMsg()
10944 {
10945 #if WIN32
10946     return NULL;
10947 #else
10948     int  prependComma = 0;
10949     char number[5];
10950     char string[MSG_SIZ];       /* Space for game-list */
10951     int  i;
10952     
10953     if (!cmailMsgLoaded) return "";
10954
10955     if (cmailMailedMove) {
10956         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10957     } else {
10958         /* Create a list of games left */
10959         sprintf(string, "[");
10960         for (i = 0; i < nCmailGames; i ++) {
10961             if (! (   cmailMoveRegistered[i]
10962                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10963                 if (prependComma) {
10964                     sprintf(number, ",%d", i + 1);
10965                 } else {
10966                     sprintf(number, "%d", i + 1);
10967                     prependComma = 1;
10968                 }
10969                 
10970                 strcat(string, number);
10971             }
10972         }
10973         strcat(string, "]");
10974
10975         if (nCmailMovesRegistered + nCmailResults == 0) {
10976             switch (nCmailGames) {
10977               case 1:
10978                 sprintf(cmailMsg,
10979                         _("Still need to make move for game\n"));
10980                 break;
10981                 
10982               case 2:
10983                 sprintf(cmailMsg,
10984                         _("Still need to make moves for both games\n"));
10985                 break;
10986                 
10987               default:
10988                 sprintf(cmailMsg,
10989                         _("Still need to make moves for all %d games\n"),
10990                         nCmailGames);
10991                 break;
10992             }
10993         } else {
10994             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10995               case 1:
10996                 sprintf(cmailMsg,
10997                         _("Still need to make a move for game %s\n"),
10998                         string);
10999                 break;
11000                 
11001               case 0:
11002                 if (nCmailResults == nCmailGames) {
11003                     sprintf(cmailMsg, _("No unfinished games\n"));
11004                 } else {
11005                     sprintf(cmailMsg, _("Ready to send mail\n"));
11006                 }
11007                 break;
11008                 
11009               default:
11010                 sprintf(cmailMsg,
11011                         _("Still need to make moves for games %s\n"),
11012                         string);
11013             }
11014         }
11015     }
11016     return cmailMsg;
11017 #endif /* WIN32 */
11018 }
11019
11020 void
11021 ResetGameEvent()
11022 {
11023     if (gameMode == Training)
11024       SetTrainingModeOff();
11025
11026     Reset(TRUE, TRUE);
11027     cmailMsgLoaded = FALSE;
11028     if (appData.icsActive) {
11029       SendToICS(ics_prefix);
11030       SendToICS("refresh\n");
11031     }
11032 }
11033
11034 void
11035 ExitEvent(status)
11036      int status;
11037 {
11038     exiting++;
11039     if (exiting > 2) {
11040       /* Give up on clean exit */
11041       exit(status);
11042     }
11043     if (exiting > 1) {
11044       /* Keep trying for clean exit */
11045       return;
11046     }
11047
11048     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11049
11050     if (telnetISR != NULL) {
11051       RemoveInputSource(telnetISR);
11052     }
11053     if (icsPR != NoProc) {
11054       DestroyChildProcess(icsPR, TRUE);
11055     }
11056
11057     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11058     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11059
11060     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11061     /* make sure this other one finishes before killing it!                  */
11062     if(endingGame) { int count = 0;
11063         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11064         while(endingGame && count++ < 10) DoSleep(1);
11065         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11066     }
11067
11068     /* Kill off chess programs */
11069     if (first.pr != NoProc) {
11070         ExitAnalyzeMode();
11071         
11072         DoSleep( appData.delayBeforeQuit );
11073         SendToProgram("quit\n", &first);
11074         DoSleep( appData.delayAfterQuit );
11075         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11076     }
11077     if (second.pr != NoProc) {
11078         DoSleep( appData.delayBeforeQuit );
11079         SendToProgram("quit\n", &second);
11080         DoSleep( appData.delayAfterQuit );
11081         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11082     }
11083     if (first.isr != NULL) {
11084         RemoveInputSource(first.isr);
11085     }
11086     if (second.isr != NULL) {
11087         RemoveInputSource(second.isr);
11088     }
11089
11090     ShutDownFrontEnd();
11091     exit(status);
11092 }
11093
11094 void
11095 PauseEvent()
11096 {
11097     if (appData.debugMode)
11098         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11099     if (pausing) {
11100         pausing = FALSE;
11101         ModeHighlight();
11102         if (gameMode == MachinePlaysWhite ||
11103             gameMode == MachinePlaysBlack) {
11104             StartClocks();
11105         } else {
11106             DisplayBothClocks();
11107         }
11108         if (gameMode == PlayFromGameFile) {
11109             if (appData.timeDelay >= 0) 
11110                 AutoPlayGameLoop();
11111         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11112             Reset(FALSE, TRUE);
11113             SendToICS(ics_prefix);
11114             SendToICS("refresh\n");
11115         } else if (currentMove < forwardMostMove) {
11116             ForwardInner(forwardMostMove);
11117         }
11118         pauseExamInvalid = FALSE;
11119     } else {
11120         switch (gameMode) {
11121           default:
11122             return;
11123           case IcsExamining:
11124             pauseExamForwardMostMove = forwardMostMove;
11125             pauseExamInvalid = FALSE;
11126             /* fall through */
11127           case IcsObserving:
11128           case IcsPlayingWhite:
11129           case IcsPlayingBlack:
11130             pausing = TRUE;
11131             ModeHighlight();
11132             return;
11133           case PlayFromGameFile:
11134             (void) StopLoadGameTimer();
11135             pausing = TRUE;
11136             ModeHighlight();
11137             break;
11138           case BeginningOfGame:
11139             if (appData.icsActive) return;
11140             /* else fall through */
11141           case MachinePlaysWhite:
11142           case MachinePlaysBlack:
11143           case TwoMachinesPlay:
11144             if (forwardMostMove == 0)
11145               return;           /* don't pause if no one has moved */
11146             if ((gameMode == MachinePlaysWhite &&
11147                  !WhiteOnMove(forwardMostMove)) ||
11148                 (gameMode == MachinePlaysBlack &&
11149                  WhiteOnMove(forwardMostMove))) {
11150                 StopClocks();
11151             }
11152             pausing = TRUE;
11153             ModeHighlight();
11154             break;
11155         }
11156     }
11157 }
11158
11159 void
11160 EditCommentEvent()
11161 {
11162     char title[MSG_SIZ];
11163
11164     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11165         strcpy(title, _("Edit comment"));
11166     } else {
11167         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11168                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11169                 parseList[currentMove - 1]);
11170     }
11171
11172     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11173 }
11174
11175
11176 void
11177 EditTagsEvent()
11178 {
11179     char *tags = PGNTags(&gameInfo);
11180     EditTagsPopUp(tags);
11181     free(tags);
11182 }
11183
11184 void
11185 AnalyzeModeEvent()
11186 {
11187     if (appData.noChessProgram || gameMode == AnalyzeMode)
11188       return;
11189
11190     if (gameMode != AnalyzeFile) {
11191         if (!appData.icsEngineAnalyze) {
11192                EditGameEvent();
11193                if (gameMode != EditGame) return;
11194         }
11195         ResurrectChessProgram();
11196         SendToProgram("analyze\n", &first);
11197         first.analyzing = TRUE;
11198         /*first.maybeThinking = TRUE;*/
11199         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11200         EngineOutputPopUp();
11201     }
11202     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11203     pausing = FALSE;
11204     ModeHighlight();
11205     SetGameInfo();
11206
11207     StartAnalysisClock();
11208     GetTimeMark(&lastNodeCountTime);
11209     lastNodeCount = 0;
11210 }
11211
11212 void
11213 AnalyzeFileEvent()
11214 {
11215     if (appData.noChessProgram || gameMode == AnalyzeFile)
11216       return;
11217
11218     if (gameMode != AnalyzeMode) {
11219         EditGameEvent();
11220         if (gameMode != EditGame) return;
11221         ResurrectChessProgram();
11222         SendToProgram("analyze\n", &first);
11223         first.analyzing = TRUE;
11224         /*first.maybeThinking = TRUE;*/
11225         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11226         EngineOutputPopUp();
11227     }
11228     gameMode = AnalyzeFile;
11229     pausing = FALSE;
11230     ModeHighlight();
11231     SetGameInfo();
11232
11233     StartAnalysisClock();
11234     GetTimeMark(&lastNodeCountTime);
11235     lastNodeCount = 0;
11236 }
11237
11238 void
11239 MachineWhiteEvent()
11240 {
11241     char buf[MSG_SIZ];
11242     char *bookHit = NULL;
11243
11244     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11245       return;
11246
11247
11248     if (gameMode == PlayFromGameFile || 
11249         gameMode == TwoMachinesPlay  || 
11250         gameMode == Training         || 
11251         gameMode == AnalyzeMode      || 
11252         gameMode == EndOfGame)
11253         EditGameEvent();
11254
11255     if (gameMode == EditPosition) 
11256         EditPositionDone(TRUE);
11257
11258     if (!WhiteOnMove(currentMove)) {
11259         DisplayError(_("It is not White's turn"), 0);
11260         return;
11261     }
11262   
11263     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11264       ExitAnalyzeMode();
11265
11266     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11267         gameMode == AnalyzeFile)
11268         TruncateGame();
11269
11270     ResurrectChessProgram();    /* in case it isn't running */
11271     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11272         gameMode = MachinePlaysWhite;
11273         ResetClocks();
11274     } else
11275     gameMode = MachinePlaysWhite;
11276     pausing = FALSE;
11277     ModeHighlight();
11278     SetGameInfo();
11279     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11280     DisplayTitle(buf);
11281     if (first.sendName) {
11282       sprintf(buf, "name %s\n", gameInfo.black);
11283       SendToProgram(buf, &first);
11284     }
11285     if (first.sendTime) {
11286       if (first.useColors) {
11287         SendToProgram("black\n", &first); /*gnu kludge*/
11288       }
11289       SendTimeRemaining(&first, TRUE);
11290     }
11291     if (first.useColors) {
11292       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11293     }
11294     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11295     SetMachineThinkingEnables();
11296     first.maybeThinking = TRUE;
11297     StartClocks();
11298     firstMove = FALSE;
11299
11300     if (appData.autoFlipView && !flipView) {
11301       flipView = !flipView;
11302       DrawPosition(FALSE, NULL);
11303       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11304     }
11305
11306     if(bookHit) { // [HGM] book: simulate book reply
11307         static char bookMove[MSG_SIZ]; // a bit generous?
11308
11309         programStats.nodes = programStats.depth = programStats.time = 
11310         programStats.score = programStats.got_only_move = 0;
11311         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11312
11313         strcpy(bookMove, "move ");
11314         strcat(bookMove, bookHit);
11315         HandleMachineMove(bookMove, &first);
11316     }
11317 }
11318
11319 void
11320 MachineBlackEvent()
11321 {
11322     char buf[MSG_SIZ];
11323    char *bookHit = NULL;
11324
11325     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11326         return;
11327
11328
11329     if (gameMode == PlayFromGameFile || 
11330         gameMode == TwoMachinesPlay  || 
11331         gameMode == Training         || 
11332         gameMode == AnalyzeMode      || 
11333         gameMode == EndOfGame)
11334         EditGameEvent();
11335
11336     if (gameMode == EditPosition) 
11337         EditPositionDone(TRUE);
11338
11339     if (WhiteOnMove(currentMove)) {
11340         DisplayError(_("It is not Black's turn"), 0);
11341         return;
11342     }
11343     
11344     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11345       ExitAnalyzeMode();
11346
11347     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11348         gameMode == AnalyzeFile)
11349         TruncateGame();
11350
11351     ResurrectChessProgram();    /* in case it isn't running */
11352     gameMode = MachinePlaysBlack;
11353     pausing = FALSE;
11354     ModeHighlight();
11355     SetGameInfo();
11356     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11357     DisplayTitle(buf);
11358     if (first.sendName) {
11359       sprintf(buf, "name %s\n", gameInfo.white);
11360       SendToProgram(buf, &first);
11361     }
11362     if (first.sendTime) {
11363       if (first.useColors) {
11364         SendToProgram("white\n", &first); /*gnu kludge*/
11365       }
11366       SendTimeRemaining(&first, FALSE);
11367     }
11368     if (first.useColors) {
11369       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11370     }
11371     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11372     SetMachineThinkingEnables();
11373     first.maybeThinking = TRUE;
11374     StartClocks();
11375
11376     if (appData.autoFlipView && flipView) {
11377       flipView = !flipView;
11378       DrawPosition(FALSE, NULL);
11379       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11380     }
11381     if(bookHit) { // [HGM] book: simulate book reply
11382         static char bookMove[MSG_SIZ]; // a bit generous?
11383
11384         programStats.nodes = programStats.depth = programStats.time = 
11385         programStats.score = programStats.got_only_move = 0;
11386         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11387
11388         strcpy(bookMove, "move ");
11389         strcat(bookMove, bookHit);
11390         HandleMachineMove(bookMove, &first);
11391     }
11392 }
11393
11394
11395 void
11396 DisplayTwoMachinesTitle()
11397 {
11398     char buf[MSG_SIZ];
11399     if (appData.matchGames > 0) {
11400         if (first.twoMachinesColor[0] == 'w') {
11401             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11402                     gameInfo.white, gameInfo.black,
11403                     first.matchWins, second.matchWins,
11404                     matchGame - 1 - (first.matchWins + second.matchWins));
11405         } else {
11406             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11407                     gameInfo.white, gameInfo.black,
11408                     second.matchWins, first.matchWins,
11409                     matchGame - 1 - (first.matchWins + second.matchWins));
11410         }
11411     } else {
11412         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11413     }
11414     DisplayTitle(buf);
11415 }
11416
11417 void
11418 TwoMachinesEvent P((void))
11419 {
11420     int i;
11421     char buf[MSG_SIZ];
11422     ChessProgramState *onmove;
11423     char *bookHit = NULL;
11424     
11425     if (appData.noChessProgram) return;
11426
11427     switch (gameMode) {
11428       case TwoMachinesPlay:
11429         return;
11430       case MachinePlaysWhite:
11431       case MachinePlaysBlack:
11432         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11433             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11434             return;
11435         }
11436         /* fall through */
11437       case BeginningOfGame:
11438       case PlayFromGameFile:
11439       case EndOfGame:
11440         EditGameEvent();
11441         if (gameMode != EditGame) return;
11442         break;
11443       case EditPosition:
11444         EditPositionDone(TRUE);
11445         break;
11446       case AnalyzeMode:
11447       case AnalyzeFile:
11448         ExitAnalyzeMode();
11449         break;
11450       case EditGame:
11451       default:
11452         break;
11453     }
11454
11455 //    forwardMostMove = currentMove;
11456     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11457     ResurrectChessProgram();    /* in case first program isn't running */
11458
11459     if (second.pr == NULL) {
11460         StartChessProgram(&second);
11461         if (second.protocolVersion == 1) {
11462           TwoMachinesEventIfReady();
11463         } else {
11464           /* kludge: allow timeout for initial "feature" command */
11465           FreezeUI();
11466           DisplayMessage("", _("Starting second chess program"));
11467           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11468         }
11469         return;
11470     }
11471     DisplayMessage("", "");
11472     InitChessProgram(&second, FALSE);
11473     SendToProgram("force\n", &second);
11474     if (startedFromSetupPosition) {
11475         SendBoard(&second, backwardMostMove);
11476     if (appData.debugMode) {
11477         fprintf(debugFP, "Two Machines\n");
11478     }
11479     }
11480     for (i = backwardMostMove; i < forwardMostMove; i++) {
11481         SendMoveToProgram(i, &second);
11482     }
11483
11484     gameMode = TwoMachinesPlay;
11485     pausing = FALSE;
11486     ModeHighlight();
11487     SetGameInfo();
11488     DisplayTwoMachinesTitle();
11489     firstMove = TRUE;
11490     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11491         onmove = &first;
11492     } else {
11493         onmove = &second;
11494     }
11495
11496     SendToProgram(first.computerString, &first);
11497     if (first.sendName) {
11498       sprintf(buf, "name %s\n", second.tidy);
11499       SendToProgram(buf, &first);
11500     }
11501     SendToProgram(second.computerString, &second);
11502     if (second.sendName) {
11503       sprintf(buf, "name %s\n", first.tidy);
11504       SendToProgram(buf, &second);
11505     }
11506
11507     ResetClocks();
11508     if (!first.sendTime || !second.sendTime) {
11509         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11510         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11511     }
11512     if (onmove->sendTime) {
11513       if (onmove->useColors) {
11514         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11515       }
11516       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11517     }
11518     if (onmove->useColors) {
11519       SendToProgram(onmove->twoMachinesColor, onmove);
11520     }
11521     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11522 //    SendToProgram("go\n", onmove);
11523     onmove->maybeThinking = TRUE;
11524     SetMachineThinkingEnables();
11525
11526     StartClocks();
11527
11528     if(bookHit) { // [HGM] book: simulate book reply
11529         static char bookMove[MSG_SIZ]; // a bit generous?
11530
11531         programStats.nodes = programStats.depth = programStats.time = 
11532         programStats.score = programStats.got_only_move = 0;
11533         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11534
11535         strcpy(bookMove, "move ");
11536         strcat(bookMove, bookHit);
11537         savedMessage = bookMove; // args for deferred call
11538         savedState = onmove;
11539         ScheduleDelayedEvent(DeferredBookMove, 1);
11540     }
11541 }
11542
11543 void
11544 TrainingEvent()
11545 {
11546     if (gameMode == Training) {
11547       SetTrainingModeOff();
11548       gameMode = PlayFromGameFile;
11549       DisplayMessage("", _("Training mode off"));
11550     } else {
11551       gameMode = Training;
11552       animateTraining = appData.animate;
11553
11554       /* make sure we are not already at the end of the game */
11555       if (currentMove < forwardMostMove) {
11556         SetTrainingModeOn();
11557         DisplayMessage("", _("Training mode on"));
11558       } else {
11559         gameMode = PlayFromGameFile;
11560         DisplayError(_("Already at end of game"), 0);
11561       }
11562     }
11563     ModeHighlight();
11564 }
11565
11566 void
11567 IcsClientEvent()
11568 {
11569     if (!appData.icsActive) return;
11570     switch (gameMode) {
11571       case IcsPlayingWhite:
11572       case IcsPlayingBlack:
11573       case IcsObserving:
11574       case IcsIdle:
11575       case BeginningOfGame:
11576       case IcsExamining:
11577         return;
11578
11579       case EditGame:
11580         break;
11581
11582       case EditPosition:
11583         EditPositionDone(TRUE);
11584         break;
11585
11586       case AnalyzeMode:
11587       case AnalyzeFile:
11588         ExitAnalyzeMode();
11589         break;
11590         
11591       default:
11592         EditGameEvent();
11593         break;
11594     }
11595
11596     gameMode = IcsIdle;
11597     ModeHighlight();
11598     return;
11599 }
11600
11601
11602 void
11603 EditGameEvent()
11604 {
11605     int i;
11606
11607     switch (gameMode) {
11608       case Training:
11609         SetTrainingModeOff();
11610         break;
11611       case MachinePlaysWhite:
11612       case MachinePlaysBlack:
11613       case BeginningOfGame:
11614         SendToProgram("force\n", &first);
11615         SetUserThinkingEnables();
11616         break;
11617       case PlayFromGameFile:
11618         (void) StopLoadGameTimer();
11619         if (gameFileFP != NULL) {
11620             gameFileFP = NULL;
11621         }
11622         break;
11623       case EditPosition:
11624         EditPositionDone(TRUE);
11625         break;
11626       case AnalyzeMode:
11627       case AnalyzeFile:
11628         ExitAnalyzeMode();
11629         SendToProgram("force\n", &first);
11630         break;
11631       case TwoMachinesPlay:
11632         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11633         ResurrectChessProgram();
11634         SetUserThinkingEnables();
11635         break;
11636       case EndOfGame:
11637         ResurrectChessProgram();
11638         break;
11639       case IcsPlayingBlack:
11640       case IcsPlayingWhite:
11641         DisplayError(_("Warning: You are still playing a game"), 0);
11642         break;
11643       case IcsObserving:
11644         DisplayError(_("Warning: You are still observing a game"), 0);
11645         break;
11646       case IcsExamining:
11647         DisplayError(_("Warning: You are still examining a game"), 0);
11648         break;
11649       case IcsIdle:
11650         break;
11651       case EditGame:
11652       default:
11653         return;
11654     }
11655     
11656     pausing = FALSE;
11657     StopClocks();
11658     first.offeredDraw = second.offeredDraw = 0;
11659
11660     if (gameMode == PlayFromGameFile) {
11661         whiteTimeRemaining = timeRemaining[0][currentMove];
11662         blackTimeRemaining = timeRemaining[1][currentMove];
11663         DisplayTitle("");
11664     }
11665
11666     if (gameMode == MachinePlaysWhite ||
11667         gameMode == MachinePlaysBlack ||
11668         gameMode == TwoMachinesPlay ||
11669         gameMode == EndOfGame) {
11670         i = forwardMostMove;
11671         while (i > currentMove) {
11672             SendToProgram("undo\n", &first);
11673             i--;
11674         }
11675         whiteTimeRemaining = timeRemaining[0][currentMove];
11676         blackTimeRemaining = timeRemaining[1][currentMove];
11677         DisplayBothClocks();
11678         if (whiteFlag || blackFlag) {
11679             whiteFlag = blackFlag = 0;
11680         }
11681         DisplayTitle("");
11682     }           
11683     
11684     gameMode = EditGame;
11685     ModeHighlight();
11686     SetGameInfo();
11687 }
11688
11689
11690 void
11691 EditPositionEvent()
11692 {
11693     if (gameMode == EditPosition) {
11694         EditGameEvent();
11695         return;
11696     }
11697     
11698     EditGameEvent();
11699     if (gameMode != EditGame) return;
11700     
11701     gameMode = EditPosition;
11702     ModeHighlight();
11703     SetGameInfo();
11704     if (currentMove > 0)
11705       CopyBoard(boards[0], boards[currentMove]);
11706     
11707     blackPlaysFirst = !WhiteOnMove(currentMove);
11708     ResetClocks();
11709     currentMove = forwardMostMove = backwardMostMove = 0;
11710     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11711     DisplayMove(-1);
11712 }
11713
11714 void
11715 ExitAnalyzeMode()
11716 {
11717     /* [DM] icsEngineAnalyze - possible call from other functions */
11718     if (appData.icsEngineAnalyze) {
11719         appData.icsEngineAnalyze = FALSE;
11720
11721         DisplayMessage("",_("Close ICS engine analyze..."));
11722     }
11723     if (first.analysisSupport && first.analyzing) {
11724       SendToProgram("exit\n", &first);
11725       first.analyzing = FALSE;
11726     }
11727     thinkOutput[0] = NULLCHAR;
11728 }
11729
11730 void
11731 EditPositionDone(Boolean fakeRights)
11732 {
11733     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11734
11735     startedFromSetupPosition = TRUE;
11736     InitChessProgram(&first, FALSE);
11737     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11738       boards[0][EP_STATUS] = EP_NONE;
11739       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11740     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11741         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11742         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11743       } else boards[0][CASTLING][2] = NoRights;
11744     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11745         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11746         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11747       } else boards[0][CASTLING][5] = NoRights;
11748     }
11749     SendToProgram("force\n", &first);
11750     if (blackPlaysFirst) {
11751         strcpy(moveList[0], "");
11752         strcpy(parseList[0], "");
11753         currentMove = forwardMostMove = backwardMostMove = 1;
11754         CopyBoard(boards[1], boards[0]);
11755     } else {
11756         currentMove = forwardMostMove = backwardMostMove = 0;
11757     }
11758     SendBoard(&first, forwardMostMove);
11759     if (appData.debugMode) {
11760         fprintf(debugFP, "EditPosDone\n");
11761     }
11762     DisplayTitle("");
11763     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11764     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11765     gameMode = EditGame;
11766     ModeHighlight();
11767     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11768     ClearHighlights(); /* [AS] */
11769 }
11770
11771 /* Pause for `ms' milliseconds */
11772 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11773 void
11774 TimeDelay(ms)
11775      long ms;
11776 {
11777     TimeMark m1, m2;
11778
11779     GetTimeMark(&m1);
11780     do {
11781         GetTimeMark(&m2);
11782     } while (SubtractTimeMarks(&m2, &m1) < ms);
11783 }
11784
11785 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11786 void
11787 SendMultiLineToICS(buf)
11788      char *buf;
11789 {
11790     char temp[MSG_SIZ+1], *p;
11791     int len;
11792
11793     len = strlen(buf);
11794     if (len > MSG_SIZ)
11795       len = MSG_SIZ;
11796   
11797     strncpy(temp, buf, len);
11798     temp[len] = 0;
11799
11800     p = temp;
11801     while (*p) {
11802         if (*p == '\n' || *p == '\r')
11803           *p = ' ';
11804         ++p;
11805     }
11806
11807     strcat(temp, "\n");
11808     SendToICS(temp);
11809     SendToPlayer(temp, strlen(temp));
11810 }
11811
11812 void
11813 SetWhiteToPlayEvent()
11814 {
11815     if (gameMode == EditPosition) {
11816         blackPlaysFirst = FALSE;
11817         DisplayBothClocks();    /* works because currentMove is 0 */
11818     } else if (gameMode == IcsExamining) {
11819         SendToICS(ics_prefix);
11820         SendToICS("tomove white\n");
11821     }
11822 }
11823
11824 void
11825 SetBlackToPlayEvent()
11826 {
11827     if (gameMode == EditPosition) {
11828         blackPlaysFirst = TRUE;
11829         currentMove = 1;        /* kludge */
11830         DisplayBothClocks();
11831         currentMove = 0;
11832     } else if (gameMode == IcsExamining) {
11833         SendToICS(ics_prefix);
11834         SendToICS("tomove black\n");
11835     }
11836 }
11837
11838 void
11839 EditPositionMenuEvent(selection, x, y)
11840      ChessSquare selection;
11841      int x, y;
11842 {
11843     char buf[MSG_SIZ];
11844     ChessSquare piece = boards[0][y][x];
11845
11846     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11847
11848     switch (selection) {
11849       case ClearBoard:
11850         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11851             SendToICS(ics_prefix);
11852             SendToICS("bsetup clear\n");
11853         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11854             SendToICS(ics_prefix);
11855             SendToICS("clearboard\n");
11856         } else {
11857             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11858                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11859                 for (y = 0; y < BOARD_HEIGHT; y++) {
11860                     if (gameMode == IcsExamining) {
11861                         if (boards[currentMove][y][x] != EmptySquare) {
11862                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11863                                     AAA + x, ONE + y);
11864                             SendToICS(buf);
11865                         }
11866                     } else {
11867                         boards[0][y][x] = p;
11868                     }
11869                 }
11870             }
11871         }
11872         if (gameMode == EditPosition) {
11873             DrawPosition(FALSE, boards[0]);
11874         }
11875         break;
11876
11877       case WhitePlay:
11878         SetWhiteToPlayEvent();
11879         break;
11880
11881       case BlackPlay:
11882         SetBlackToPlayEvent();
11883         break;
11884
11885       case EmptySquare:
11886         if (gameMode == IcsExamining) {
11887             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11888             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11889             SendToICS(buf);
11890         } else {
11891             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11892                 if(x == BOARD_LEFT-2) {
11893                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11894                     boards[0][y][1] = 0;
11895                 } else
11896                 if(x == BOARD_RGHT+1) {
11897                     if(y >= gameInfo.holdingsSize) break;
11898                     boards[0][y][BOARD_WIDTH-2] = 0;
11899                 } else break;
11900             }
11901             boards[0][y][x] = EmptySquare;
11902             DrawPosition(FALSE, boards[0]);
11903         }
11904         break;
11905
11906       case PromotePiece:
11907         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11908            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11909             selection = (ChessSquare) (PROMOTED piece);
11910         } else if(piece == EmptySquare) selection = WhiteSilver;
11911         else selection = (ChessSquare)((int)piece - 1);
11912         goto defaultlabel;
11913
11914       case DemotePiece:
11915         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11916            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11917             selection = (ChessSquare) (DEMOTED piece);
11918         } else if(piece == EmptySquare) selection = BlackSilver;
11919         else selection = (ChessSquare)((int)piece + 1);       
11920         goto defaultlabel;
11921
11922       case WhiteQueen:
11923       case BlackQueen:
11924         if(gameInfo.variant == VariantShatranj ||
11925            gameInfo.variant == VariantXiangqi  ||
11926            gameInfo.variant == VariantCourier  ||
11927            gameInfo.variant == VariantMakruk     )
11928             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11929         goto defaultlabel;
11930
11931       case WhiteKing:
11932       case BlackKing:
11933         if(gameInfo.variant == VariantXiangqi)
11934             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11935         if(gameInfo.variant == VariantKnightmate)
11936             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11937       default:
11938         defaultlabel:
11939         if (gameMode == IcsExamining) {
11940             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11941             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11942                     PieceToChar(selection), AAA + x, ONE + y);
11943             SendToICS(buf);
11944         } else {
11945             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11946                 int n;
11947                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11948                     n = PieceToNumber(selection - BlackPawn);
11949                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11950                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11951                     boards[0][BOARD_HEIGHT-1-n][1]++;
11952                 } else
11953                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11954                     n = PieceToNumber(selection);
11955                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11956                     boards[0][n][BOARD_WIDTH-1] = selection;
11957                     boards[0][n][BOARD_WIDTH-2]++;
11958                 }
11959             } else
11960             boards[0][y][x] = selection;
11961             DrawPosition(TRUE, boards[0]);
11962         }
11963         break;
11964     }
11965 }
11966
11967
11968 void
11969 DropMenuEvent(selection, x, y)
11970      ChessSquare selection;
11971      int x, y;
11972 {
11973     ChessMove moveType;
11974
11975     switch (gameMode) {
11976       case IcsPlayingWhite:
11977       case MachinePlaysBlack:
11978         if (!WhiteOnMove(currentMove)) {
11979             DisplayMoveError(_("It is Black's turn"));
11980             return;
11981         }
11982         moveType = WhiteDrop;
11983         break;
11984       case IcsPlayingBlack:
11985       case MachinePlaysWhite:
11986         if (WhiteOnMove(currentMove)) {
11987             DisplayMoveError(_("It is White's turn"));
11988             return;
11989         }
11990         moveType = BlackDrop;
11991         break;
11992       case EditGame:
11993         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11994         break;
11995       default:
11996         return;
11997     }
11998
11999     if (moveType == BlackDrop && selection < BlackPawn) {
12000       selection = (ChessSquare) ((int) selection
12001                                  + (int) BlackPawn - (int) WhitePawn);
12002     }
12003     if (boards[currentMove][y][x] != EmptySquare) {
12004         DisplayMoveError(_("That square is occupied"));
12005         return;
12006     }
12007
12008     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12009 }
12010
12011 void
12012 AcceptEvent()
12013 {
12014     /* Accept a pending offer of any kind from opponent */
12015     
12016     if (appData.icsActive) {
12017         SendToICS(ics_prefix);
12018         SendToICS("accept\n");
12019     } else if (cmailMsgLoaded) {
12020         if (currentMove == cmailOldMove &&
12021             commentList[cmailOldMove] != NULL &&
12022             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12023                    "Black offers a draw" : "White offers a draw")) {
12024             TruncateGame();
12025             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12026             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12027         } else {
12028             DisplayError(_("There is no pending offer on this move"), 0);
12029             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12030         }
12031     } else {
12032         /* Not used for offers from chess program */
12033     }
12034 }
12035
12036 void
12037 DeclineEvent()
12038 {
12039     /* Decline a pending offer of any kind from opponent */
12040     
12041     if (appData.icsActive) {
12042         SendToICS(ics_prefix);
12043         SendToICS("decline\n");
12044     } else if (cmailMsgLoaded) {
12045         if (currentMove == cmailOldMove &&
12046             commentList[cmailOldMove] != NULL &&
12047             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12048                    "Black offers a draw" : "White offers a draw")) {
12049 #ifdef NOTDEF
12050             AppendComment(cmailOldMove, "Draw declined", TRUE);
12051             DisplayComment(cmailOldMove - 1, "Draw declined");
12052 #endif /*NOTDEF*/
12053         } else {
12054             DisplayError(_("There is no pending offer on this move"), 0);
12055         }
12056     } else {
12057         /* Not used for offers from chess program */
12058     }
12059 }
12060
12061 void
12062 RematchEvent()
12063 {
12064     /* Issue ICS rematch command */
12065     if (appData.icsActive) {
12066         SendToICS(ics_prefix);
12067         SendToICS("rematch\n");
12068     }
12069 }
12070
12071 void
12072 CallFlagEvent()
12073 {
12074     /* Call your opponent's flag (claim a win on time) */
12075     if (appData.icsActive) {
12076         SendToICS(ics_prefix);
12077         SendToICS("flag\n");
12078     } else {
12079         switch (gameMode) {
12080           default:
12081             return;
12082           case MachinePlaysWhite:
12083             if (whiteFlag) {
12084                 if (blackFlag)
12085                   GameEnds(GameIsDrawn, "Both players ran out of time",
12086                            GE_PLAYER);
12087                 else
12088                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12089             } else {
12090                 DisplayError(_("Your opponent is not out of time"), 0);
12091             }
12092             break;
12093           case MachinePlaysBlack:
12094             if (blackFlag) {
12095                 if (whiteFlag)
12096                   GameEnds(GameIsDrawn, "Both players ran out of time",
12097                            GE_PLAYER);
12098                 else
12099                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12100             } else {
12101                 DisplayError(_("Your opponent is not out of time"), 0);
12102             }
12103             break;
12104         }
12105     }
12106 }
12107
12108 void
12109 DrawEvent()
12110 {
12111     /* Offer draw or accept pending draw offer from opponent */
12112     
12113     if (appData.icsActive) {
12114         /* Note: tournament rules require draw offers to be
12115            made after you make your move but before you punch
12116            your clock.  Currently ICS doesn't let you do that;
12117            instead, you immediately punch your clock after making
12118            a move, but you can offer a draw at any time. */
12119         
12120         SendToICS(ics_prefix);
12121         SendToICS("draw\n");
12122         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12123     } else if (cmailMsgLoaded) {
12124         if (currentMove == cmailOldMove &&
12125             commentList[cmailOldMove] != NULL &&
12126             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12127                    "Black offers a draw" : "White offers a draw")) {
12128             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12129             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12130         } else if (currentMove == cmailOldMove + 1) {
12131             char *offer = WhiteOnMove(cmailOldMove) ?
12132               "White offers a draw" : "Black offers a draw";
12133             AppendComment(currentMove, offer, TRUE);
12134             DisplayComment(currentMove - 1, offer);
12135             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12136         } else {
12137             DisplayError(_("You must make your move before offering a draw"), 0);
12138             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12139         }
12140     } else if (first.offeredDraw) {
12141         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12142     } else {
12143         if (first.sendDrawOffers) {
12144             SendToProgram("draw\n", &first);
12145             userOfferedDraw = TRUE;
12146         }
12147     }
12148 }
12149
12150 void
12151 AdjournEvent()
12152 {
12153     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12154     
12155     if (appData.icsActive) {
12156         SendToICS(ics_prefix);
12157         SendToICS("adjourn\n");
12158     } else {
12159         /* Currently GNU Chess doesn't offer or accept Adjourns */
12160     }
12161 }
12162
12163
12164 void
12165 AbortEvent()
12166 {
12167     /* Offer Abort or accept pending Abort offer from opponent */
12168     
12169     if (appData.icsActive) {
12170         SendToICS(ics_prefix);
12171         SendToICS("abort\n");
12172     } else {
12173         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12174     }
12175 }
12176
12177 void
12178 ResignEvent()
12179 {
12180     /* Resign.  You can do this even if it's not your turn. */
12181     
12182     if (appData.icsActive) {
12183         SendToICS(ics_prefix);
12184         SendToICS("resign\n");
12185     } else {
12186         switch (gameMode) {
12187           case MachinePlaysWhite:
12188             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12189             break;
12190           case MachinePlaysBlack:
12191             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12192             break;
12193           case EditGame:
12194             if (cmailMsgLoaded) {
12195                 TruncateGame();
12196                 if (WhiteOnMove(cmailOldMove)) {
12197                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12198                 } else {
12199                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12200                 }
12201                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12202             }
12203             break;
12204           default:
12205             break;
12206         }
12207     }
12208 }
12209
12210
12211 void
12212 StopObservingEvent()
12213 {
12214     /* Stop observing current games */
12215     SendToICS(ics_prefix);
12216     SendToICS("unobserve\n");
12217 }
12218
12219 void
12220 StopExaminingEvent()
12221 {
12222     /* Stop observing current game */
12223     SendToICS(ics_prefix);
12224     SendToICS("unexamine\n");
12225 }
12226
12227 void
12228 ForwardInner(target)
12229      int target;
12230 {
12231     int limit;
12232
12233     if (appData.debugMode)
12234         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12235                 target, currentMove, forwardMostMove);
12236
12237     if (gameMode == EditPosition)
12238       return;
12239
12240     if (gameMode == PlayFromGameFile && !pausing)
12241       PauseEvent();
12242     
12243     if (gameMode == IcsExamining && pausing)
12244       limit = pauseExamForwardMostMove;
12245     else
12246       limit = forwardMostMove;
12247     
12248     if (target > limit) target = limit;
12249
12250     if (target > 0 && moveList[target - 1][0]) {
12251         int fromX, fromY, toX, toY;
12252         toX = moveList[target - 1][2] - AAA;
12253         toY = moveList[target - 1][3] - ONE;
12254         if (moveList[target - 1][1] == '@') {
12255             if (appData.highlightLastMove) {
12256                 SetHighlights(-1, -1, toX, toY);
12257             }
12258         } else {
12259             fromX = moveList[target - 1][0] - AAA;
12260             fromY = moveList[target - 1][1] - ONE;
12261             if (target == currentMove + 1) {
12262                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12263             }
12264             if (appData.highlightLastMove) {
12265                 SetHighlights(fromX, fromY, toX, toY);
12266             }
12267         }
12268     }
12269     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12270         gameMode == Training || gameMode == PlayFromGameFile || 
12271         gameMode == AnalyzeFile) {
12272         while (currentMove < target) {
12273             SendMoveToProgram(currentMove++, &first);
12274         }
12275     } else {
12276         currentMove = target;
12277     }
12278     
12279     if (gameMode == EditGame || gameMode == EndOfGame) {
12280         whiteTimeRemaining = timeRemaining[0][currentMove];
12281         blackTimeRemaining = timeRemaining[1][currentMove];
12282     }
12283     DisplayBothClocks();
12284     DisplayMove(currentMove - 1);
12285     DrawPosition(FALSE, boards[currentMove]);
12286     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12287     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12288         DisplayComment(currentMove - 1, commentList[currentMove]);
12289     }
12290 }
12291
12292
12293 void
12294 ForwardEvent()
12295 {
12296     if (gameMode == IcsExamining && !pausing) {
12297         SendToICS(ics_prefix);
12298         SendToICS("forward\n");
12299     } else {
12300         ForwardInner(currentMove + 1);
12301     }
12302 }
12303
12304 void
12305 ToEndEvent()
12306 {
12307     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12308         /* to optimze, we temporarily turn off analysis mode while we feed
12309          * the remaining moves to the engine. Otherwise we get analysis output
12310          * after each move.
12311          */ 
12312         if (first.analysisSupport) {
12313           SendToProgram("exit\nforce\n", &first);
12314           first.analyzing = FALSE;
12315         }
12316     }
12317         
12318     if (gameMode == IcsExamining && !pausing) {
12319         SendToICS(ics_prefix);
12320         SendToICS("forward 999999\n");
12321     } else {
12322         ForwardInner(forwardMostMove);
12323     }
12324
12325     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12326         /* we have fed all the moves, so reactivate analysis mode */
12327         SendToProgram("analyze\n", &first);
12328         first.analyzing = TRUE;
12329         /*first.maybeThinking = TRUE;*/
12330         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12331     }
12332 }
12333
12334 void
12335 BackwardInner(target)
12336      int target;
12337 {
12338     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12339
12340     if (appData.debugMode)
12341         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12342                 target, currentMove, forwardMostMove);
12343
12344     if (gameMode == EditPosition) return;
12345     if (currentMove <= backwardMostMove) {
12346         ClearHighlights();
12347         DrawPosition(full_redraw, boards[currentMove]);
12348         return;
12349     }
12350     if (gameMode == PlayFromGameFile && !pausing)
12351       PauseEvent();
12352     
12353     if (moveList[target][0]) {
12354         int fromX, fromY, toX, toY;
12355         toX = moveList[target][2] - AAA;
12356         toY = moveList[target][3] - ONE;
12357         if (moveList[target][1] == '@') {
12358             if (appData.highlightLastMove) {
12359                 SetHighlights(-1, -1, toX, toY);
12360             }
12361         } else {
12362             fromX = moveList[target][0] - AAA;
12363             fromY = moveList[target][1] - ONE;
12364             if (target == currentMove - 1) {
12365                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12366             }
12367             if (appData.highlightLastMove) {
12368                 SetHighlights(fromX, fromY, toX, toY);
12369             }
12370         }
12371     }
12372     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12373         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12374         while (currentMove > target) {
12375             SendToProgram("undo\n", &first);
12376             currentMove--;
12377         }
12378     } else {
12379         currentMove = target;
12380     }
12381     
12382     if (gameMode == EditGame || gameMode == EndOfGame) {
12383         whiteTimeRemaining = timeRemaining[0][currentMove];
12384         blackTimeRemaining = timeRemaining[1][currentMove];
12385     }
12386     DisplayBothClocks();
12387     DisplayMove(currentMove - 1);
12388     DrawPosition(full_redraw, boards[currentMove]);
12389     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12390     // [HGM] PV info: routine tests if comment empty
12391     DisplayComment(currentMove - 1, commentList[currentMove]);
12392 }
12393
12394 void
12395 BackwardEvent()
12396 {
12397     if (gameMode == IcsExamining && !pausing) {
12398         SendToICS(ics_prefix);
12399         SendToICS("backward\n");
12400     } else {
12401         BackwardInner(currentMove - 1);
12402     }
12403 }
12404
12405 void
12406 ToStartEvent()
12407 {
12408     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12409         /* to optimize, we temporarily turn off analysis mode while we undo
12410          * all the moves. Otherwise we get analysis output after each undo.
12411          */ 
12412         if (first.analysisSupport) {
12413           SendToProgram("exit\nforce\n", &first);
12414           first.analyzing = FALSE;
12415         }
12416     }
12417
12418     if (gameMode == IcsExamining && !pausing) {
12419         SendToICS(ics_prefix);
12420         SendToICS("backward 999999\n");
12421     } else {
12422         BackwardInner(backwardMostMove);
12423     }
12424
12425     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12426         /* we have fed all the moves, so reactivate analysis mode */
12427         SendToProgram("analyze\n", &first);
12428         first.analyzing = TRUE;
12429         /*first.maybeThinking = TRUE;*/
12430         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12431     }
12432 }
12433
12434 void
12435 ToNrEvent(int to)
12436 {
12437   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12438   if (to >= forwardMostMove) to = forwardMostMove;
12439   if (to <= backwardMostMove) to = backwardMostMove;
12440   if (to < currentMove) {
12441     BackwardInner(to);
12442   } else {
12443     ForwardInner(to);
12444   }
12445 }
12446
12447 void
12448 RevertEvent()
12449 {
12450     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12451         return;
12452     }
12453     if (gameMode != IcsExamining) {
12454         DisplayError(_("You are not examining a game"), 0);
12455         return;
12456     }
12457     if (pausing) {
12458         DisplayError(_("You can't revert while pausing"), 0);
12459         return;
12460     }
12461     SendToICS(ics_prefix);
12462     SendToICS("revert\n");
12463 }
12464
12465 void
12466 RetractMoveEvent()
12467 {
12468     switch (gameMode) {
12469       case MachinePlaysWhite:
12470       case MachinePlaysBlack:
12471         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12472             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12473             return;
12474         }
12475         if (forwardMostMove < 2) return;
12476         currentMove = forwardMostMove = forwardMostMove - 2;
12477         whiteTimeRemaining = timeRemaining[0][currentMove];
12478         blackTimeRemaining = timeRemaining[1][currentMove];
12479         DisplayBothClocks();
12480         DisplayMove(currentMove - 1);
12481         ClearHighlights();/*!! could figure this out*/
12482         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12483         SendToProgram("remove\n", &first);
12484         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12485         break;
12486
12487       case BeginningOfGame:
12488       default:
12489         break;
12490
12491       case IcsPlayingWhite:
12492       case IcsPlayingBlack:
12493         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12494             SendToICS(ics_prefix);
12495             SendToICS("takeback 2\n");
12496         } else {
12497             SendToICS(ics_prefix);
12498             SendToICS("takeback 1\n");
12499         }
12500         break;
12501     }
12502 }
12503
12504 void
12505 MoveNowEvent()
12506 {
12507     ChessProgramState *cps;
12508
12509     switch (gameMode) {
12510       case MachinePlaysWhite:
12511         if (!WhiteOnMove(forwardMostMove)) {
12512             DisplayError(_("It is your turn"), 0);
12513             return;
12514         }
12515         cps = &first;
12516         break;
12517       case MachinePlaysBlack:
12518         if (WhiteOnMove(forwardMostMove)) {
12519             DisplayError(_("It is your turn"), 0);
12520             return;
12521         }
12522         cps = &first;
12523         break;
12524       case TwoMachinesPlay:
12525         if (WhiteOnMove(forwardMostMove) ==
12526             (first.twoMachinesColor[0] == 'w')) {
12527             cps = &first;
12528         } else {
12529             cps = &second;
12530         }
12531         break;
12532       case BeginningOfGame:
12533       default:
12534         return;
12535     }
12536     SendToProgram("?\n", cps);
12537 }
12538
12539 void
12540 TruncateGameEvent()
12541 {
12542     EditGameEvent();
12543     if (gameMode != EditGame) return;
12544     TruncateGame();
12545 }
12546
12547 void
12548 TruncateGame()
12549 {
12550     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12551     if (forwardMostMove > currentMove) {
12552         if (gameInfo.resultDetails != NULL) {
12553             free(gameInfo.resultDetails);
12554             gameInfo.resultDetails = NULL;
12555             gameInfo.result = GameUnfinished;
12556         }
12557         forwardMostMove = currentMove;
12558         HistorySet(parseList, backwardMostMove, forwardMostMove,
12559                    currentMove-1);
12560     }
12561 }
12562
12563 void
12564 HintEvent()
12565 {
12566     if (appData.noChessProgram) return;
12567     switch (gameMode) {
12568       case MachinePlaysWhite:
12569         if (WhiteOnMove(forwardMostMove)) {
12570             DisplayError(_("Wait until your turn"), 0);
12571             return;
12572         }
12573         break;
12574       case BeginningOfGame:
12575       case MachinePlaysBlack:
12576         if (!WhiteOnMove(forwardMostMove)) {
12577             DisplayError(_("Wait until your turn"), 0);
12578             return;
12579         }
12580         break;
12581       default:
12582         DisplayError(_("No hint available"), 0);
12583         return;
12584     }
12585     SendToProgram("hint\n", &first);
12586     hintRequested = TRUE;
12587 }
12588
12589 void
12590 BookEvent()
12591 {
12592     if (appData.noChessProgram) return;
12593     switch (gameMode) {
12594       case MachinePlaysWhite:
12595         if (WhiteOnMove(forwardMostMove)) {
12596             DisplayError(_("Wait until your turn"), 0);
12597             return;
12598         }
12599         break;
12600       case BeginningOfGame:
12601       case MachinePlaysBlack:
12602         if (!WhiteOnMove(forwardMostMove)) {
12603             DisplayError(_("Wait until your turn"), 0);
12604             return;
12605         }
12606         break;
12607       case EditPosition:
12608         EditPositionDone(TRUE);
12609         break;
12610       case TwoMachinesPlay:
12611         return;
12612       default:
12613         break;
12614     }
12615     SendToProgram("bk\n", &first);
12616     bookOutput[0] = NULLCHAR;
12617     bookRequested = TRUE;
12618 }
12619
12620 void
12621 AboutGameEvent()
12622 {
12623     char *tags = PGNTags(&gameInfo);
12624     TagsPopUp(tags, CmailMsg());
12625     free(tags);
12626 }
12627
12628 /* end button procedures */
12629
12630 void
12631 PrintPosition(fp, move)
12632      FILE *fp;
12633      int move;
12634 {
12635     int i, j;
12636     
12637     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12638         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12639             char c = PieceToChar(boards[move][i][j]);
12640             fputc(c == 'x' ? '.' : c, fp);
12641             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12642         }
12643     }
12644     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12645       fprintf(fp, "white to play\n");
12646     else
12647       fprintf(fp, "black to play\n");
12648 }
12649
12650 void
12651 PrintOpponents(fp)
12652      FILE *fp;
12653 {
12654     if (gameInfo.white != NULL) {
12655         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12656     } else {
12657         fprintf(fp, "\n");
12658     }
12659 }
12660
12661 /* Find last component of program's own name, using some heuristics */
12662 void
12663 TidyProgramName(prog, host, buf)
12664      char *prog, *host, buf[MSG_SIZ];
12665 {
12666     char *p, *q;
12667     int local = (strcmp(host, "localhost") == 0);
12668     while (!local && (p = strchr(prog, ';')) != NULL) {
12669         p++;
12670         while (*p == ' ') p++;
12671         prog = p;
12672     }
12673     if (*prog == '"' || *prog == '\'') {
12674         q = strchr(prog + 1, *prog);
12675     } else {
12676         q = strchr(prog, ' ');
12677     }
12678     if (q == NULL) q = prog + strlen(prog);
12679     p = q;
12680     while (p >= prog && *p != '/' && *p != '\\') p--;
12681     p++;
12682     if(p == prog && *p == '"') p++;
12683     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12684     memcpy(buf, p, q - p);
12685     buf[q - p] = NULLCHAR;
12686     if (!local) {
12687         strcat(buf, "@");
12688         strcat(buf, host);
12689     }
12690 }
12691
12692 char *
12693 TimeControlTagValue()
12694 {
12695     char buf[MSG_SIZ];
12696     if (!appData.clockMode) {
12697         strcpy(buf, "-");
12698     } else if (movesPerSession > 0) {
12699         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12700     } else if (timeIncrement == 0) {
12701         sprintf(buf, "%ld", timeControl/1000);
12702     } else {
12703         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12704     }
12705     return StrSave(buf);
12706 }
12707
12708 void
12709 SetGameInfo()
12710 {
12711     /* This routine is used only for certain modes */
12712     VariantClass v = gameInfo.variant;
12713     ChessMove r = GameUnfinished;
12714     char *p = NULL;
12715
12716     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12717         r = gameInfo.result; 
12718         p = gameInfo.resultDetails; 
12719         gameInfo.resultDetails = NULL;
12720     }
12721     ClearGameInfo(&gameInfo);
12722     gameInfo.variant = v;
12723
12724     switch (gameMode) {
12725       case MachinePlaysWhite:
12726         gameInfo.event = StrSave( appData.pgnEventHeader );
12727         gameInfo.site = StrSave(HostName());
12728         gameInfo.date = PGNDate();
12729         gameInfo.round = StrSave("-");
12730         gameInfo.white = StrSave(first.tidy);
12731         gameInfo.black = StrSave(UserName());
12732         gameInfo.timeControl = TimeControlTagValue();
12733         break;
12734
12735       case MachinePlaysBlack:
12736         gameInfo.event = StrSave( appData.pgnEventHeader );
12737         gameInfo.site = StrSave(HostName());
12738         gameInfo.date = PGNDate();
12739         gameInfo.round = StrSave("-");
12740         gameInfo.white = StrSave(UserName());
12741         gameInfo.black = StrSave(first.tidy);
12742         gameInfo.timeControl = TimeControlTagValue();
12743         break;
12744
12745       case TwoMachinesPlay:
12746         gameInfo.event = StrSave( appData.pgnEventHeader );
12747         gameInfo.site = StrSave(HostName());
12748         gameInfo.date = PGNDate();
12749         if (matchGame > 0) {
12750             char buf[MSG_SIZ];
12751             sprintf(buf, "%d", matchGame);
12752             gameInfo.round = StrSave(buf);
12753         } else {
12754             gameInfo.round = StrSave("-");
12755         }
12756         if (first.twoMachinesColor[0] == 'w') {
12757             gameInfo.white = StrSave(first.tidy);
12758             gameInfo.black = StrSave(second.tidy);
12759         } else {
12760             gameInfo.white = StrSave(second.tidy);
12761             gameInfo.black = StrSave(first.tidy);
12762         }
12763         gameInfo.timeControl = TimeControlTagValue();
12764         break;
12765
12766       case EditGame:
12767         gameInfo.event = StrSave("Edited game");
12768         gameInfo.site = StrSave(HostName());
12769         gameInfo.date = PGNDate();
12770         gameInfo.round = StrSave("-");
12771         gameInfo.white = StrSave("-");
12772         gameInfo.black = StrSave("-");
12773         gameInfo.result = r;
12774         gameInfo.resultDetails = p;
12775         break;
12776
12777       case EditPosition:
12778         gameInfo.event = StrSave("Edited position");
12779         gameInfo.site = StrSave(HostName());
12780         gameInfo.date = PGNDate();
12781         gameInfo.round = StrSave("-");
12782         gameInfo.white = StrSave("-");
12783         gameInfo.black = StrSave("-");
12784         break;
12785
12786       case IcsPlayingWhite:
12787       case IcsPlayingBlack:
12788       case IcsObserving:
12789       case IcsExamining:
12790         break;
12791
12792       case PlayFromGameFile:
12793         gameInfo.event = StrSave("Game from non-PGN file");
12794         gameInfo.site = StrSave(HostName());
12795         gameInfo.date = PGNDate();
12796         gameInfo.round = StrSave("-");
12797         gameInfo.white = StrSave("?");
12798         gameInfo.black = StrSave("?");
12799         break;
12800
12801       default:
12802         break;
12803     }
12804 }
12805
12806 void
12807 ReplaceComment(index, text)
12808      int index;
12809      char *text;
12810 {
12811     int len;
12812
12813     while (*text == '\n') text++;
12814     len = strlen(text);
12815     while (len > 0 && text[len - 1] == '\n') len--;
12816
12817     if (commentList[index] != NULL)
12818       free(commentList[index]);
12819
12820     if (len == 0) {
12821         commentList[index] = NULL;
12822         return;
12823     }
12824   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12825       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12826       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12827     commentList[index] = (char *) malloc(len + 2);
12828     strncpy(commentList[index], text, len);
12829     commentList[index][len] = '\n';
12830     commentList[index][len + 1] = NULLCHAR;
12831   } else { 
12832     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12833     char *p;
12834     commentList[index] = (char *) malloc(len + 6);
12835     strcpy(commentList[index], "{\n");
12836     strncpy(commentList[index]+2, text, len);
12837     commentList[index][len+2] = NULLCHAR;
12838     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12839     strcat(commentList[index], "\n}\n");
12840   }
12841 }
12842
12843 void
12844 CrushCRs(text)
12845      char *text;
12846 {
12847   char *p = text;
12848   char *q = text;
12849   char ch;
12850
12851   do {
12852     ch = *p++;
12853     if (ch == '\r') continue;
12854     *q++ = ch;
12855   } while (ch != '\0');
12856 }
12857
12858 void
12859 AppendComment(index, text, addBraces)
12860      int index;
12861      char *text;
12862      Boolean addBraces; // [HGM] braces: tells if we should add {}
12863 {
12864     int oldlen, len;
12865     char *old;
12866
12867 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12868     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12869
12870     CrushCRs(text);
12871     while (*text == '\n') text++;
12872     len = strlen(text);
12873     while (len > 0 && text[len - 1] == '\n') len--;
12874
12875     if (len == 0) return;
12876
12877     if (commentList[index] != NULL) {
12878         old = commentList[index];
12879         oldlen = strlen(old);
12880         while(commentList[index][oldlen-1] ==  '\n')
12881           commentList[index][--oldlen] = NULLCHAR;
12882         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12883         strcpy(commentList[index], old);
12884         free(old);
12885         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12886         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12887           if(addBraces) addBraces = FALSE; else { text++; len--; }
12888           while (*text == '\n') { text++; len--; }
12889           commentList[index][--oldlen] = NULLCHAR;
12890       }
12891         if(addBraces) strcat(commentList[index], "\n{\n");
12892         else          strcat(commentList[index], "\n");
12893         strcat(commentList[index], text);
12894         if(addBraces) strcat(commentList[index], "\n}\n");
12895         else          strcat(commentList[index], "\n");
12896     } else {
12897         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12898         if(addBraces)
12899              strcpy(commentList[index], "{\n");
12900         else commentList[index][0] = NULLCHAR;
12901         strcat(commentList[index], text);
12902         strcat(commentList[index], "\n");
12903         if(addBraces) strcat(commentList[index], "}\n");
12904     }
12905 }
12906
12907 static char * FindStr( char * text, char * sub_text )
12908 {
12909     char * result = strstr( text, sub_text );
12910
12911     if( result != NULL ) {
12912         result += strlen( sub_text );
12913     }
12914
12915     return result;
12916 }
12917
12918 /* [AS] Try to extract PV info from PGN comment */
12919 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12920 char *GetInfoFromComment( int index, char * text )
12921 {
12922     char * sep = text;
12923
12924     if( text != NULL && index > 0 ) {
12925         int score = 0;
12926         int depth = 0;
12927         int time = -1, sec = 0, deci;
12928         char * s_eval = FindStr( text, "[%eval " );
12929         char * s_emt = FindStr( text, "[%emt " );
12930
12931         if( s_eval != NULL || s_emt != NULL ) {
12932             /* New style */
12933             char delim;
12934
12935             if( s_eval != NULL ) {
12936                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12937                     return text;
12938                 }
12939
12940                 if( delim != ']' ) {
12941                     return text;
12942                 }
12943             }
12944
12945             if( s_emt != NULL ) {
12946             }
12947                 return text;
12948         }
12949         else {
12950             /* We expect something like: [+|-]nnn.nn/dd */
12951             int score_lo = 0;
12952
12953             if(*text != '{') return text; // [HGM] braces: must be normal comment
12954
12955             sep = strchr( text, '/' );
12956             if( sep == NULL || sep < (text+4) ) {
12957                 return text;
12958             }
12959
12960             time = -1; sec = -1; deci = -1;
12961             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12962                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12963                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12964                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12965                 return text;
12966             }
12967
12968             if( score_lo < 0 || score_lo >= 100 ) {
12969                 return text;
12970             }
12971
12972             if(sec >= 0) time = 600*time + 10*sec; else
12973             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12974
12975             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12976
12977             /* [HGM] PV time: now locate end of PV info */
12978             while( *++sep >= '0' && *sep <= '9'); // strip depth
12979             if(time >= 0)
12980             while( *++sep >= '0' && *sep <= '9'); // strip time
12981             if(sec >= 0)
12982             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12983             if(deci >= 0)
12984             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12985             while(*sep == ' ') sep++;
12986         }
12987
12988         if( depth <= 0 ) {
12989             return text;
12990         }
12991
12992         if( time < 0 ) {
12993             time = -1;
12994         }
12995
12996         pvInfoList[index-1].depth = depth;
12997         pvInfoList[index-1].score = score;
12998         pvInfoList[index-1].time  = 10*time; // centi-sec
12999         if(*sep == '}') *sep = 0; else *--sep = '{';
13000     }
13001     return sep;
13002 }
13003
13004 void
13005 SendToProgram(message, cps)
13006      char *message;
13007      ChessProgramState *cps;
13008 {
13009     int count, outCount, error;
13010     char buf[MSG_SIZ];
13011
13012     if (cps->pr == NULL) return;
13013     Attention(cps);
13014     
13015     if (appData.debugMode) {
13016         TimeMark now;
13017         GetTimeMark(&now);
13018         fprintf(debugFP, "%ld >%-6s: %s", 
13019                 SubtractTimeMarks(&now, &programStartTime),
13020                 cps->which, message);
13021     }
13022     
13023     count = strlen(message);
13024     outCount = OutputToProcess(cps->pr, message, count, &error);
13025     if (outCount < count && !exiting 
13026                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13027         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13028         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13029             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13030                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13031                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13032             } else {
13033                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13034             }
13035             gameInfo.resultDetails = StrSave(buf);
13036         }
13037         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13038     }
13039 }
13040
13041 void
13042 ReceiveFromProgram(isr, closure, message, count, error)
13043      InputSourceRef isr;
13044      VOIDSTAR closure;
13045      char *message;
13046      int count;
13047      int error;
13048 {
13049     char *end_str;
13050     char buf[MSG_SIZ];
13051     ChessProgramState *cps = (ChessProgramState *)closure;
13052
13053     if (isr != cps->isr) return; /* Killed intentionally */
13054     if (count <= 0) {
13055         if (count == 0) {
13056             sprintf(buf,
13057                     _("Error: %s chess program (%s) exited unexpectedly"),
13058                     cps->which, cps->program);
13059         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13060                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13061                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13062                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13063                 } else {
13064                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13065                 }
13066                 gameInfo.resultDetails = StrSave(buf);
13067             }
13068             RemoveInputSource(cps->isr);
13069             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13070         } else {
13071             sprintf(buf,
13072                     _("Error reading from %s chess program (%s)"),
13073                     cps->which, cps->program);
13074             RemoveInputSource(cps->isr);
13075
13076             /* [AS] Program is misbehaving badly... kill it */
13077             if( count == -2 ) {
13078                 DestroyChildProcess( cps->pr, 9 );
13079                 cps->pr = NoProc;
13080             }
13081
13082             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13083         }
13084         return;
13085     }
13086     
13087     if ((end_str = strchr(message, '\r')) != NULL)
13088       *end_str = NULLCHAR;
13089     if ((end_str = strchr(message, '\n')) != NULL)
13090       *end_str = NULLCHAR;
13091     
13092     if (appData.debugMode) {
13093         TimeMark now; int print = 1;
13094         char *quote = ""; char c; int i;
13095
13096         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13097                 char start = message[0];
13098                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13099                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13100                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13101                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13102                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13103                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13104                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13105                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13106                         { quote = "# "; print = (appData.engineComments == 2); }
13107                 message[0] = start; // restore original message
13108         }
13109         if(print) {
13110                 GetTimeMark(&now);
13111                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13112                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13113                         quote,
13114                         message);
13115         }
13116     }
13117
13118     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13119     if (appData.icsEngineAnalyze) {
13120         if (strstr(message, "whisper") != NULL ||
13121              strstr(message, "kibitz") != NULL || 
13122             strstr(message, "tellics") != NULL) return;
13123     }
13124
13125     HandleMachineMove(message, cps);
13126 }
13127
13128
13129 void
13130 SendTimeControl(cps, mps, tc, inc, sd, st)
13131      ChessProgramState *cps;
13132      int mps, inc, sd, st;
13133      long tc;
13134 {
13135     char buf[MSG_SIZ];
13136     int seconds;
13137
13138     if( timeControl_2 > 0 ) {
13139         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13140             tc = timeControl_2;
13141         }
13142     }
13143     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13144     inc /= cps->timeOdds;
13145     st  /= cps->timeOdds;
13146
13147     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13148
13149     if (st > 0) {
13150       /* Set exact time per move, normally using st command */
13151       if (cps->stKludge) {
13152         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13153         seconds = st % 60;
13154         if (seconds == 0) {
13155           sprintf(buf, "level 1 %d\n", st/60);
13156         } else {
13157           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13158         }
13159       } else {
13160         sprintf(buf, "st %d\n", st);
13161       }
13162     } else {
13163       /* Set conventional or incremental time control, using level command */
13164       if (seconds == 0) {
13165         /* Note old gnuchess bug -- minutes:seconds used to not work.
13166            Fixed in later versions, but still avoid :seconds
13167            when seconds is 0. */
13168         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13169       } else {
13170         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13171                 seconds, inc/1000);
13172       }
13173     }
13174     SendToProgram(buf, cps);
13175
13176     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13177     /* Orthogonally, limit search to given depth */
13178     if (sd > 0) {
13179       if (cps->sdKludge) {
13180         sprintf(buf, "depth\n%d\n", sd);
13181       } else {
13182         sprintf(buf, "sd %d\n", sd);
13183       }
13184       SendToProgram(buf, cps);
13185     }
13186
13187     if(cps->nps > 0) { /* [HGM] nps */
13188         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13189         else {
13190                 sprintf(buf, "nps %d\n", cps->nps);
13191               SendToProgram(buf, cps);
13192         }
13193     }
13194 }
13195
13196 ChessProgramState *WhitePlayer()
13197 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13198 {
13199     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13200        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13201         return &second;
13202     return &first;
13203 }
13204
13205 void
13206 SendTimeRemaining(cps, machineWhite)
13207      ChessProgramState *cps;
13208      int /*boolean*/ machineWhite;
13209 {
13210     char message[MSG_SIZ];
13211     long time, otime;
13212
13213     /* Note: this routine must be called when the clocks are stopped
13214        or when they have *just* been set or switched; otherwise
13215        it will be off by the time since the current tick started.
13216     */
13217     if (machineWhite) {
13218         time = whiteTimeRemaining / 10;
13219         otime = blackTimeRemaining / 10;
13220     } else {
13221         time = blackTimeRemaining / 10;
13222         otime = whiteTimeRemaining / 10;
13223     }
13224     /* [HGM] translate opponent's time by time-odds factor */
13225     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13226     if (appData.debugMode) {
13227         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13228     }
13229
13230     if (time <= 0) time = 1;
13231     if (otime <= 0) otime = 1;
13232     
13233     sprintf(message, "time %ld\n", time);
13234     SendToProgram(message, cps);
13235
13236     sprintf(message, "otim %ld\n", otime);
13237     SendToProgram(message, cps);
13238 }
13239
13240 int
13241 BoolFeature(p, name, loc, cps)
13242      char **p;
13243      char *name;
13244      int *loc;
13245      ChessProgramState *cps;
13246 {
13247   char buf[MSG_SIZ];
13248   int len = strlen(name);
13249   int val;
13250   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13251     (*p) += len + 1;
13252     sscanf(*p, "%d", &val);
13253     *loc = (val != 0);
13254     while (**p && **p != ' ') (*p)++;
13255     sprintf(buf, "accepted %s\n", name);
13256     SendToProgram(buf, cps);
13257     return TRUE;
13258   }
13259   return FALSE;
13260 }
13261
13262 int
13263 IntFeature(p, name, loc, cps)
13264      char **p;
13265      char *name;
13266      int *loc;
13267      ChessProgramState *cps;
13268 {
13269   char buf[MSG_SIZ];
13270   int len = strlen(name);
13271   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13272     (*p) += len + 1;
13273     sscanf(*p, "%d", loc);
13274     while (**p && **p != ' ') (*p)++;
13275     sprintf(buf, "accepted %s\n", name);
13276     SendToProgram(buf, cps);
13277     return TRUE;
13278   }
13279   return FALSE;
13280 }
13281
13282 int
13283 StringFeature(p, name, loc, cps)
13284      char **p;
13285      char *name;
13286      char loc[];
13287      ChessProgramState *cps;
13288 {
13289   char buf[MSG_SIZ];
13290   int len = strlen(name);
13291   if (strncmp((*p), name, len) == 0
13292       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13293     (*p) += len + 2;
13294     sscanf(*p, "%[^\"]", loc);
13295     while (**p && **p != '\"') (*p)++;
13296     if (**p == '\"') (*p)++;
13297     sprintf(buf, "accepted %s\n", name);
13298     SendToProgram(buf, cps);
13299     return TRUE;
13300   }
13301   return FALSE;
13302 }
13303
13304 int 
13305 ParseOption(Option *opt, ChessProgramState *cps)
13306 // [HGM] options: process the string that defines an engine option, and determine
13307 // name, type, default value, and allowed value range
13308 {
13309         char *p, *q, buf[MSG_SIZ];
13310         int n, min = (-1)<<31, max = 1<<31, def;
13311
13312         if(p = strstr(opt->name, " -spin ")) {
13313             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13314             if(max < min) max = min; // enforce consistency
13315             if(def < min) def = min;
13316             if(def > max) def = max;
13317             opt->value = def;
13318             opt->min = min;
13319             opt->max = max;
13320             opt->type = Spin;
13321         } else if((p = strstr(opt->name, " -slider "))) {
13322             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13323             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13324             if(max < min) max = min; // enforce consistency
13325             if(def < min) def = min;
13326             if(def > max) def = max;
13327             opt->value = def;
13328             opt->min = min;
13329             opt->max = max;
13330             opt->type = Spin; // Slider;
13331         } else if((p = strstr(opt->name, " -string "))) {
13332             opt->textValue = p+9;
13333             opt->type = TextBox;
13334         } else if((p = strstr(opt->name, " -file "))) {
13335             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13336             opt->textValue = p+7;
13337             opt->type = TextBox; // FileName;
13338         } else if((p = strstr(opt->name, " -path "))) {
13339             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13340             opt->textValue = p+7;
13341             opt->type = TextBox; // PathName;
13342         } else if(p = strstr(opt->name, " -check ")) {
13343             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13344             opt->value = (def != 0);
13345             opt->type = CheckBox;
13346         } else if(p = strstr(opt->name, " -combo ")) {
13347             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13348             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13349             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13350             opt->value = n = 0;
13351             while(q = StrStr(q, " /// ")) {
13352                 n++; *q = 0;    // count choices, and null-terminate each of them
13353                 q += 5;
13354                 if(*q == '*') { // remember default, which is marked with * prefix
13355                     q++;
13356                     opt->value = n;
13357                 }
13358                 cps->comboList[cps->comboCnt++] = q;
13359             }
13360             cps->comboList[cps->comboCnt++] = NULL;
13361             opt->max = n + 1;
13362             opt->type = ComboBox;
13363         } else if(p = strstr(opt->name, " -button")) {
13364             opt->type = Button;
13365         } else if(p = strstr(opt->name, " -save")) {
13366             opt->type = SaveButton;
13367         } else return FALSE;
13368         *p = 0; // terminate option name
13369         // now look if the command-line options define a setting for this engine option.
13370         if(cps->optionSettings && cps->optionSettings[0])
13371             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13372         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13373                 sprintf(buf, "option %s", p);
13374                 if(p = strstr(buf, ",")) *p = 0;
13375                 strcat(buf, "\n");
13376                 SendToProgram(buf, cps);
13377         }
13378         return TRUE;
13379 }
13380
13381 void
13382 FeatureDone(cps, val)
13383      ChessProgramState* cps;
13384      int val;
13385 {
13386   DelayedEventCallback cb = GetDelayedEvent();
13387   if ((cb == InitBackEnd3 && cps == &first) ||
13388       (cb == TwoMachinesEventIfReady && cps == &second)) {
13389     CancelDelayedEvent();
13390     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13391   }
13392   cps->initDone = val;
13393 }
13394
13395 /* Parse feature command from engine */
13396 void
13397 ParseFeatures(args, cps)
13398      char* args;
13399      ChessProgramState *cps;  
13400 {
13401   char *p = args;
13402   char *q;
13403   int val;
13404   char buf[MSG_SIZ];
13405
13406   for (;;) {
13407     while (*p == ' ') p++;
13408     if (*p == NULLCHAR) return;
13409
13410     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13411     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13412     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13413     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13414     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13415     if (BoolFeature(&p, "reuse", &val, cps)) {
13416       /* Engine can disable reuse, but can't enable it if user said no */
13417       if (!val) cps->reuse = FALSE;
13418       continue;
13419     }
13420     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13421     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13422       if (gameMode == TwoMachinesPlay) {
13423         DisplayTwoMachinesTitle();
13424       } else {
13425         DisplayTitle("");
13426       }
13427       continue;
13428     }
13429     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13430     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13431     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13432     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13433     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13434     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13435     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13436     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13437     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13438     if (IntFeature(&p, "done", &val, cps)) {
13439       FeatureDone(cps, val);
13440       continue;
13441     }
13442     /* Added by Tord: */
13443     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13444     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13445     /* End of additions by Tord */
13446
13447     /* [HGM] added features: */
13448     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13449     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13450     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13451     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13452     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13453     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13454     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13455         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13456             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13457             SendToProgram(buf, cps);
13458             continue;
13459         }
13460         if(cps->nrOptions >= MAX_OPTIONS) {
13461             cps->nrOptions--;
13462             sprintf(buf, "%s engine has too many options\n", cps->which);
13463             DisplayError(buf, 0);
13464         }
13465         continue;
13466     }
13467     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13468     /* End of additions by HGM */
13469
13470     /* unknown feature: complain and skip */
13471     q = p;
13472     while (*q && *q != '=') q++;
13473     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13474     SendToProgram(buf, cps);
13475     p = q;
13476     if (*p == '=') {
13477       p++;
13478       if (*p == '\"') {
13479         p++;
13480         while (*p && *p != '\"') p++;
13481         if (*p == '\"') p++;
13482       } else {
13483         while (*p && *p != ' ') p++;
13484       }
13485     }
13486   }
13487
13488 }
13489
13490 void
13491 PeriodicUpdatesEvent(newState)
13492      int newState;
13493 {
13494     if (newState == appData.periodicUpdates)
13495       return;
13496
13497     appData.periodicUpdates=newState;
13498
13499     /* Display type changes, so update it now */
13500 //    DisplayAnalysis();
13501
13502     /* Get the ball rolling again... */
13503     if (newState) {
13504         AnalysisPeriodicEvent(1);
13505         StartAnalysisClock();
13506     }
13507 }
13508
13509 void
13510 PonderNextMoveEvent(newState)
13511      int newState;
13512 {
13513     if (newState == appData.ponderNextMove) return;
13514     if (gameMode == EditPosition) EditPositionDone(TRUE);
13515     if (newState) {
13516         SendToProgram("hard\n", &first);
13517         if (gameMode == TwoMachinesPlay) {
13518             SendToProgram("hard\n", &second);
13519         }
13520     } else {
13521         SendToProgram("easy\n", &first);
13522         thinkOutput[0] = NULLCHAR;
13523         if (gameMode == TwoMachinesPlay) {
13524             SendToProgram("easy\n", &second);
13525         }
13526     }
13527     appData.ponderNextMove = newState;
13528 }
13529
13530 void
13531 NewSettingEvent(option, command, value)
13532      char *command;
13533      int option, value;
13534 {
13535     char buf[MSG_SIZ];
13536
13537     if (gameMode == EditPosition) EditPositionDone(TRUE);
13538     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13539     SendToProgram(buf, &first);
13540     if (gameMode == TwoMachinesPlay) {
13541         SendToProgram(buf, &second);
13542     }
13543 }
13544
13545 void
13546 ShowThinkingEvent()
13547 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13548 {
13549     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13550     int newState = appData.showThinking
13551         // [HGM] thinking: other features now need thinking output as well
13552         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13553     
13554     if (oldState == newState) return;
13555     oldState = newState;
13556     if (gameMode == EditPosition) EditPositionDone(TRUE);
13557     if (oldState) {
13558         SendToProgram("post\n", &first);
13559         if (gameMode == TwoMachinesPlay) {
13560             SendToProgram("post\n", &second);
13561         }
13562     } else {
13563         SendToProgram("nopost\n", &first);
13564         thinkOutput[0] = NULLCHAR;
13565         if (gameMode == TwoMachinesPlay) {
13566             SendToProgram("nopost\n", &second);
13567         }
13568     }
13569 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13570 }
13571
13572 void
13573 AskQuestionEvent(title, question, replyPrefix, which)
13574      char *title; char *question; char *replyPrefix; char *which;
13575 {
13576   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13577   if (pr == NoProc) return;
13578   AskQuestion(title, question, replyPrefix, pr);
13579 }
13580
13581 void
13582 DisplayMove(moveNumber)
13583      int moveNumber;
13584 {
13585     char message[MSG_SIZ];
13586     char res[MSG_SIZ];
13587     char cpThinkOutput[MSG_SIZ];
13588
13589     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13590     
13591     if (moveNumber == forwardMostMove - 1 || 
13592         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13593
13594         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13595
13596         if (strchr(cpThinkOutput, '\n')) {
13597             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13598         }
13599     } else {
13600         *cpThinkOutput = NULLCHAR;
13601     }
13602
13603     /* [AS] Hide thinking from human user */
13604     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13605         *cpThinkOutput = NULLCHAR;
13606         if( thinkOutput[0] != NULLCHAR ) {
13607             int i;
13608
13609             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13610                 cpThinkOutput[i] = '.';
13611             }
13612             cpThinkOutput[i] = NULLCHAR;
13613             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13614         }
13615     }
13616
13617     if (moveNumber == forwardMostMove - 1 &&
13618         gameInfo.resultDetails != NULL) {
13619         if (gameInfo.resultDetails[0] == NULLCHAR) {
13620             sprintf(res, " %s", PGNResult(gameInfo.result));
13621         } else {
13622             sprintf(res, " {%s} %s",
13623                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13624         }
13625     } else {
13626         res[0] = NULLCHAR;
13627     }
13628
13629     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13630         DisplayMessage(res, cpThinkOutput);
13631     } else {
13632         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13633                 WhiteOnMove(moveNumber) ? " " : ".. ",
13634                 parseList[moveNumber], res);
13635         DisplayMessage(message, cpThinkOutput);
13636     }
13637 }
13638
13639 void
13640 DisplayComment(moveNumber, text)
13641      int moveNumber;
13642      char *text;
13643 {
13644     char title[MSG_SIZ];
13645     char buf[8000]; // comment can be long!
13646     int score, depth;
13647     
13648     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13649       strcpy(title, "Comment");
13650     } else {
13651       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13652               WhiteOnMove(moveNumber) ? " " : ".. ",
13653               parseList[moveNumber]);
13654     }
13655     // [HGM] PV info: display PV info together with (or as) comment
13656     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13657       if(text == NULL) text = "";                                           
13658       score = pvInfoList[moveNumber].score;
13659       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13660               depth, (pvInfoList[moveNumber].time+50)/100, text);
13661       text = buf;
13662     }
13663     if (text != NULL && (appData.autoDisplayComment || commentUp))
13664         CommentPopUp(title, text);
13665 }
13666
13667 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13668  * might be busy thinking or pondering.  It can be omitted if your
13669  * gnuchess is configured to stop thinking immediately on any user
13670  * input.  However, that gnuchess feature depends on the FIONREAD
13671  * ioctl, which does not work properly on some flavors of Unix.
13672  */
13673 void
13674 Attention(cps)
13675      ChessProgramState *cps;
13676 {
13677 #if ATTENTION
13678     if (!cps->useSigint) return;
13679     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13680     switch (gameMode) {
13681       case MachinePlaysWhite:
13682       case MachinePlaysBlack:
13683       case TwoMachinesPlay:
13684       case IcsPlayingWhite:
13685       case IcsPlayingBlack:
13686       case AnalyzeMode:
13687       case AnalyzeFile:
13688         /* Skip if we know it isn't thinking */
13689         if (!cps->maybeThinking) return;
13690         if (appData.debugMode)
13691           fprintf(debugFP, "Interrupting %s\n", cps->which);
13692         InterruptChildProcess(cps->pr);
13693         cps->maybeThinking = FALSE;
13694         break;
13695       default:
13696         break;
13697     }
13698 #endif /*ATTENTION*/
13699 }
13700
13701 int
13702 CheckFlags()
13703 {
13704     if (whiteTimeRemaining <= 0) {
13705         if (!whiteFlag) {
13706             whiteFlag = TRUE;
13707             if (appData.icsActive) {
13708                 if (appData.autoCallFlag &&
13709                     gameMode == IcsPlayingBlack && !blackFlag) {
13710                   SendToICS(ics_prefix);
13711                   SendToICS("flag\n");
13712                 }
13713             } else {
13714                 if (blackFlag) {
13715                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13716                 } else {
13717                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13718                     if (appData.autoCallFlag) {
13719                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13720                         return TRUE;
13721                     }
13722                 }
13723             }
13724         }
13725     }
13726     if (blackTimeRemaining <= 0) {
13727         if (!blackFlag) {
13728             blackFlag = TRUE;
13729             if (appData.icsActive) {
13730                 if (appData.autoCallFlag &&
13731                     gameMode == IcsPlayingWhite && !whiteFlag) {
13732                   SendToICS(ics_prefix);
13733                   SendToICS("flag\n");
13734                 }
13735             } else {
13736                 if (whiteFlag) {
13737                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13738                 } else {
13739                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13740                     if (appData.autoCallFlag) {
13741                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13742                         return TRUE;
13743                     }
13744                 }
13745             }
13746         }
13747     }
13748     return FALSE;
13749 }
13750
13751 void
13752 CheckTimeControl()
13753 {
13754     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13755         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13756
13757     /*
13758      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13759      */
13760     if ( !WhiteOnMove(forwardMostMove) )
13761         /* White made time control */
13762         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13763         /* [HGM] time odds: correct new time quota for time odds! */
13764                                             / WhitePlayer()->timeOdds;
13765       else
13766         /* Black made time control */
13767         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13768                                             / WhitePlayer()->other->timeOdds;
13769 }
13770
13771 void
13772 DisplayBothClocks()
13773 {
13774     int wom = gameMode == EditPosition ?
13775       !blackPlaysFirst : WhiteOnMove(currentMove);
13776     DisplayWhiteClock(whiteTimeRemaining, wom);
13777     DisplayBlackClock(blackTimeRemaining, !wom);
13778 }
13779
13780
13781 /* Timekeeping seems to be a portability nightmare.  I think everyone
13782    has ftime(), but I'm really not sure, so I'm including some ifdefs
13783    to use other calls if you don't.  Clocks will be less accurate if
13784    you have neither ftime nor gettimeofday.
13785 */
13786
13787 /* VS 2008 requires the #include outside of the function */
13788 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13789 #include <sys/timeb.h>
13790 #endif
13791
13792 /* Get the current time as a TimeMark */
13793 void
13794 GetTimeMark(tm)
13795      TimeMark *tm;
13796 {
13797 #if HAVE_GETTIMEOFDAY
13798
13799     struct timeval timeVal;
13800     struct timezone timeZone;
13801
13802     gettimeofday(&timeVal, &timeZone);
13803     tm->sec = (long) timeVal.tv_sec; 
13804     tm->ms = (int) (timeVal.tv_usec / 1000L);
13805
13806 #else /*!HAVE_GETTIMEOFDAY*/
13807 #if HAVE_FTIME
13808
13809 // include <sys/timeb.h> / moved to just above start of function
13810     struct timeb timeB;
13811
13812     ftime(&timeB);
13813     tm->sec = (long) timeB.time;
13814     tm->ms = (int) timeB.millitm;
13815
13816 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13817     tm->sec = (long) time(NULL);
13818     tm->ms = 0;
13819 #endif
13820 #endif
13821 }
13822
13823 /* Return the difference in milliseconds between two
13824    time marks.  We assume the difference will fit in a long!
13825 */
13826 long
13827 SubtractTimeMarks(tm2, tm1)
13828      TimeMark *tm2, *tm1;
13829 {
13830     return 1000L*(tm2->sec - tm1->sec) +
13831            (long) (tm2->ms - tm1->ms);
13832 }
13833
13834
13835 /*
13836  * Code to manage the game clocks.
13837  *
13838  * In tournament play, black starts the clock and then white makes a move.
13839  * We give the human user a slight advantage if he is playing white---the
13840  * clocks don't run until he makes his first move, so it takes zero time.
13841  * Also, we don't account for network lag, so we could get out of sync
13842  * with GNU Chess's clock -- but then, referees are always right.  
13843  */
13844
13845 static TimeMark tickStartTM;
13846 static long intendedTickLength;
13847
13848 long
13849 NextTickLength(timeRemaining)
13850      long timeRemaining;
13851 {
13852     long nominalTickLength, nextTickLength;
13853
13854     if (timeRemaining > 0L && timeRemaining <= 10000L)
13855       nominalTickLength = 100L;
13856     else
13857       nominalTickLength = 1000L;
13858     nextTickLength = timeRemaining % nominalTickLength;
13859     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13860
13861     return nextTickLength;
13862 }
13863
13864 /* Adjust clock one minute up or down */
13865 void
13866 AdjustClock(Boolean which, int dir)
13867 {
13868     if(which) blackTimeRemaining += 60000*dir;
13869     else      whiteTimeRemaining += 60000*dir;
13870     DisplayBothClocks();
13871 }
13872
13873 /* Stop clocks and reset to a fresh time control */
13874 void
13875 ResetClocks() 
13876 {
13877     (void) StopClockTimer();
13878     if (appData.icsActive) {
13879         whiteTimeRemaining = blackTimeRemaining = 0;
13880     } else if (searchTime) {
13881         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13882         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13883     } else { /* [HGM] correct new time quote for time odds */
13884         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13885         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13886     }
13887     if (whiteFlag || blackFlag) {
13888         DisplayTitle("");
13889         whiteFlag = blackFlag = FALSE;
13890     }
13891     DisplayBothClocks();
13892 }
13893
13894 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13895
13896 /* Decrement running clock by amount of time that has passed */
13897 void
13898 DecrementClocks()
13899 {
13900     long timeRemaining;
13901     long lastTickLength, fudge;
13902     TimeMark now;
13903
13904     if (!appData.clockMode) return;
13905     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13906         
13907     GetTimeMark(&now);
13908
13909     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13910
13911     /* Fudge if we woke up a little too soon */
13912     fudge = intendedTickLength - lastTickLength;
13913     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13914
13915     if (WhiteOnMove(forwardMostMove)) {
13916         if(whiteNPS >= 0) lastTickLength = 0;
13917         timeRemaining = whiteTimeRemaining -= lastTickLength;
13918         DisplayWhiteClock(whiteTimeRemaining - fudge,
13919                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13920     } else {
13921         if(blackNPS >= 0) lastTickLength = 0;
13922         timeRemaining = blackTimeRemaining -= lastTickLength;
13923         DisplayBlackClock(blackTimeRemaining - fudge,
13924                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13925     }
13926
13927     if (CheckFlags()) return;
13928         
13929     tickStartTM = now;
13930     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13931     StartClockTimer(intendedTickLength);
13932
13933     /* if the time remaining has fallen below the alarm threshold, sound the
13934      * alarm. if the alarm has sounded and (due to a takeback or time control
13935      * with increment) the time remaining has increased to a level above the
13936      * threshold, reset the alarm so it can sound again. 
13937      */
13938     
13939     if (appData.icsActive && appData.icsAlarm) {
13940
13941         /* make sure we are dealing with the user's clock */
13942         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13943                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13944            )) return;
13945
13946         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13947             alarmSounded = FALSE;
13948         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13949             PlayAlarmSound();
13950             alarmSounded = TRUE;
13951         }
13952     }
13953 }
13954
13955
13956 /* A player has just moved, so stop the previously running
13957    clock and (if in clock mode) start the other one.
13958    We redisplay both clocks in case we're in ICS mode, because
13959    ICS gives us an update to both clocks after every move.
13960    Note that this routine is called *after* forwardMostMove
13961    is updated, so the last fractional tick must be subtracted
13962    from the color that is *not* on move now.
13963 */
13964 void
13965 SwitchClocks()
13966 {
13967     long lastTickLength;
13968     TimeMark now;
13969     int flagged = FALSE;
13970
13971     GetTimeMark(&now);
13972
13973     if (StopClockTimer() && appData.clockMode) {
13974         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13975         if (WhiteOnMove(forwardMostMove)) {
13976             if(blackNPS >= 0) lastTickLength = 0;
13977             blackTimeRemaining -= lastTickLength;
13978            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13979 //         if(pvInfoList[forwardMostMove-1].time == -1)
13980                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13981                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13982         } else {
13983            if(whiteNPS >= 0) lastTickLength = 0;
13984            whiteTimeRemaining -= lastTickLength;
13985            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13986 //         if(pvInfoList[forwardMostMove-1].time == -1)
13987                  pvInfoList[forwardMostMove-1].time = 
13988                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13989         }
13990         flagged = CheckFlags();
13991     }
13992     CheckTimeControl();
13993
13994     if (flagged || !appData.clockMode) return;
13995
13996     switch (gameMode) {
13997       case MachinePlaysBlack:
13998       case MachinePlaysWhite:
13999       case BeginningOfGame:
14000         if (pausing) return;
14001         break;
14002
14003       case EditGame:
14004       case PlayFromGameFile:
14005       case IcsExamining:
14006         return;
14007
14008       default:
14009         break;
14010     }
14011
14012     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14013         if(WhiteOnMove(forwardMostMove))
14014              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14015         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14016     }
14017
14018     tickStartTM = now;
14019     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14020       whiteTimeRemaining : blackTimeRemaining);
14021     StartClockTimer(intendedTickLength);
14022 }
14023         
14024
14025 /* Stop both clocks */
14026 void
14027 StopClocks()
14028 {       
14029     long lastTickLength;
14030     TimeMark now;
14031
14032     if (!StopClockTimer()) return;
14033     if (!appData.clockMode) return;
14034
14035     GetTimeMark(&now);
14036
14037     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14038     if (WhiteOnMove(forwardMostMove)) {
14039         if(whiteNPS >= 0) lastTickLength = 0;
14040         whiteTimeRemaining -= lastTickLength;
14041         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14042     } else {
14043         if(blackNPS >= 0) lastTickLength = 0;
14044         blackTimeRemaining -= lastTickLength;
14045         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14046     }
14047     CheckFlags();
14048 }
14049         
14050 /* Start clock of player on move.  Time may have been reset, so
14051    if clock is already running, stop and restart it. */
14052 void
14053 StartClocks()
14054 {
14055     (void) StopClockTimer(); /* in case it was running already */
14056     DisplayBothClocks();
14057     if (CheckFlags()) return;
14058
14059     if (!appData.clockMode) return;
14060     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14061
14062     GetTimeMark(&tickStartTM);
14063     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14064       whiteTimeRemaining : blackTimeRemaining);
14065
14066    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14067     whiteNPS = blackNPS = -1; 
14068     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14069        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14070         whiteNPS = first.nps;
14071     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14072        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14073         blackNPS = first.nps;
14074     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14075         whiteNPS = second.nps;
14076     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14077         blackNPS = second.nps;
14078     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14079
14080     StartClockTimer(intendedTickLength);
14081 }
14082
14083 char *
14084 TimeString(ms)
14085      long ms;
14086 {
14087     long second, minute, hour, day;
14088     char *sign = "";
14089     static char buf[32];
14090     
14091     if (ms > 0 && ms <= 9900) {
14092       /* convert milliseconds to tenths, rounding up */
14093       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14094
14095       sprintf(buf, " %03.1f ", tenths/10.0);
14096       return buf;
14097     }
14098
14099     /* convert milliseconds to seconds, rounding up */
14100     /* use floating point to avoid strangeness of integer division
14101        with negative dividends on many machines */
14102     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14103
14104     if (second < 0) {
14105         sign = "-";
14106         second = -second;
14107     }
14108     
14109     day = second / (60 * 60 * 24);
14110     second = second % (60 * 60 * 24);
14111     hour = second / (60 * 60);
14112     second = second % (60 * 60);
14113     minute = second / 60;
14114     second = second % 60;
14115     
14116     if (day > 0)
14117       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14118               sign, day, hour, minute, second);
14119     else if (hour > 0)
14120       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14121     else
14122       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14123     
14124     return buf;
14125 }
14126
14127
14128 /*
14129  * This is necessary because some C libraries aren't ANSI C compliant yet.
14130  */
14131 char *
14132 StrStr(string, match)
14133      char *string, *match;
14134 {
14135     int i, length;
14136     
14137     length = strlen(match);
14138     
14139     for (i = strlen(string) - length; i >= 0; i--, string++)
14140       if (!strncmp(match, string, length))
14141         return string;
14142     
14143     return NULL;
14144 }
14145
14146 char *
14147 StrCaseStr(string, match)
14148      char *string, *match;
14149 {
14150     int i, j, length;
14151     
14152     length = strlen(match);
14153     
14154     for (i = strlen(string) - length; i >= 0; i--, string++) {
14155         for (j = 0; j < length; j++) {
14156             if (ToLower(match[j]) != ToLower(string[j]))
14157               break;
14158         }
14159         if (j == length) return string;
14160     }
14161
14162     return NULL;
14163 }
14164
14165 #ifndef _amigados
14166 int
14167 StrCaseCmp(s1, s2)
14168      char *s1, *s2;
14169 {
14170     char c1, c2;
14171     
14172     for (;;) {
14173         c1 = ToLower(*s1++);
14174         c2 = ToLower(*s2++);
14175         if (c1 > c2) return 1;
14176         if (c1 < c2) return -1;
14177         if (c1 == NULLCHAR) return 0;
14178     }
14179 }
14180
14181
14182 int
14183 ToLower(c)
14184      int c;
14185 {
14186     return isupper(c) ? tolower(c) : c;
14187 }
14188
14189
14190 int
14191 ToUpper(c)
14192      int c;
14193 {
14194     return islower(c) ? toupper(c) : c;
14195 }
14196 #endif /* !_amigados    */
14197
14198 char *
14199 StrSave(s)
14200      char *s;
14201 {
14202     char *ret;
14203
14204     if ((ret = (char *) malloc(strlen(s) + 1))) {
14205         strcpy(ret, s);
14206     }
14207     return ret;
14208 }
14209
14210 char *
14211 StrSavePtr(s, savePtr)
14212      char *s, **savePtr;
14213 {
14214     if (*savePtr) {
14215         free(*savePtr);
14216     }
14217     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14218         strcpy(*savePtr, s);
14219     }
14220     return(*savePtr);
14221 }
14222
14223 char *
14224 PGNDate()
14225 {
14226     time_t clock;
14227     struct tm *tm;
14228     char buf[MSG_SIZ];
14229
14230     clock = time((time_t *)NULL);
14231     tm = localtime(&clock);
14232     sprintf(buf, "%04d.%02d.%02d",
14233             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14234     return StrSave(buf);
14235 }
14236
14237
14238 char *
14239 PositionToFEN(move, overrideCastling)
14240      int move;
14241      char *overrideCastling;
14242 {
14243     int i, j, fromX, fromY, toX, toY;
14244     int whiteToPlay;
14245     char buf[128];
14246     char *p, *q;
14247     int emptycount;
14248     ChessSquare piece;
14249
14250     whiteToPlay = (gameMode == EditPosition) ?
14251       !blackPlaysFirst : (move % 2 == 0);
14252     p = buf;
14253
14254     /* Piece placement data */
14255     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14256         emptycount = 0;
14257         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14258             if (boards[move][i][j] == EmptySquare) {
14259                 emptycount++;
14260             } else { ChessSquare piece = boards[move][i][j];
14261                 if (emptycount > 0) {
14262                     if(emptycount<10) /* [HGM] can be >= 10 */
14263                         *p++ = '0' + emptycount;
14264                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14265                     emptycount = 0;
14266                 }
14267                 if(PieceToChar(piece) == '+') {
14268                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14269                     *p++ = '+';
14270                     piece = (ChessSquare)(DEMOTED piece);
14271                 } 
14272                 *p++ = PieceToChar(piece);
14273                 if(p[-1] == '~') {
14274                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14275                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14276                     *p++ = '~';
14277                 }
14278             }
14279         }
14280         if (emptycount > 0) {
14281             if(emptycount<10) /* [HGM] can be >= 10 */
14282                 *p++ = '0' + emptycount;
14283             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14284             emptycount = 0;
14285         }
14286         *p++ = '/';
14287     }
14288     *(p - 1) = ' ';
14289
14290     /* [HGM] print Crazyhouse or Shogi holdings */
14291     if( gameInfo.holdingsWidth ) {
14292         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14293         q = p;
14294         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14295             piece = boards[move][i][BOARD_WIDTH-1];
14296             if( piece != EmptySquare )
14297               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14298                   *p++ = PieceToChar(piece);
14299         }
14300         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14301             piece = boards[move][BOARD_HEIGHT-i-1][0];
14302             if( piece != EmptySquare )
14303               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14304                   *p++ = PieceToChar(piece);
14305         }
14306
14307         if( q == p ) *p++ = '-';
14308         *p++ = ']';
14309         *p++ = ' ';
14310     }
14311
14312     /* Active color */
14313     *p++ = whiteToPlay ? 'w' : 'b';
14314     *p++ = ' ';
14315
14316   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14317     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14318   } else {
14319   if(nrCastlingRights) {
14320      q = p;
14321      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14322        /* [HGM] write directly from rights */
14323            if(boards[move][CASTLING][2] != NoRights &&
14324               boards[move][CASTLING][0] != NoRights   )
14325                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14326            if(boards[move][CASTLING][2] != NoRights &&
14327               boards[move][CASTLING][1] != NoRights   )
14328                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14329            if(boards[move][CASTLING][5] != NoRights &&
14330               boards[move][CASTLING][3] != NoRights   )
14331                 *p++ = boards[move][CASTLING][3] + AAA;
14332            if(boards[move][CASTLING][5] != NoRights &&
14333               boards[move][CASTLING][4] != NoRights   )
14334                 *p++ = boards[move][CASTLING][4] + AAA;
14335      } else {
14336
14337         /* [HGM] write true castling rights */
14338         if( nrCastlingRights == 6 ) {
14339             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14340                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14341             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14342                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14343             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14344                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14345             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14346                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14347         }
14348      }
14349      if (q == p) *p++ = '-'; /* No castling rights */
14350      *p++ = ' ';
14351   }
14352
14353   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14354      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14355     /* En passant target square */
14356     if (move > backwardMostMove) {
14357         fromX = moveList[move - 1][0] - AAA;
14358         fromY = moveList[move - 1][1] - ONE;
14359         toX = moveList[move - 1][2] - AAA;
14360         toY = moveList[move - 1][3] - ONE;
14361         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14362             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14363             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14364             fromX == toX) {
14365             /* 2-square pawn move just happened */
14366             *p++ = toX + AAA;
14367             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14368         } else {
14369             *p++ = '-';
14370         }
14371     } else if(move == backwardMostMove) {
14372         // [HGM] perhaps we should always do it like this, and forget the above?
14373         if((signed char)boards[move][EP_STATUS] >= 0) {
14374             *p++ = boards[move][EP_STATUS] + AAA;
14375             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14376         } else {
14377             *p++ = '-';
14378         }
14379     } else {
14380         *p++ = '-';
14381     }
14382     *p++ = ' ';
14383   }
14384   }
14385
14386     /* [HGM] find reversible plies */
14387     {   int i = 0, j=move;
14388
14389         if (appData.debugMode) { int k;
14390             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14391             for(k=backwardMostMove; k<=forwardMostMove; k++)
14392                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14393
14394         }
14395
14396         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14397         if( j == backwardMostMove ) i += initialRulePlies;
14398         sprintf(p, "%d ", i);
14399         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14400     }
14401     /* Fullmove number */
14402     sprintf(p, "%d", (move / 2) + 1);
14403     
14404     return StrSave(buf);
14405 }
14406
14407 Boolean
14408 ParseFEN(board, blackPlaysFirst, fen)
14409     Board board;
14410      int *blackPlaysFirst;
14411      char *fen;
14412 {
14413     int i, j;
14414     char *p;
14415     int emptycount;
14416     ChessSquare piece;
14417
14418     p = fen;
14419
14420     /* [HGM] by default clear Crazyhouse holdings, if present */
14421     if(gameInfo.holdingsWidth) {
14422        for(i=0; i<BOARD_HEIGHT; i++) {
14423            board[i][0]             = EmptySquare; /* black holdings */
14424            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14425            board[i][1]             = (ChessSquare) 0; /* black counts */
14426            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14427        }
14428     }
14429
14430     /* Piece placement data */
14431     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14432         j = 0;
14433         for (;;) {
14434             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14435                 if (*p == '/') p++;
14436                 emptycount = gameInfo.boardWidth - j;
14437                 while (emptycount--)
14438                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14439                 break;
14440 #if(BOARD_FILES >= 10)
14441             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14442                 p++; emptycount=10;
14443                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14444                 while (emptycount--)
14445                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14446 #endif
14447             } else if (isdigit(*p)) {
14448                 emptycount = *p++ - '0';
14449                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14450                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14451                 while (emptycount--)
14452                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14453             } else if (*p == '+' || isalpha(*p)) {
14454                 if (j >= gameInfo.boardWidth) return FALSE;
14455                 if(*p=='+') {
14456                     piece = CharToPiece(*++p);
14457                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14458                     piece = (ChessSquare) (PROMOTED piece ); p++;
14459                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14460                 } else piece = CharToPiece(*p++);
14461
14462                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14463                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14464                     piece = (ChessSquare) (PROMOTED piece);
14465                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14466                     p++;
14467                 }
14468                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14469             } else {
14470                 return FALSE;
14471             }
14472         }
14473     }
14474     while (*p == '/' || *p == ' ') p++;
14475
14476     /* [HGM] look for Crazyhouse holdings here */
14477     while(*p==' ') p++;
14478     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14479         if(*p == '[') p++;
14480         if(*p == '-' ) *p++; /* empty holdings */ else {
14481             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14482             /* if we would allow FEN reading to set board size, we would   */
14483             /* have to add holdings and shift the board read so far here   */
14484             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14485                 *p++;
14486                 if((int) piece >= (int) BlackPawn ) {
14487                     i = (int)piece - (int)BlackPawn;
14488                     i = PieceToNumber((ChessSquare)i);
14489                     if( i >= gameInfo.holdingsSize ) return FALSE;
14490                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14491                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14492                 } else {
14493                     i = (int)piece - (int)WhitePawn;
14494                     i = PieceToNumber((ChessSquare)i);
14495                     if( i >= gameInfo.holdingsSize ) return FALSE;
14496                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14497                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14498                 }
14499             }
14500         }
14501         if(*p == ']') *p++;
14502     }
14503
14504     while(*p == ' ') p++;
14505
14506     /* Active color */
14507     switch (*p++) {
14508       case 'w':
14509         *blackPlaysFirst = FALSE;
14510         break;
14511       case 'b': 
14512         *blackPlaysFirst = TRUE;
14513         break;
14514       default:
14515         return FALSE;
14516     }
14517
14518     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14519     /* return the extra info in global variiables             */
14520
14521     /* set defaults in case FEN is incomplete */
14522     board[EP_STATUS] = EP_UNKNOWN;
14523     for(i=0; i<nrCastlingRights; i++ ) {
14524         board[CASTLING][i] =
14525             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14526     }   /* assume possible unless obviously impossible */
14527     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14528     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14529     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14530                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14531     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14532     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14533     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14534                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14535     FENrulePlies = 0;
14536
14537     while(*p==' ') p++;
14538     if(nrCastlingRights) {
14539       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14540           /* castling indicator present, so default becomes no castlings */
14541           for(i=0; i<nrCastlingRights; i++ ) {
14542                  board[CASTLING][i] = NoRights;
14543           }
14544       }
14545       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14546              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14547              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14548              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14549         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14550
14551         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14552             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14553             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14554         }
14555         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14556             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14557         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14558                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14559         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14560                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14561         switch(c) {
14562           case'K':
14563               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14564               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14565               board[CASTLING][2] = whiteKingFile;
14566               break;
14567           case'Q':
14568               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14569               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14570               board[CASTLING][2] = whiteKingFile;
14571               break;
14572           case'k':
14573               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14574               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14575               board[CASTLING][5] = blackKingFile;
14576               break;
14577           case'q':
14578               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14579               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14580               board[CASTLING][5] = blackKingFile;
14581           case '-':
14582               break;
14583           default: /* FRC castlings */
14584               if(c >= 'a') { /* black rights */
14585                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14586                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14587                   if(i == BOARD_RGHT) break;
14588                   board[CASTLING][5] = i;
14589                   c -= AAA;
14590                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14591                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14592                   if(c > i)
14593                       board[CASTLING][3] = c;
14594                   else
14595                       board[CASTLING][4] = c;
14596               } else { /* white rights */
14597                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14598                     if(board[0][i] == WhiteKing) break;
14599                   if(i == BOARD_RGHT) break;
14600                   board[CASTLING][2] = i;
14601                   c -= AAA - 'a' + 'A';
14602                   if(board[0][c] >= WhiteKing) break;
14603                   if(c > i)
14604                       board[CASTLING][0] = c;
14605                   else
14606                       board[CASTLING][1] = c;
14607               }
14608         }
14609       }
14610       for(i=0; i<nrCastlingRights; i++)
14611         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14612     if (appData.debugMode) {
14613         fprintf(debugFP, "FEN castling rights:");
14614         for(i=0; i<nrCastlingRights; i++)
14615         fprintf(debugFP, " %d", board[CASTLING][i]);
14616         fprintf(debugFP, "\n");
14617     }
14618
14619       while(*p==' ') p++;
14620     }
14621
14622     /* read e.p. field in games that know e.p. capture */
14623     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14624        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14625       if(*p=='-') {
14626         p++; board[EP_STATUS] = EP_NONE;
14627       } else {
14628          char c = *p++ - AAA;
14629
14630          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14631          if(*p >= '0' && *p <='9') *p++;
14632          board[EP_STATUS] = c;
14633       }
14634     }
14635
14636
14637     if(sscanf(p, "%d", &i) == 1) {
14638         FENrulePlies = i; /* 50-move ply counter */
14639         /* (The move number is still ignored)    */
14640     }
14641
14642     return TRUE;
14643 }
14644       
14645 void
14646 EditPositionPasteFEN(char *fen)
14647 {
14648   if (fen != NULL) {
14649     Board initial_position;
14650
14651     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14652       DisplayError(_("Bad FEN position in clipboard"), 0);
14653       return ;
14654     } else {
14655       int savedBlackPlaysFirst = blackPlaysFirst;
14656       EditPositionEvent();
14657       blackPlaysFirst = savedBlackPlaysFirst;
14658       CopyBoard(boards[0], initial_position);
14659       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14660       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14661       DisplayBothClocks();
14662       DrawPosition(FALSE, boards[currentMove]);
14663     }
14664   }
14665 }
14666
14667 static char cseq[12] = "\\   ";
14668
14669 Boolean set_cont_sequence(char *new_seq)
14670 {
14671     int len;
14672     Boolean ret;
14673
14674     // handle bad attempts to set the sequence
14675         if (!new_seq)
14676                 return 0; // acceptable error - no debug
14677
14678     len = strlen(new_seq);
14679     ret = (len > 0) && (len < sizeof(cseq));
14680     if (ret)
14681         strcpy(cseq, new_seq);
14682     else if (appData.debugMode)
14683         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14684     return ret;
14685 }
14686
14687 /*
14688     reformat a source message so words don't cross the width boundary.  internal
14689     newlines are not removed.  returns the wrapped size (no null character unless
14690     included in source message).  If dest is NULL, only calculate the size required
14691     for the dest buffer.  lp argument indicats line position upon entry, and it's
14692     passed back upon exit.
14693 */
14694 int wrap(char *dest, char *src, int count, int width, int *lp)
14695 {
14696     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14697
14698     cseq_len = strlen(cseq);
14699     old_line = line = *lp;
14700     ansi = len = clen = 0;
14701
14702     for (i=0; i < count; i++)
14703     {
14704         if (src[i] == '\033')
14705             ansi = 1;
14706
14707         // if we hit the width, back up
14708         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14709         {
14710             // store i & len in case the word is too long
14711             old_i = i, old_len = len;
14712
14713             // find the end of the last word
14714             while (i && src[i] != ' ' && src[i] != '\n')
14715             {
14716                 i--;
14717                 len--;
14718             }
14719
14720             // word too long?  restore i & len before splitting it
14721             if ((old_i-i+clen) >= width)
14722             {
14723                 i = old_i;
14724                 len = old_len;
14725             }
14726
14727             // extra space?
14728             if (i && src[i-1] == ' ')
14729                 len--;
14730
14731             if (src[i] != ' ' && src[i] != '\n')
14732             {
14733                 i--;
14734                 if (len)
14735                     len--;
14736             }
14737
14738             // now append the newline and continuation sequence
14739             if (dest)
14740                 dest[len] = '\n';
14741             len++;
14742             if (dest)
14743                 strncpy(dest+len, cseq, cseq_len);
14744             len += cseq_len;
14745             line = cseq_len;
14746             clen = cseq_len;
14747             continue;
14748         }
14749
14750         if (dest)
14751             dest[len] = src[i];
14752         len++;
14753         if (!ansi)
14754             line++;
14755         if (src[i] == '\n')
14756             line = 0;
14757         if (src[i] == 'm')
14758             ansi = 0;
14759     }
14760     if (dest && appData.debugMode)
14761     {
14762         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14763             count, width, line, len, *lp);
14764         show_bytes(debugFP, src, count);
14765         fprintf(debugFP, "\ndest: ");
14766         show_bytes(debugFP, dest, len);
14767         fprintf(debugFP, "\n");
14768     }
14769     *lp = dest ? line : old_line;
14770
14771     return len;
14772 }
14773
14774 // [HGM] vari: routines for shelving variations
14775
14776 void 
14777 PushTail(int firstMove, int lastMove)
14778 {
14779         int i, j, nrMoves = lastMove - firstMove;
14780
14781         if(appData.icsActive) { // only in local mode
14782                 forwardMostMove = currentMove; // mimic old ICS behavior
14783                 return;
14784         }
14785         if(storedGames >= MAX_VARIATIONS-1) return;
14786
14787         // push current tail of game on stack
14788         savedResult[storedGames] = gameInfo.result;
14789         savedDetails[storedGames] = gameInfo.resultDetails;
14790         gameInfo.resultDetails = NULL;
14791         savedFirst[storedGames] = firstMove;
14792         savedLast [storedGames] = lastMove;
14793         savedFramePtr[storedGames] = framePtr;
14794         framePtr -= nrMoves; // reserve space for the boards
14795         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14796             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14797             for(j=0; j<MOVE_LEN; j++)
14798                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14799             for(j=0; j<2*MOVE_LEN; j++)
14800                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14801             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14802             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14803             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14804             pvInfoList[firstMove+i-1].depth = 0;
14805             commentList[framePtr+i] = commentList[firstMove+i];
14806             commentList[firstMove+i] = NULL;
14807         }
14808
14809         storedGames++;
14810         forwardMostMove = currentMove; // truncte game so we can start variation
14811         if(storedGames == 1) GreyRevert(FALSE);
14812 }
14813
14814 Boolean
14815 PopTail(Boolean annotate)
14816 {
14817         int i, j, nrMoves;
14818         char buf[8000], moveBuf[20];
14819
14820         if(appData.icsActive) return FALSE; // only in local mode
14821         if(!storedGames) return FALSE; // sanity
14822
14823         storedGames--;
14824         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14825         nrMoves = savedLast[storedGames] - currentMove;
14826         if(annotate) {
14827                 int cnt = 10;
14828                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14829                 else strcpy(buf, "(");
14830                 for(i=currentMove; i<forwardMostMove; i++) {
14831                         if(WhiteOnMove(i))
14832                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14833                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14834                         strcat(buf, moveBuf);
14835                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14836                 }
14837                 strcat(buf, ")");
14838         }
14839         for(i=1; i<nrMoves; i++) { // copy last variation back
14840             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14841             for(j=0; j<MOVE_LEN; j++)
14842                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14843             for(j=0; j<2*MOVE_LEN; j++)
14844                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14845             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14846             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14847             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14848             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14849             commentList[currentMove+i] = commentList[framePtr+i];
14850             commentList[framePtr+i] = NULL;
14851         }
14852         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14853         framePtr = savedFramePtr[storedGames];
14854         gameInfo.result = savedResult[storedGames];
14855         if(gameInfo.resultDetails != NULL) {
14856             free(gameInfo.resultDetails);
14857       }
14858         gameInfo.resultDetails = savedDetails[storedGames];
14859         forwardMostMove = currentMove + nrMoves;
14860         if(storedGames == 0) GreyRevert(TRUE);
14861         return TRUE;
14862 }
14863
14864 void 
14865 CleanupTail()
14866 {       // remove all shelved variations
14867         int i;
14868         for(i=0; i<storedGames; i++) {
14869             if(savedDetails[i])
14870                 free(savedDetails[i]);
14871             savedDetails[i] = NULL;
14872         }
14873         for(i=framePtr; i<MAX_MOVES; i++) {
14874                 if(commentList[i]) free(commentList[i]);
14875                 commentList[i] = NULL;
14876         }
14877         framePtr = MAX_MOVES-1;
14878         storedGames = 0;
14879 }