65ea20dd6f715fa2746297116c5820133c6c6ce3
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 # define T_(s) gettext(s)
135 #else 
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s) 
141 #   define N_(s) s 
142 #   define T_(s) s
143 # endif
144 #endif 
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
162                       int toX, int toY));
163 void HandleMachineMove P((char *message, ChessProgramState *cps));
164 int AutoPlayOneMove P((void));
165 int LoadGameOneMove P((ChessMove readAhead));
166 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
167 int LoadPositionFromFile P((char *filename, int n, char *title));
168 int SavePositionToFile P((char *filename));
169 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
170                                                                                 Board board));
171 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
172 void ShowMove P((int fromX, int fromY, int toX, int toY));
173 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
174                    /*char*/int promoChar));
175 void BackwardInner P((int target));
176 void ForwardInner P((int target));
177 int Adjudicate P((ChessProgramState *cps));
178 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
179 void EditPositionDone P((Boolean fakeRights));
180 void PrintOpponents P((FILE *fp));
181 void PrintPosition P((FILE *fp, int move));
182 void StartChessProgram P((ChessProgramState *cps));
183 void SendToProgram P((char *message, ChessProgramState *cps));
184 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
185 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
186                            char *buf, int count, int error));
187 void SendTimeControl P((ChessProgramState *cps,
188                         int mps, long tc, int inc, int sd, int st));
189 char *TimeControlTagValue P((void));
190 void Attention P((ChessProgramState *cps));
191 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
192 void ResurrectChessProgram P((void));
193 void DisplayComment P((int moveNumber, char *text));
194 void DisplayMove P((int moveNumber));
195
196 void ParseGameHistory P((char *game));
197 void ParseBoard12 P((char *string));
198 void KeepAlive P((void));
199 void StartClocks P((void));
200 void SwitchClocks P((int nr));
201 void StopClocks P((void));
202 void ResetClocks P((void));
203 char *PGNDate P((void));
204 void SetGameInfo P((void));
205 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
206 int RegisterMove P((void));
207 void MakeRegisteredMove P((void));
208 void TruncateGame P((void));
209 int looking_at P((char *, int *, char *));
210 void CopyPlayerNameIntoFileName P((char **, char *));
211 char *SavePart P((char *));
212 int SaveGameOldStyle P((FILE *));
213 int SaveGamePGN P((FILE *));
214 void GetTimeMark P((TimeMark *));
215 long SubtractTimeMarks P((TimeMark *, TimeMark *));
216 int CheckFlags P((void));
217 long NextTickLength P((long));
218 void CheckTimeControl P((void));
219 void show_bytes P((FILE *, char *, int));
220 int string_to_rating P((char *str));
221 void ParseFeatures P((char* args, ChessProgramState *cps));
222 void InitBackEnd3 P((void));
223 void FeatureDone P((ChessProgramState* cps, int val));
224 void InitChessProgram P((ChessProgramState *cps, int setup));
225 void OutputKibitz(int window, char *text);
226 int PerpetualChase(int first, int last);
227 int EngineOutputIsUp();
228 void InitDrawingSizes(int x, int y);
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245
246 extern int tinyLayout, smallLayout;
247 ChessProgramStats programStats;
248 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 int endPV = -1;
250 static int exiting = 0; /* [HGM] moved to top */
251 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
252 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
253 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
254 int partnerHighlight[2];
255 Boolean partnerBoardValid = 0;
256 char partnerStatus[MSG_SIZ];
257 Boolean partnerUp;
258 Boolean originalFlip;
259 Boolean twoBoards = 0;
260 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
261 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
262 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
263 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
264 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
265 int opponentKibitzes;
266 int lastSavedGame; /* [HGM] save: ID of game */
267 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
268 extern int chatCount;
269 int chattingPartner;
270 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271
272 /* States for ics_getting_history */
273 #define H_FALSE 0
274 #define H_REQUESTED 1
275 #define H_GOT_REQ_HEADER 2
276 #define H_GOT_UNREQ_HEADER 3
277 #define H_GETTING_MOVES 4
278 #define H_GOT_UNWANTED_HEADER 5
279
280 /* whosays values for GameEnds */
281 #define GE_ICS 0
282 #define GE_ENGINE 1
283 #define GE_PLAYER 2
284 #define GE_FILE 3
285 #define GE_XBOARD 4
286 #define GE_ENGINE1 5
287 #define GE_ENGINE2 6
288
289 /* Maximum number of games in a cmail message */
290 #define CMAIL_MAX_GAMES 20
291
292 /* Different types of move when calling RegisterMove */
293 #define CMAIL_MOVE   0
294 #define CMAIL_RESIGN 1
295 #define CMAIL_DRAW   2
296 #define CMAIL_ACCEPT 3
297
298 /* Different types of result to remember for each game */
299 #define CMAIL_NOT_RESULT 0
300 #define CMAIL_OLD_RESULT 1
301 #define CMAIL_NEW_RESULT 2
302
303 /* Telnet protocol constants */
304 #define TN_WILL 0373
305 #define TN_WONT 0374
306 #define TN_DO   0375
307 #define TN_DONT 0376
308 #define TN_IAC  0377
309 #define TN_ECHO 0001
310 #define TN_SGA  0003
311 #define TN_PORT 23
312
313 /* [AS] */
314 static char * safeStrCpy( char * dst, const char * src, size_t count )
315 {
316     assert( dst != NULL );
317     assert( src != NULL );
318     assert( count > 0 );
319
320     strncpy( dst, src, count );
321     dst[ count-1 ] = '\0';
322     return dst;
323 }
324
325 /* Some compiler can't cast u64 to double
326  * This function do the job for us:
327
328  * We use the highest bit for cast, this only
329  * works if the highest bit is not
330  * in use (This should not happen)
331  *
332  * We used this for all compiler
333  */
334 double
335 u64ToDouble(u64 value)
336 {
337   double r;
338   u64 tmp = value & u64Const(0x7fffffffffffffff);
339   r = (double)(s64)tmp;
340   if (value & u64Const(0x8000000000000000))
341        r +=  9.2233720368547758080e18; /* 2^63 */
342  return r;
343 }
344
345 /* Fake up flags for now, as we aren't keeping track of castling
346    availability yet. [HGM] Change of logic: the flag now only
347    indicates the type of castlings allowed by the rule of the game.
348    The actual rights themselves are maintained in the array
349    castlingRights, as part of the game history, and are not probed
350    by this function.
351  */
352 int
353 PosFlags(index)
354 {
355   int flags = F_ALL_CASTLE_OK;
356   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
357   switch (gameInfo.variant) {
358   case VariantSuicide:
359     flags &= ~F_ALL_CASTLE_OK;
360   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
361     flags |= F_IGNORE_CHECK;
362   case VariantLosers:
363     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
364     break;
365   case VariantAtomic:
366     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
367     break;
368   case VariantKriegspiel:
369     flags |= F_KRIEGSPIEL_CAPTURE;
370     break;
371   case VariantCapaRandom: 
372   case VariantFischeRandom:
373     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
374   case VariantNoCastle:
375   case VariantShatranj:
376   case VariantCourier:
377   case VariantMakruk:
378     flags &= ~F_ALL_CASTLE_OK;
379     break;
380   default:
381     break;
382   }
383   return flags;
384 }
385
386 FILE *gameFileFP, *debugFP;
387
388 /* 
389     [AS] Note: sometimes, the sscanf() function is used to parse the input
390     into a fixed-size buffer. Because of this, we must be prepared to
391     receive strings as long as the size of the input buffer, which is currently
392     set to 4K for Windows and 8K for the rest.
393     So, we must either allocate sufficiently large buffers here, or
394     reduce the size of the input buffer in the input reading part.
395 */
396
397 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
398 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
399 char thinkOutput1[MSG_SIZ*10];
400
401 ChessProgramState first, second;
402
403 /* premove variables */
404 int premoveToX = 0;
405 int premoveToY = 0;
406 int premoveFromX = 0;
407 int premoveFromY = 0;
408 int premovePromoChar = 0;
409 int gotPremove = 0;
410 Boolean alarmSounded;
411 /* end premove variables */
412
413 char *ics_prefix = "$";
414 int ics_type = ICS_GENERIC;
415
416 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
417 int pauseExamForwardMostMove = 0;
418 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
419 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
420 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
421 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
422 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
423 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
424 int whiteFlag = FALSE, blackFlag = FALSE;
425 int userOfferedDraw = FALSE;
426 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
427 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
428 int cmailMoveType[CMAIL_MAX_GAMES];
429 long ics_clock_paused = 0;
430 ProcRef icsPR = NoProc, cmailPR = NoProc;
431 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
432 GameMode gameMode = BeginningOfGame;
433 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
434 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
435 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
436 int hiddenThinkOutputState = 0; /* [AS] */
437 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
438 int adjudicateLossPlies = 6;
439 char white_holding[64], black_holding[64];
440 TimeMark lastNodeCountTime;
441 long lastNodeCount=0;
442 int have_sent_ICS_logon = 0;
443 int movesPerSession;
444 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
445 long timeControl_2; /* [AS] Allow separate time controls */
446 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
447 long timeRemaining[2][MAX_MOVES];
448 int matchGame = 0;
449 TimeMark programStartTime;
450 char ics_handle[MSG_SIZ];
451 int have_set_title = 0;
452
453 /* animateTraining preserves the state of appData.animate
454  * when Training mode is activated. This allows the
455  * response to be animated when appData.animate == TRUE and
456  * appData.animateDragging == TRUE.
457  */
458 Boolean animateTraining;
459
460 GameInfo gameInfo;
461
462 AppData appData;
463
464 Board boards[MAX_MOVES];
465 /* [HGM] Following 7 needed for accurate legality tests: */
466 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
467 signed char  initialRights[BOARD_FILES];
468 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
469 int   initialRulePlies, FENrulePlies;
470 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
471 int loadFlag = 0; 
472 int shuffleOpenings;
473 int mute; // mute all sounds
474
475 // [HGM] vari: next 12 to save and restore variations
476 #define MAX_VARIATIONS 10
477 int framePtr = MAX_MOVES-1; // points to free stack entry
478 int storedGames = 0;
479 int savedFirst[MAX_VARIATIONS];
480 int savedLast[MAX_VARIATIONS];
481 int savedFramePtr[MAX_VARIATIONS];
482 char *savedDetails[MAX_VARIATIONS];
483 ChessMove savedResult[MAX_VARIATIONS];
484
485 void PushTail P((int firstMove, int lastMove));
486 Boolean PopTail P((Boolean annotate));
487 void CleanupTail P((void));
488
489 ChessSquare  FIDEArray[2][BOARD_FILES] = {
490     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
491         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
492     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
493         BlackKing, BlackBishop, BlackKnight, BlackRook }
494 };
495
496 ChessSquare twoKingsArray[2][BOARD_FILES] = {
497     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
498         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
499     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
500         BlackKing, BlackKing, BlackKnight, BlackRook }
501 };
502
503 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
504     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
505         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
506     { BlackRook, BlackMan, BlackBishop, BlackQueen,
507         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
508 };
509
510 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
511     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
514         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
515 };
516
517 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
518     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
519         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
521         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
522 };
523
524 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
525     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
526         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
527     { BlackRook, BlackKnight, BlackMan, BlackFerz,
528         BlackKing, BlackMan, BlackKnight, BlackRook }
529 };
530
531
532 #if (BOARD_FILES>=10)
533 ChessSquare ShogiArray[2][BOARD_FILES] = {
534     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
535         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
536     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
537         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
538 };
539
540 ChessSquare XiangqiArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
542         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
544         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 };
546
547 ChessSquare CapablancaArray[2][BOARD_FILES] = {
548     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
549         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
551         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
552 };
553
554 ChessSquare GreatArray[2][BOARD_FILES] = {
555     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
556         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
557     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
558         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
559 };
560
561 ChessSquare JanusArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
563         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
564     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
565         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
566 };
567
568 #ifdef GOTHIC
569 ChessSquare GothicArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
571         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
573         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
574 };
575 #else // !GOTHIC
576 #define GothicArray CapablancaArray
577 #endif // !GOTHIC
578
579 #ifdef FALCON
580 ChessSquare FalconArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
582         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
584         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
585 };
586 #else // !FALCON
587 #define FalconArray CapablancaArray
588 #endif // !FALCON
589
590 #else // !(BOARD_FILES>=10)
591 #define XiangqiPosition FIDEArray
592 #define CapablancaArray FIDEArray
593 #define GothicArray FIDEArray
594 #define GreatArray FIDEArray
595 #endif // !(BOARD_FILES>=10)
596
597 #if (BOARD_FILES>=12)
598 ChessSquare CourierArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
600         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
601     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
602         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
603 };
604 #else // !(BOARD_FILES>=12)
605 #define CourierArray CapablancaArray
606 #endif // !(BOARD_FILES>=12)
607
608
609 Board initialPosition;
610
611
612 /* Convert str to a rating. Checks for special cases of "----",
613
614    "++++", etc. Also strips ()'s */
615 int
616 string_to_rating(str)
617   char *str;
618 {
619   while(*str && !isdigit(*str)) ++str;
620   if (!*str)
621     return 0;   /* One of the special "no rating" cases */
622   else
623     return atoi(str);
624 }
625
626 void
627 ClearProgramStats()
628 {
629     /* Init programStats */
630     programStats.movelist[0] = 0;
631     programStats.depth = 0;
632     programStats.nr_moves = 0;
633     programStats.moves_left = 0;
634     programStats.nodes = 0;
635     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
636     programStats.score = 0;
637     programStats.got_only_move = 0;
638     programStats.got_fail = 0;
639     programStats.line_is_book = 0;
640 }
641
642 void
643 InitBackEnd1()
644 {
645     int matched, min, sec;
646
647     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
648     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
649
650     GetTimeMark(&programStartTime);
651     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
652
653     ClearProgramStats();
654     programStats.ok_to_send = 1;
655     programStats.seen_stat = 0;
656
657     /*
658      * Initialize game list
659      */
660     ListNew(&gameList);
661
662
663     /*
664      * Internet chess server status
665      */
666     if (appData.icsActive) {
667         appData.matchMode = FALSE;
668         appData.matchGames = 0;
669 #if ZIPPY       
670         appData.noChessProgram = !appData.zippyPlay;
671 #else
672         appData.zippyPlay = FALSE;
673         appData.zippyTalk = FALSE;
674         appData.noChessProgram = TRUE;
675 #endif
676         if (*appData.icsHelper != NULLCHAR) {
677             appData.useTelnet = TRUE;
678             appData.telnetProgram = appData.icsHelper;
679         }
680     } else {
681         appData.zippyTalk = appData.zippyPlay = FALSE;
682     }
683
684     /* [AS] Initialize pv info list [HGM] and game state */
685     {
686         int i, j;
687
688         for( i=0; i<=framePtr; i++ ) {
689             pvInfoList[i].depth = -1;
690             boards[i][EP_STATUS] = EP_NONE;
691             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
692         }
693     }
694
695     /*
696      * Parse timeControl resource
697      */
698     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
699                           appData.movesPerSession)) {
700         char buf[MSG_SIZ];
701         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
702         DisplayFatalError(buf, 0, 2);
703     }
704
705     /*
706      * Parse searchTime resource
707      */
708     if (*appData.searchTime != NULLCHAR) {
709         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
710         if (matched == 1) {
711             searchTime = min * 60;
712         } else if (matched == 2) {
713             searchTime = min * 60 + sec;
714         } else {
715             char buf[MSG_SIZ];
716             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
717             DisplayFatalError(buf, 0, 2);
718         }
719     }
720
721     /* [AS] Adjudication threshold */
722     adjudicateLossThreshold = appData.adjudicateLossThreshold;
723     
724     first.which = _("first");
725     second.which = _("second");
726     first.maybeThinking = second.maybeThinking = FALSE;
727     first.pr = second.pr = NoProc;
728     first.isr = second.isr = NULL;
729     first.sendTime = second.sendTime = 2;
730     first.sendDrawOffers = 1;
731     if (appData.firstPlaysBlack) {
732         first.twoMachinesColor = "black\n";
733         second.twoMachinesColor = "white\n";
734     } else {
735         first.twoMachinesColor = "white\n";
736         second.twoMachinesColor = "black\n";
737     }
738     first.program = appData.firstChessProgram;
739     second.program = appData.secondChessProgram;
740     first.host = appData.firstHost;
741     second.host = appData.secondHost;
742     first.dir = appData.firstDirectory;
743     second.dir = appData.secondDirectory;
744     first.other = &second;
745     second.other = &first;
746     first.initString = appData.initString;
747     second.initString = appData.secondInitString;
748     first.computerString = appData.firstComputerString;
749     second.computerString = appData.secondComputerString;
750     first.useSigint = second.useSigint = TRUE;
751     first.useSigterm = second.useSigterm = TRUE;
752     first.reuse = appData.reuseFirst;
753     second.reuse = appData.reuseSecond;
754     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
755     second.nps = appData.secondNPS;
756     first.useSetboard = second.useSetboard = FALSE;
757     first.useSAN = second.useSAN = FALSE;
758     first.usePing = second.usePing = FALSE;
759     first.lastPing = second.lastPing = 0;
760     first.lastPong = second.lastPong = 0;
761     first.usePlayother = second.usePlayother = FALSE;
762     first.useColors = second.useColors = TRUE;
763     first.useUsermove = second.useUsermove = FALSE;
764     first.sendICS = second.sendICS = FALSE;
765     first.sendName = second.sendName = appData.icsActive;
766     first.sdKludge = second.sdKludge = FALSE;
767     first.stKludge = second.stKludge = FALSE;
768     TidyProgramName(first.program, first.host, first.tidy);
769     TidyProgramName(second.program, second.host, second.tidy);
770     first.matchWins = second.matchWins = 0;
771     strcpy(first.variants, appData.variant);
772     strcpy(second.variants, appData.variant);
773     first.analysisSupport = second.analysisSupport = 2; /* detect */
774     first.analyzing = second.analyzing = FALSE;
775     first.initDone = second.initDone = FALSE;
776
777     /* New features added by Tord: */
778     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
779     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
780     /* End of new features added by Tord. */
781     first.fenOverride  = appData.fenOverride1;
782     second.fenOverride = appData.fenOverride2;
783
784     /* [HGM] time odds: set factor for each machine */
785     first.timeOdds  = appData.firstTimeOdds;
786     second.timeOdds = appData.secondTimeOdds;
787     { float norm = 1;
788         if(appData.timeOddsMode) {
789             norm = first.timeOdds;
790             if(norm > second.timeOdds) norm = second.timeOdds;
791         }
792         first.timeOdds /= norm;
793         second.timeOdds /= norm;
794     }
795
796     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797     first.accumulateTC = appData.firstAccumulateTC;
798     second.accumulateTC = appData.secondAccumulateTC;
799     first.maxNrOfSessions = second.maxNrOfSessions = 1;
800
801     /* [HGM] debug */
802     first.debug = second.debug = FALSE;
803     first.supportsNPS = second.supportsNPS = UNKNOWN;
804
805     /* [HGM] options */
806     first.optionSettings  = appData.firstOptions;
807     second.optionSettings = appData.secondOptions;
808
809     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
810     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
811     first.isUCI = appData.firstIsUCI; /* [AS] */
812     second.isUCI = appData.secondIsUCI; /* [AS] */
813     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
814     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
815
816     if (appData.firstProtocolVersion > PROTOVER ||
817         appData.firstProtocolVersion < 1) {
818       char buf[MSG_SIZ];
819       sprintf(buf, _("protocol version %d not supported"),
820               appData.firstProtocolVersion);
821       DisplayFatalError(buf, 0, 2);
822     } else {
823       first.protocolVersion = appData.firstProtocolVersion;
824     }
825
826     if (appData.secondProtocolVersion > PROTOVER ||
827         appData.secondProtocolVersion < 1) {
828       char buf[MSG_SIZ];
829       sprintf(buf, _("protocol version %d not supported"),
830               appData.secondProtocolVersion);
831       DisplayFatalError(buf, 0, 2);
832     } else {
833       second.protocolVersion = appData.secondProtocolVersion;
834     }
835
836     if (appData.icsActive) {
837         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
838 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
839     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
840         appData.clockMode = FALSE;
841         first.sendTime = second.sendTime = 0;
842     }
843     
844 #if ZIPPY
845     /* Override some settings from environment variables, for backward
846        compatibility.  Unfortunately it's not feasible to have the env
847        vars just set defaults, at least in xboard.  Ugh.
848     */
849     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
850       ZippyInit();
851     }
852 #endif
853     
854     if (appData.noChessProgram) {
855         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
856         sprintf(programVersion, "%s", PACKAGE_STRING);
857     } else {
858       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
859       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
860       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
861     }
862
863     if (!appData.icsActive) {
864       char buf[MSG_SIZ];
865       /* Check for variants that are supported only in ICS mode,
866          or not at all.  Some that are accepted here nevertheless
867          have bugs; see comments below.
868       */
869       VariantClass variant = StringToVariant(appData.variant);
870       switch (variant) {
871       case VariantBughouse:     /* need four players and two boards */
872       case VariantKriegspiel:   /* need to hide pieces and move details */
873       /* case VariantFischeRandom: (Fabien: moved below) */
874         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
875         DisplayFatalError(buf, 0, 2);
876         return;
877
878       case VariantUnknown:
879       case VariantLoadable:
880       case Variant29:
881       case Variant30:
882       case Variant31:
883       case Variant32:
884       case Variant33:
885       case Variant34:
886       case Variant35:
887       case Variant36:
888       default:
889         sprintf(buf, _("Unknown variant name %s"), appData.variant);
890         DisplayFatalError(buf, 0, 2);
891         return;
892
893       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
894       case VariantFairy:      /* [HGM] TestLegality definitely off! */
895       case VariantGothic:     /* [HGM] should work */
896       case VariantCapablanca: /* [HGM] should work */
897       case VariantCourier:    /* [HGM] initial forced moves not implemented */
898       case VariantShogi:      /* [HGM] drops not tested for legality */
899       case VariantKnightmate: /* [HGM] should work */
900       case VariantCylinder:   /* [HGM] untested */
901       case VariantFalcon:     /* [HGM] untested */
902       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
903                                  offboard interposition not understood */
904       case VariantNormal:     /* definitely works! */
905       case VariantWildCastle: /* pieces not automatically shuffled */
906       case VariantNoCastle:   /* pieces not automatically shuffled */
907       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
908       case VariantLosers:     /* should work except for win condition,
909                                  and doesn't know captures are mandatory */
910       case VariantSuicide:    /* should work except for win condition,
911                                  and doesn't know captures are mandatory */
912       case VariantGiveaway:   /* should work except for win condition,
913                                  and doesn't know captures are mandatory */
914       case VariantTwoKings:   /* should work */
915       case VariantAtomic:     /* should work except for win condition */
916       case Variant3Check:     /* should work except for win condition */
917       case VariantShatranj:   /* should work except for all win conditions */
918       case VariantMakruk:     /* should work except for daw countdown */
919       case VariantBerolina:   /* might work if TestLegality is off */
920       case VariantCapaRandom: /* should work */
921       case VariantJanus:      /* should work */
922       case VariantSuper:      /* experimental */
923       case VariantGreat:      /* experimental, requires legality testing to be off */
924         break;
925       }
926     }
927
928     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
929     InitEngineUCI( installDir, &second );
930 }
931
932 int NextIntegerFromString( char ** str, long * value )
933 {
934     int result = -1;
935     char * s = *str;
936
937     while( *s == ' ' || *s == '\t' ) {
938         s++;
939     }
940
941     *value = 0;
942
943     if( *s >= '0' && *s <= '9' ) {
944         while( *s >= '0' && *s <= '9' ) {
945             *value = *value * 10 + (*s - '0');
946             s++;
947         }
948
949         result = 0;
950     }
951
952     *str = s;
953
954     return result;
955 }
956
957 int NextTimeControlFromString( char ** str, long * value )
958 {
959     long temp;
960     int result = NextIntegerFromString( str, &temp );
961
962     if( result == 0 ) {
963         *value = temp * 60; /* Minutes */
964         if( **str == ':' ) {
965             (*str)++;
966             result = NextIntegerFromString( str, &temp );
967             *value += temp; /* Seconds */
968         }
969     }
970
971     return result;
972 }
973
974 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
975 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
976     int result = -1; long temp, temp2;
977
978     if(**str != '+') return -1; // old params remain in force!
979     (*str)++;
980     if( NextTimeControlFromString( str, &temp ) ) return -1;
981
982     if(**str != '/') {
983         /* time only: incremental or sudden-death time control */
984         if(**str == '+') { /* increment follows; read it */
985             (*str)++;
986             if(result = NextIntegerFromString( str, &temp2)) return -1;
987             *inc = temp2 * 1000;
988         } else *inc = 0;
989         *moves = 0; *tc = temp * 1000; 
990         return 0;
991     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
992
993     (*str)++; /* classical time control */
994     result = NextTimeControlFromString( str, &temp2);
995     if(result == 0) {
996         *moves = temp/60;
997         *tc    = temp2 * 1000;
998         *inc   = 0;
999     }
1000     return result;
1001 }
1002
1003 int GetTimeQuota(int movenr)
1004 {   /* [HGM] get time to add from the multi-session time-control string */
1005     int moves=1; /* kludge to force reading of first session */
1006     long time, increment;
1007     char *s = fullTimeControlString;
1008
1009     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1010     do {
1011         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1012         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1013         if(movenr == -1) return time;    /* last move before new session     */
1014         if(!moves) return increment;     /* current session is incremental   */
1015         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1016     } while(movenr >= -1);               /* try again for next session       */
1017
1018     return 0; // no new time quota on this move
1019 }
1020
1021 int
1022 ParseTimeControl(tc, ti, mps)
1023      char *tc;
1024      int ti;
1025      int mps;
1026 {
1027   long tc1;
1028   long tc2;
1029   char buf[MSG_SIZ];
1030   
1031   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1032   if(ti > 0) {
1033     if(mps)
1034       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1035     else sprintf(buf, "+%s+%d", tc, ti);
1036   } else {
1037     if(mps)
1038              sprintf(buf, "+%d/%s", mps, tc);
1039     else sprintf(buf, "+%s", tc);
1040   }
1041   fullTimeControlString = StrSave(buf);
1042   
1043   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1044     return FALSE;
1045   }
1046   
1047   if( *tc == '/' ) {
1048     /* Parse second time control */
1049     tc++;
1050     
1051     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1052       return FALSE;
1053     }
1054     
1055     if( tc2 == 0 ) {
1056       return FALSE;
1057     }
1058     
1059     timeControl_2 = tc2 * 1000;
1060   }
1061   else {
1062     timeControl_2 = 0;
1063   }
1064   
1065   if( tc1 == 0 ) {
1066     return FALSE;
1067   }
1068   
1069   timeControl = tc1 * 1000;
1070   
1071   if (ti >= 0) {
1072     timeIncrement = ti * 1000;  /* convert to ms */
1073     movesPerSession = 0;
1074   } else {
1075     timeIncrement = 0;
1076     movesPerSession = mps;
1077   }
1078   return TRUE;
1079 }
1080
1081 void
1082 InitBackEnd2()
1083 {
1084     if (appData.debugMode) {
1085         fprintf(debugFP, "%s\n", programVersion);
1086     }
1087
1088     set_cont_sequence(appData.wrapContSeq);
1089     if (appData.matchGames > 0) {
1090         appData.matchMode = TRUE;
1091     } else if (appData.matchMode) {
1092         appData.matchGames = 1;
1093     }
1094     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1095         appData.matchGames = appData.sameColorGames;
1096     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1097         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1098         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1099     }
1100     Reset(TRUE, FALSE);
1101     if (appData.noChessProgram || first.protocolVersion == 1) {
1102       InitBackEnd3();
1103     } else {
1104       /* kludge: allow timeout for initial "feature" commands */
1105       FreezeUI();
1106       DisplayMessage("", _("Starting chess program"));
1107       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1108     }
1109 }
1110
1111 void
1112 InitBackEnd3 P((void))
1113 {
1114     GameMode initialMode;
1115     char buf[MSG_SIZ];
1116     int err;
1117
1118     InitChessProgram(&first, startedFromSetupPosition);
1119
1120     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1121         free(programVersion);
1122         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1123         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1124     }
1125
1126     if (appData.icsActive) {
1127 #ifdef WIN32
1128         /* [DM] Make a console window if needed [HGM] merged ifs */
1129         ConsoleCreate(); 
1130 #endif
1131         err = establish();
1132         if (err != 0) {
1133             if (*appData.icsCommPort != NULLCHAR) {
1134                 sprintf(buf, _("Could not open comm port %s"),  
1135                         appData.icsCommPort);
1136             } else {
1137                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1138                         appData.icsHost, appData.icsPort);
1139             }
1140             DisplayFatalError(buf, err, 1);
1141             return;
1142         }
1143         SetICSMode();
1144         telnetISR =
1145           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1146         fromUserISR =
1147           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1148         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1149             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1150     } else if (appData.noChessProgram) {
1151         SetNCPMode();
1152     } else {
1153         SetGNUMode();
1154     }
1155
1156     if (*appData.cmailGameName != NULLCHAR) {
1157         SetCmailMode();
1158         OpenLoopback(&cmailPR);
1159         cmailISR =
1160           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1161     }
1162     
1163     ThawUI();
1164     DisplayMessage("", "");
1165     if (StrCaseCmp(appData.initialMode, "") == 0) {
1166       initialMode = BeginningOfGame;
1167     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1168       initialMode = TwoMachinesPlay;
1169     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1170       initialMode = AnalyzeFile; 
1171     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1172       initialMode = AnalyzeMode;
1173     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1174       initialMode = MachinePlaysWhite;
1175     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1176       initialMode = MachinePlaysBlack;
1177     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1178       initialMode = EditGame;
1179     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1180       initialMode = EditPosition;
1181     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1182       initialMode = Training;
1183     } else {
1184       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1185       DisplayFatalError(buf, 0, 2);
1186       return;
1187     }
1188
1189     if (appData.matchMode) {
1190         /* Set up machine vs. machine match */
1191         if (appData.noChessProgram) {
1192             DisplayFatalError(_("Can't have a match with no chess programs"),
1193                               0, 2);
1194             return;
1195         }
1196         matchMode = TRUE;
1197         matchGame = 1;
1198         if (*appData.loadGameFile != NULLCHAR) {
1199             int index = appData.loadGameIndex; // [HGM] autoinc
1200             if(index<0) lastIndex = index = 1;
1201             if (!LoadGameFromFile(appData.loadGameFile,
1202                                   index,
1203                                   appData.loadGameFile, FALSE)) {
1204                 DisplayFatalError(_("Bad game file"), 0, 1);
1205                 return;
1206             }
1207         } else if (*appData.loadPositionFile != NULLCHAR) {
1208             int index = appData.loadPositionIndex; // [HGM] autoinc
1209             if(index<0) lastIndex = index = 1;
1210             if (!LoadPositionFromFile(appData.loadPositionFile,
1211                                       index,
1212                                       appData.loadPositionFile)) {
1213                 DisplayFatalError(_("Bad position file"), 0, 1);
1214                 return;
1215             }
1216         }
1217         TwoMachinesEvent();
1218     } else if (*appData.cmailGameName != NULLCHAR) {
1219         /* Set up cmail mode */
1220         ReloadCmailMsgEvent(TRUE);
1221     } else {
1222         /* Set up other modes */
1223         if (initialMode == AnalyzeFile) {
1224           if (*appData.loadGameFile == NULLCHAR) {
1225             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1226             return;
1227           }
1228         }
1229         if (*appData.loadGameFile != NULLCHAR) {
1230             (void) LoadGameFromFile(appData.loadGameFile,
1231                                     appData.loadGameIndex,
1232                                     appData.loadGameFile, TRUE);
1233         } else if (*appData.loadPositionFile != NULLCHAR) {
1234             (void) LoadPositionFromFile(appData.loadPositionFile,
1235                                         appData.loadPositionIndex,
1236                                         appData.loadPositionFile);
1237             /* [HGM] try to make self-starting even after FEN load */
1238             /* to allow automatic setup of fairy variants with wtm */
1239             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1240                 gameMode = BeginningOfGame;
1241                 setboardSpoiledMachineBlack = 1;
1242             }
1243             /* [HGM] loadPos: make that every new game uses the setup */
1244             /* from file as long as we do not switch variant          */
1245             if(!blackPlaysFirst) {
1246                 startedFromPositionFile = TRUE;
1247                 CopyBoard(filePosition, boards[0]);
1248             }
1249         }
1250         if (initialMode == AnalyzeMode) {
1251           if (appData.noChessProgram) {
1252             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1253             return;
1254           }
1255           if (appData.icsActive) {
1256             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1257             return;
1258           }
1259           AnalyzeModeEvent();
1260         } else if (initialMode == AnalyzeFile) {
1261           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1262           ShowThinkingEvent();
1263           AnalyzeFileEvent();
1264           AnalysisPeriodicEvent(1);
1265         } else if (initialMode == MachinePlaysWhite) {
1266           if (appData.noChessProgram) {
1267             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1268                               0, 2);
1269             return;
1270           }
1271           if (appData.icsActive) {
1272             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1273                               0, 2);
1274             return;
1275           }
1276           MachineWhiteEvent();
1277         } else if (initialMode == MachinePlaysBlack) {
1278           if (appData.noChessProgram) {
1279             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1280                               0, 2);
1281             return;
1282           }
1283           if (appData.icsActive) {
1284             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1285                               0, 2);
1286             return;
1287           }
1288           MachineBlackEvent();
1289         } else if (initialMode == TwoMachinesPlay) {
1290           if (appData.noChessProgram) {
1291             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1292                               0, 2);
1293             return;
1294           }
1295           if (appData.icsActive) {
1296             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1297                               0, 2);
1298             return;
1299           }
1300           TwoMachinesEvent();
1301         } else if (initialMode == EditGame) {
1302           EditGameEvent();
1303         } else if (initialMode == EditPosition) {
1304           EditPositionEvent();
1305         } else if (initialMode == Training) {
1306           if (*appData.loadGameFile == NULLCHAR) {
1307             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1308             return;
1309           }
1310           TrainingEvent();
1311         }
1312     }
1313 }
1314
1315 /*
1316  * Establish will establish a contact to a remote host.port.
1317  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1318  *  used to talk to the host.
1319  * Returns 0 if okay, error code if not.
1320  */
1321 int
1322 establish()
1323 {
1324     char buf[MSG_SIZ];
1325
1326     if (*appData.icsCommPort != NULLCHAR) {
1327         /* Talk to the host through a serial comm port */
1328         return OpenCommPort(appData.icsCommPort, &icsPR);
1329
1330     } else if (*appData.gateway != NULLCHAR) {
1331         if (*appData.remoteShell == NULLCHAR) {
1332             /* Use the rcmd protocol to run telnet program on a gateway host */
1333             snprintf(buf, sizeof(buf), "%s %s %s",
1334                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1335             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1336
1337         } else {
1338             /* Use the rsh program to run telnet program on a gateway host */
1339             if (*appData.remoteUser == NULLCHAR) {
1340                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1341                         appData.gateway, appData.telnetProgram,
1342                         appData.icsHost, appData.icsPort);
1343             } else {
1344                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1345                         appData.remoteShell, appData.gateway, 
1346                         appData.remoteUser, appData.telnetProgram,
1347                         appData.icsHost, appData.icsPort);
1348             }
1349             return StartChildProcess(buf, "", &icsPR);
1350
1351         }
1352     } else if (appData.useTelnet) {
1353         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1354
1355     } else {
1356         /* TCP socket interface differs somewhat between
1357            Unix and NT; handle details in the front end.
1358            */
1359         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1360     }
1361 }
1362
1363 void EscapeExpand(char *p, char *q)
1364 {       // [HGM] initstring: routine to shape up string arguments
1365         while(*p++ = *q++) if(p[-1] == '\\')
1366             switch(*q++) {
1367                 case 'n': p[-1] = '\n'; break;
1368                 case 'r': p[-1] = '\r'; break;
1369                 case 't': p[-1] = '\t'; break;
1370                 case '\\': p[-1] = '\\'; break;
1371                 case 0: *p = 0; return;
1372                 default: p[-1] = q[-1]; break;
1373             }
1374 }
1375
1376 void
1377 show_bytes(fp, buf, count)
1378      FILE *fp;
1379      char *buf;
1380      int count;
1381 {
1382     while (count--) {
1383         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1384             fprintf(fp, "\\%03o", *buf & 0xff);
1385         } else {
1386             putc(*buf, fp);
1387         }
1388         buf++;
1389     }
1390     fflush(fp);
1391 }
1392
1393 /* Returns an errno value */
1394 int
1395 OutputMaybeTelnet(pr, message, count, outError)
1396      ProcRef pr;
1397      char *message;
1398      int count;
1399      int *outError;
1400 {
1401     char buf[8192], *p, *q, *buflim;
1402     int left, newcount, outcount;
1403
1404     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1405         *appData.gateway != NULLCHAR) {
1406         if (appData.debugMode) {
1407             fprintf(debugFP, ">ICS: ");
1408             show_bytes(debugFP, message, count);
1409             fprintf(debugFP, "\n");
1410         }
1411         return OutputToProcess(pr, message, count, outError);
1412     }
1413
1414     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1415     p = message;
1416     q = buf;
1417     left = count;
1418     newcount = 0;
1419     while (left) {
1420         if (q >= buflim) {
1421             if (appData.debugMode) {
1422                 fprintf(debugFP, ">ICS: ");
1423                 show_bytes(debugFP, buf, newcount);
1424                 fprintf(debugFP, "\n");
1425             }
1426             outcount = OutputToProcess(pr, buf, newcount, outError);
1427             if (outcount < newcount) return -1; /* to be sure */
1428             q = buf;
1429             newcount = 0;
1430         }
1431         if (*p == '\n') {
1432             *q++ = '\r';
1433             newcount++;
1434         } else if (((unsigned char) *p) == TN_IAC) {
1435             *q++ = (char) TN_IAC;
1436             newcount ++;
1437         }
1438         *q++ = *p++;
1439         newcount++;
1440         left--;
1441     }
1442     if (appData.debugMode) {
1443         fprintf(debugFP, ">ICS: ");
1444         show_bytes(debugFP, buf, newcount);
1445         fprintf(debugFP, "\n");
1446     }
1447     outcount = OutputToProcess(pr, buf, newcount, outError);
1448     if (outcount < newcount) return -1; /* to be sure */
1449     return count;
1450 }
1451
1452 void
1453 read_from_player(isr, closure, message, count, error)
1454      InputSourceRef isr;
1455      VOIDSTAR closure;
1456      char *message;
1457      int count;
1458      int error;
1459 {
1460     int outError, outCount;
1461     static int gotEof = 0;
1462
1463     /* Pass data read from player on to ICS */
1464     if (count > 0) {
1465         gotEof = 0;
1466         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1467         if (outCount < count) {
1468             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1469         }
1470     } else if (count < 0) {
1471         RemoveInputSource(isr);
1472         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1473     } else if (gotEof++ > 0) {
1474         RemoveInputSource(isr);
1475         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1476     }
1477 }
1478
1479 void
1480 KeepAlive()
1481 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1482     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1483     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1484     SendToICS("date\n");
1485     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1486 }
1487
1488 /* added routine for printf style output to ics */
1489 void ics_printf(char *format, ...)
1490 {
1491     char buffer[MSG_SIZ];
1492     va_list args;
1493
1494     va_start(args, format);
1495     vsnprintf(buffer, sizeof(buffer), format, args);
1496     buffer[sizeof(buffer)-1] = '\0';
1497     SendToICS(buffer);
1498     va_end(args);
1499 }
1500
1501 void
1502 SendToICS(s)
1503      char *s;
1504 {
1505     int count, outCount, outError;
1506
1507     if (icsPR == NULL) return;
1508
1509     count = strlen(s);
1510     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1511     if (outCount < count) {
1512         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1513     }
1514 }
1515
1516 /* This is used for sending logon scripts to the ICS. Sending
1517    without a delay causes problems when using timestamp on ICC
1518    (at least on my machine). */
1519 void
1520 SendToICSDelayed(s,msdelay)
1521      char *s;
1522      long msdelay;
1523 {
1524     int count, outCount, outError;
1525
1526     if (icsPR == NULL) return;
1527
1528     count = strlen(s);
1529     if (appData.debugMode) {
1530         fprintf(debugFP, ">ICS: ");
1531         show_bytes(debugFP, s, count);
1532         fprintf(debugFP, "\n");
1533     }
1534     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1535                                       msdelay);
1536     if (outCount < count) {
1537         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1538     }
1539 }
1540
1541
1542 /* Remove all highlighting escape sequences in s
1543    Also deletes any suffix starting with '(' 
1544    */
1545 char *
1546 StripHighlightAndTitle(s)
1547      char *s;
1548 {
1549     static char retbuf[MSG_SIZ];
1550     char *p = retbuf;
1551
1552     while (*s != NULLCHAR) {
1553         while (*s == '\033') {
1554             while (*s != NULLCHAR && !isalpha(*s)) s++;
1555             if (*s != NULLCHAR) s++;
1556         }
1557         while (*s != NULLCHAR && *s != '\033') {
1558             if (*s == '(' || *s == '[') {
1559                 *p = NULLCHAR;
1560                 return retbuf;
1561             }
1562             *p++ = *s++;
1563         }
1564     }
1565     *p = NULLCHAR;
1566     return retbuf;
1567 }
1568
1569 /* Remove all highlighting escape sequences in s */
1570 char *
1571 StripHighlight(s)
1572      char *s;
1573 {
1574     static char retbuf[MSG_SIZ];
1575     char *p = retbuf;
1576
1577     while (*s != NULLCHAR) {
1578         while (*s == '\033') {
1579             while (*s != NULLCHAR && !isalpha(*s)) s++;
1580             if (*s != NULLCHAR) s++;
1581         }
1582         while (*s != NULLCHAR && *s != '\033') {
1583             *p++ = *s++;
1584         }
1585     }
1586     *p = NULLCHAR;
1587     return retbuf;
1588 }
1589
1590 char *variantNames[] = VARIANT_NAMES;
1591 char *
1592 VariantName(v)
1593      VariantClass v;
1594 {
1595     return variantNames[v];
1596 }
1597
1598
1599 /* Identify a variant from the strings the chess servers use or the
1600    PGN Variant tag names we use. */
1601 VariantClass
1602 StringToVariant(e)
1603      char *e;
1604 {
1605     char *p;
1606     int wnum = -1;
1607     VariantClass v = VariantNormal;
1608     int i, found = FALSE;
1609     char buf[MSG_SIZ];
1610
1611     if (!e) return v;
1612
1613     /* [HGM] skip over optional board-size prefixes */
1614     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1615         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1616         while( *e++ != '_');
1617     }
1618
1619     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1620         v = VariantNormal;
1621         found = TRUE;
1622     } else
1623     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1624       if (StrCaseStr(e, variantNames[i])) {
1625         v = (VariantClass) i;
1626         found = TRUE;
1627         break;
1628       }
1629     }
1630
1631     if (!found) {
1632       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1633           || StrCaseStr(e, "wild/fr") 
1634           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1635         v = VariantFischeRandom;
1636       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1637                  (i = 1, p = StrCaseStr(e, "w"))) {
1638         p += i;
1639         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1640         if (isdigit(*p)) {
1641           wnum = atoi(p);
1642         } else {
1643           wnum = -1;
1644         }
1645         switch (wnum) {
1646         case 0: /* FICS only, actually */
1647         case 1:
1648           /* Castling legal even if K starts on d-file */
1649           v = VariantWildCastle;
1650           break;
1651         case 2:
1652         case 3:
1653         case 4:
1654           /* Castling illegal even if K & R happen to start in
1655              normal positions. */
1656           v = VariantNoCastle;
1657           break;
1658         case 5:
1659         case 7:
1660         case 8:
1661         case 10:
1662         case 11:
1663         case 12:
1664         case 13:
1665         case 14:
1666         case 15:
1667         case 18:
1668         case 19:
1669           /* Castling legal iff K & R start in normal positions */
1670           v = VariantNormal;
1671           break;
1672         case 6:
1673         case 20:
1674         case 21:
1675           /* Special wilds for position setup; unclear what to do here */
1676           v = VariantLoadable;
1677           break;
1678         case 9:
1679           /* Bizarre ICC game */
1680           v = VariantTwoKings;
1681           break;
1682         case 16:
1683           v = VariantKriegspiel;
1684           break;
1685         case 17:
1686           v = VariantLosers;
1687           break;
1688         case 22:
1689           v = VariantFischeRandom;
1690           break;
1691         case 23:
1692           v = VariantCrazyhouse;
1693           break;
1694         case 24:
1695           v = VariantBughouse;
1696           break;
1697         case 25:
1698           v = Variant3Check;
1699           break;
1700         case 26:
1701           /* Not quite the same as FICS suicide! */
1702           v = VariantGiveaway;
1703           break;
1704         case 27:
1705           v = VariantAtomic;
1706           break;
1707         case 28:
1708           v = VariantShatranj;
1709           break;
1710
1711         /* Temporary names for future ICC types.  The name *will* change in 
1712            the next xboard/WinBoard release after ICC defines it. */
1713         case 29:
1714           v = Variant29;
1715           break;
1716         case 30:
1717           v = Variant30;
1718           break;
1719         case 31:
1720           v = Variant31;
1721           break;
1722         case 32:
1723           v = Variant32;
1724           break;
1725         case 33:
1726           v = Variant33;
1727           break;
1728         case 34:
1729           v = Variant34;
1730           break;
1731         case 35:
1732           v = Variant35;
1733           break;
1734         case 36:
1735           v = Variant36;
1736           break;
1737         case 37:
1738           v = VariantShogi;
1739           break;
1740         case 38:
1741           v = VariantXiangqi;
1742           break;
1743         case 39:
1744           v = VariantCourier;
1745           break;
1746         case 40:
1747           v = VariantGothic;
1748           break;
1749         case 41:
1750           v = VariantCapablanca;
1751           break;
1752         case 42:
1753           v = VariantKnightmate;
1754           break;
1755         case 43:
1756           v = VariantFairy;
1757           break;
1758         case 44:
1759           v = VariantCylinder;
1760           break;
1761         case 45:
1762           v = VariantFalcon;
1763           break;
1764         case 46:
1765           v = VariantCapaRandom;
1766           break;
1767         case 47:
1768           v = VariantBerolina;
1769           break;
1770         case 48:
1771           v = VariantJanus;
1772           break;
1773         case 49:
1774           v = VariantSuper;
1775           break;
1776         case 50:
1777           v = VariantGreat;
1778           break;
1779         case -1:
1780           /* Found "wild" or "w" in the string but no number;
1781              must assume it's normal chess. */
1782           v = VariantNormal;
1783           break;
1784         default:
1785           sprintf(buf, _("Unknown wild type %d"), wnum);
1786           DisplayError(buf, 0);
1787           v = VariantUnknown;
1788           break;
1789         }
1790       }
1791     }
1792     if (appData.debugMode) {
1793       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1794               e, wnum, VariantName(v));
1795     }
1796     return v;
1797 }
1798
1799 static int leftover_start = 0, leftover_len = 0;
1800 char star_match[STAR_MATCH_N][MSG_SIZ];
1801
1802 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1803    advance *index beyond it, and set leftover_start to the new value of
1804    *index; else return FALSE.  If pattern contains the character '*', it
1805    matches any sequence of characters not containing '\r', '\n', or the
1806    character following the '*' (if any), and the matched sequence(s) are
1807    copied into star_match.
1808    */
1809 int
1810 looking_at(buf, index, pattern)
1811      char *buf;
1812      int *index;
1813      char *pattern;
1814 {
1815     char *bufp = &buf[*index], *patternp = pattern;
1816     int star_count = 0;
1817     char *matchp = star_match[0];
1818     
1819     for (;;) {
1820         if (*patternp == NULLCHAR) {
1821             *index = leftover_start = bufp - buf;
1822             *matchp = NULLCHAR;
1823             return TRUE;
1824         }
1825         if (*bufp == NULLCHAR) return FALSE;
1826         if (*patternp == '*') {
1827             if (*bufp == *(patternp + 1)) {
1828                 *matchp = NULLCHAR;
1829                 matchp = star_match[++star_count];
1830                 patternp += 2;
1831                 bufp++;
1832                 continue;
1833             } else if (*bufp == '\n' || *bufp == '\r') {
1834                 patternp++;
1835                 if (*patternp == NULLCHAR)
1836                   continue;
1837                 else
1838                   return FALSE;
1839             } else {
1840                 *matchp++ = *bufp++;
1841                 continue;
1842             }
1843         }
1844         if (*patternp != *bufp) return FALSE;
1845         patternp++;
1846         bufp++;
1847     }
1848 }
1849
1850 void
1851 SendToPlayer(data, length)
1852      char *data;
1853      int length;
1854 {
1855     int error, outCount;
1856     outCount = OutputToProcess(NoProc, data, length, &error);
1857     if (outCount < length) {
1858         DisplayFatalError(_("Error writing to display"), error, 1);
1859     }
1860 }
1861
1862 void
1863 PackHolding(packed, holding)
1864      char packed[];
1865      char *holding;
1866 {
1867     char *p = holding;
1868     char *q = packed;
1869     int runlength = 0;
1870     int curr = 9999;
1871     do {
1872         if (*p == curr) {
1873             runlength++;
1874         } else {
1875             switch (runlength) {
1876               case 0:
1877                 break;
1878               case 1:
1879                 *q++ = curr;
1880                 break;
1881               case 2:
1882                 *q++ = curr;
1883                 *q++ = curr;
1884                 break;
1885               default:
1886                 sprintf(q, "%d", runlength);
1887                 while (*q) q++;
1888                 *q++ = curr;
1889                 break;
1890             }
1891             runlength = 1;
1892             curr = *p;
1893         }
1894     } while (*p++);
1895     *q = NULLCHAR;
1896 }
1897
1898 /* Telnet protocol requests from the front end */
1899 void
1900 TelnetRequest(ddww, option)
1901      unsigned char ddww, option;
1902 {
1903     unsigned char msg[3];
1904     int outCount, outError;
1905
1906     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1907
1908     if (appData.debugMode) {
1909         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1910         switch (ddww) {
1911           case TN_DO:
1912             ddwwStr = "DO";
1913             break;
1914           case TN_DONT:
1915             ddwwStr = "DONT";
1916             break;
1917           case TN_WILL:
1918             ddwwStr = "WILL";
1919             break;
1920           case TN_WONT:
1921             ddwwStr = "WONT";
1922             break;
1923           default:
1924             ddwwStr = buf1;
1925             sprintf(buf1, "%d", ddww);
1926             break;
1927         }
1928         switch (option) {
1929           case TN_ECHO:
1930             optionStr = "ECHO";
1931             break;
1932           default:
1933             optionStr = buf2;
1934             sprintf(buf2, "%d", option);
1935             break;
1936         }
1937         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1938     }
1939     msg[0] = TN_IAC;
1940     msg[1] = ddww;
1941     msg[2] = option;
1942     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1943     if (outCount < 3) {
1944         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1945     }
1946 }
1947
1948 void
1949 DoEcho()
1950 {
1951     if (!appData.icsActive) return;
1952     TelnetRequest(TN_DO, TN_ECHO);
1953 }
1954
1955 void
1956 DontEcho()
1957 {
1958     if (!appData.icsActive) return;
1959     TelnetRequest(TN_DONT, TN_ECHO);
1960 }
1961
1962 void
1963 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1964 {
1965     /* put the holdings sent to us by the server on the board holdings area */
1966     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1967     char p;
1968     ChessSquare piece;
1969
1970     if(gameInfo.holdingsWidth < 2)  return;
1971     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1972         return; // prevent overwriting by pre-board holdings
1973
1974     if( (int)lowestPiece >= BlackPawn ) {
1975         holdingsColumn = 0;
1976         countsColumn = 1;
1977         holdingsStartRow = BOARD_HEIGHT-1;
1978         direction = -1;
1979     } else {
1980         holdingsColumn = BOARD_WIDTH-1;
1981         countsColumn = BOARD_WIDTH-2;
1982         holdingsStartRow = 0;
1983         direction = 1;
1984     }
1985
1986     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1987         board[i][holdingsColumn] = EmptySquare;
1988         board[i][countsColumn]   = (ChessSquare) 0;
1989     }
1990     while( (p=*holdings++) != NULLCHAR ) {
1991         piece = CharToPiece( ToUpper(p) );
1992         if(piece == EmptySquare) continue;
1993         /*j = (int) piece - (int) WhitePawn;*/
1994         j = PieceToNumber(piece);
1995         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1996         if(j < 0) continue;               /* should not happen */
1997         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1998         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1999         board[holdingsStartRow+j*direction][countsColumn]++;
2000     }
2001 }
2002
2003
2004 void
2005 VariantSwitch(Board board, VariantClass newVariant)
2006 {
2007    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2008    static Board oldBoard;
2009
2010    startedFromPositionFile = FALSE;
2011    if(gameInfo.variant == newVariant) return;
2012
2013    /* [HGM] This routine is called each time an assignment is made to
2014     * gameInfo.variant during a game, to make sure the board sizes
2015     * are set to match the new variant. If that means adding or deleting
2016     * holdings, we shift the playing board accordingly
2017     * This kludge is needed because in ICS observe mode, we get boards
2018     * of an ongoing game without knowing the variant, and learn about the
2019     * latter only later. This can be because of the move list we requested,
2020     * in which case the game history is refilled from the beginning anyway,
2021     * but also when receiving holdings of a crazyhouse game. In the latter
2022     * case we want to add those holdings to the already received position.
2023     */
2024
2025    
2026    if (appData.debugMode) {
2027      fprintf(debugFP, "Switch board from %s to %s\n",
2028              VariantName(gameInfo.variant), VariantName(newVariant));
2029      setbuf(debugFP, NULL);
2030    }
2031    shuffleOpenings = 0;       /* [HGM] shuffle */
2032    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2033    switch(newVariant) 
2034      {
2035      case VariantShogi:
2036        newWidth = 9;  newHeight = 9;
2037        gameInfo.holdingsSize = 7;
2038      case VariantBughouse:
2039      case VariantCrazyhouse:
2040        newHoldingsWidth = 2; break;
2041      case VariantGreat:
2042        newWidth = 10;
2043      case VariantSuper:
2044        newHoldingsWidth = 2;
2045        gameInfo.holdingsSize = 8;
2046        break;
2047      case VariantGothic:
2048      case VariantCapablanca:
2049      case VariantCapaRandom:
2050        newWidth = 10;
2051      default:
2052        newHoldingsWidth = gameInfo.holdingsSize = 0;
2053      };
2054    
2055    if(newWidth  != gameInfo.boardWidth  ||
2056       newHeight != gameInfo.boardHeight ||
2057       newHoldingsWidth != gameInfo.holdingsWidth ) {
2058      
2059      /* shift position to new playing area, if needed */
2060      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2061        for(i=0; i<BOARD_HEIGHT; i++) 
2062          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2063            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2064              board[i][j];
2065        for(i=0; i<newHeight; i++) {
2066          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2067          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2068        }
2069      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2070        for(i=0; i<BOARD_HEIGHT; i++)
2071          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2072            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2073              board[i][j];
2074      }
2075      gameInfo.boardWidth  = newWidth;
2076      gameInfo.boardHeight = newHeight;
2077      gameInfo.holdingsWidth = newHoldingsWidth;
2078      gameInfo.variant = newVariant;
2079      InitDrawingSizes(-2, 0);
2080    } else gameInfo.variant = newVariant;
2081    CopyBoard(oldBoard, board);   // remember correctly formatted board
2082      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2083    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2084 }
2085
2086 static int loggedOn = FALSE;
2087
2088 /*-- Game start info cache: --*/
2089 int gs_gamenum;
2090 char gs_kind[MSG_SIZ];
2091 static char player1Name[128] = "";
2092 static char player2Name[128] = "";
2093 static char cont_seq[] = "\n\\   ";
2094 static int player1Rating = -1;
2095 static int player2Rating = -1;
2096 /*----------------------------*/
2097
2098 ColorClass curColor = ColorNormal;
2099 int suppressKibitz = 0;
2100
2101 // [HGM] seekgraph
2102 Boolean soughtPending = FALSE;
2103 Boolean seekGraphUp;
2104 #define MAX_SEEK_ADS 200
2105 #define SQUARE 0x80
2106 char *seekAdList[MAX_SEEK_ADS];
2107 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2108 float tcList[MAX_SEEK_ADS];
2109 char colorList[MAX_SEEK_ADS];
2110 int nrOfSeekAds = 0;
2111 int minRating = 1010, maxRating = 2800;
2112 int hMargin = 10, vMargin = 20, h, w;
2113 extern int squareSize, lineGap;
2114
2115 void
2116 PlotSeekAd(int i)
2117 {
2118         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2119         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2120         if(r < minRating+100 && r >=0 ) r = minRating+100;
2121         if(r > maxRating) r = maxRating;
2122         if(tc < 1.) tc = 1.;
2123         if(tc > 95.) tc = 95.;
2124         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2125         y = ((double)r - minRating)/(maxRating - minRating)
2126             * (h-vMargin-squareSize/8-1) + vMargin;
2127         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2128         if(strstr(seekAdList[i], " u ")) color = 1;
2129         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2130            !strstr(seekAdList[i], "bullet") &&
2131            !strstr(seekAdList[i], "blitz") &&
2132            !strstr(seekAdList[i], "standard") ) color = 2;
2133         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2134         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2135 }
2136
2137 void
2138 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2139 {
2140         char buf[MSG_SIZ], *ext = "";
2141         VariantClass v = StringToVariant(type);
2142         if(strstr(type, "wild")) {
2143             ext = type + 4; // append wild number
2144             if(v == VariantFischeRandom) type = "chess960"; else
2145             if(v == VariantLoadable) type = "setup"; else
2146             type = VariantName(v);
2147         }
2148         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2149         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2150             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2151             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2152             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2153             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2154             seekNrList[nrOfSeekAds] = nr;
2155             zList[nrOfSeekAds] = 0;
2156             seekAdList[nrOfSeekAds++] = StrSave(buf);
2157             if(plot) PlotSeekAd(nrOfSeekAds-1);
2158         }
2159 }
2160
2161 void
2162 EraseSeekDot(int i)
2163 {
2164     int x = xList[i], y = yList[i], d=squareSize/4, k;
2165     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2166     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2167     // now replot every dot that overlapped
2168     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2169         int xx = xList[k], yy = yList[k];
2170         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2171             DrawSeekDot(xx, yy, colorList[k]);
2172     }
2173 }
2174
2175 void
2176 RemoveSeekAd(int nr)
2177 {
2178         int i;
2179         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2180             EraseSeekDot(i);
2181             if(seekAdList[i]) free(seekAdList[i]);
2182             seekAdList[i] = seekAdList[--nrOfSeekAds];
2183             seekNrList[i] = seekNrList[nrOfSeekAds];
2184             ratingList[i] = ratingList[nrOfSeekAds];
2185             colorList[i]  = colorList[nrOfSeekAds];
2186             tcList[i] = tcList[nrOfSeekAds];
2187             xList[i]  = xList[nrOfSeekAds];
2188             yList[i]  = yList[nrOfSeekAds];
2189             zList[i]  = zList[nrOfSeekAds];
2190             seekAdList[nrOfSeekAds] = NULL;
2191             break;
2192         }
2193 }
2194
2195 Boolean
2196 MatchSoughtLine(char *line)
2197 {
2198     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2199     int nr, base, inc, u=0; char dummy;
2200
2201     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2202        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2203        (u=1) &&
2204        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2205         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2206         // match: compact and save the line
2207         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2208         return TRUE;
2209     }
2210     return FALSE;
2211 }
2212
2213 int
2214 DrawSeekGraph()
2215 {
2216     int i;
2217     if(!seekGraphUp) return FALSE;
2218     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2219     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2220
2221     DrawSeekBackground(0, 0, w, h);
2222     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2223     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2224     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2225         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2226         yy = h-1-yy;
2227         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2228         if(i%500 == 0) {
2229             char buf[MSG_SIZ];
2230             sprintf(buf, "%d", i);
2231             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2232         }
2233     }
2234     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2235     for(i=1; i<100; i+=(i<10?1:5)) {
2236         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2237         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2238         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2239             char buf[MSG_SIZ];
2240             sprintf(buf, "%d", i);
2241             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2242         }
2243     }
2244     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2245     return TRUE;
2246 }
2247
2248 int SeekGraphClick(ClickType click, int x, int y, int moving)
2249 {
2250     static int lastDown = 0, displayed = 0, lastSecond;
2251     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2252         if(click == Release || moving) return FALSE;
2253         nrOfSeekAds = 0;
2254         soughtPending = TRUE;
2255         SendToICS(ics_prefix);
2256         SendToICS("sought\n"); // should this be "sought all"?
2257     } else { // issue challenge based on clicked ad
2258         int dist = 10000; int i, closest = 0, second = 0;
2259         for(i=0; i<nrOfSeekAds; i++) {
2260             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2261             if(d < dist) { dist = d; closest = i; }
2262             second += (d - zList[i] < 120); // count in-range ads
2263             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2264         }
2265         if(dist < 120) {
2266             char buf[MSG_SIZ];
2267             second = (second > 1);
2268             if(displayed != closest || second != lastSecond) {
2269                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2270                 lastSecond = second; displayed = closest;
2271             }
2272             if(click == Press) {
2273                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2274                 lastDown = closest;
2275                 return TRUE;
2276             } // on press 'hit', only show info
2277             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2278             sprintf(buf, "play %d\n", seekNrList[closest]);
2279             SendToICS(ics_prefix);
2280             SendToICS(buf);
2281             return TRUE; // let incoming board of started game pop down the graph
2282         } else if(click == Release) { // release 'miss' is ignored
2283             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2284             if(moving == 2) { // right up-click
2285                 nrOfSeekAds = 0; // refresh graph
2286                 soughtPending = TRUE;
2287                 SendToICS(ics_prefix);
2288                 SendToICS("sought\n"); // should this be "sought all"?
2289             }
2290             return TRUE;
2291         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2292         // press miss or release hit 'pop down' seek graph
2293         seekGraphUp = FALSE;
2294         DrawPosition(TRUE, NULL);
2295     }
2296     return TRUE;
2297 }
2298
2299 void
2300 read_from_ics(isr, closure, data, count, error)
2301      InputSourceRef isr;
2302      VOIDSTAR closure;
2303      char *data;
2304      int count;
2305      int error;
2306 {
2307 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2308 #define STARTED_NONE 0
2309 #define STARTED_MOVES 1
2310 #define STARTED_BOARD 2
2311 #define STARTED_OBSERVE 3
2312 #define STARTED_HOLDINGS 4
2313 #define STARTED_CHATTER 5
2314 #define STARTED_COMMENT 6
2315 #define STARTED_MOVES_NOHIDE 7
2316     
2317     static int started = STARTED_NONE;
2318     static char parse[20000];
2319     static int parse_pos = 0;
2320     static char buf[BUF_SIZE + 1];
2321     static int firstTime = TRUE, intfSet = FALSE;
2322     static ColorClass prevColor = ColorNormal;
2323     static int savingComment = FALSE;
2324     static int cmatch = 0; // continuation sequence match
2325     char *bp;
2326     char str[500];
2327     int i, oldi;
2328     int buf_len;
2329     int next_out;
2330     int tkind;
2331     int backup;    /* [DM] For zippy color lines */
2332     char *p;
2333     char talker[MSG_SIZ]; // [HGM] chat
2334     int channel;
2335
2336     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2337
2338     if (appData.debugMode) {
2339       if (!error) {
2340         fprintf(debugFP, "<ICS: ");
2341         show_bytes(debugFP, data, count);
2342         fprintf(debugFP, "\n");
2343       }
2344     }
2345
2346     if (appData.debugMode) { int f = forwardMostMove;
2347         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2348                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2349                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2350     }
2351     if (count > 0) {
2352         /* If last read ended with a partial line that we couldn't parse,
2353            prepend it to the new read and try again. */
2354         if (leftover_len > 0) {
2355             for (i=0; i<leftover_len; i++)
2356               buf[i] = buf[leftover_start + i];
2357         }
2358
2359     /* copy new characters into the buffer */
2360     bp = buf + leftover_len;
2361     buf_len=leftover_len;
2362     for (i=0; i<count; i++)
2363     {
2364         // ignore these
2365         if (data[i] == '\r')
2366             continue;
2367
2368         // join lines split by ICS?
2369         if (!appData.noJoin)
2370         {
2371             /*
2372                 Joining just consists of finding matches against the
2373                 continuation sequence, and discarding that sequence
2374                 if found instead of copying it.  So, until a match
2375                 fails, there's nothing to do since it might be the
2376                 complete sequence, and thus, something we don't want
2377                 copied.
2378             */
2379             if (data[i] == cont_seq[cmatch])
2380             {
2381                 cmatch++;
2382                 if (cmatch == strlen(cont_seq))
2383                 {
2384                     cmatch = 0; // complete match.  just reset the counter
2385
2386                     /*
2387                         it's possible for the ICS to not include the space
2388                         at the end of the last word, making our [correct]
2389                         join operation fuse two separate words.  the server
2390                         does this when the space occurs at the width setting.
2391                     */
2392                     if (!buf_len || buf[buf_len-1] != ' ')
2393                     {
2394                         *bp++ = ' ';
2395                         buf_len++;
2396                     }
2397                 }
2398                 continue;
2399             }
2400             else if (cmatch)
2401             {
2402                 /*
2403                     match failed, so we have to copy what matched before
2404                     falling through and copying this character.  In reality,
2405                     this will only ever be just the newline character, but
2406                     it doesn't hurt to be precise.
2407                 */
2408                 strncpy(bp, cont_seq, cmatch);
2409                 bp += cmatch;
2410                 buf_len += cmatch;
2411                 cmatch = 0;
2412             }
2413         }
2414
2415         // copy this char
2416         *bp++ = data[i];
2417         buf_len++;
2418     }
2419
2420         buf[buf_len] = NULLCHAR;
2421 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2422         next_out = 0;
2423         leftover_start = 0;
2424         
2425         i = 0;
2426         while (i < buf_len) {
2427             /* Deal with part of the TELNET option negotiation
2428                protocol.  We refuse to do anything beyond the
2429                defaults, except that we allow the WILL ECHO option,
2430                which ICS uses to turn off password echoing when we are
2431                directly connected to it.  We reject this option
2432                if localLineEditing mode is on (always on in xboard)
2433                and we are talking to port 23, which might be a real
2434                telnet server that will try to keep WILL ECHO on permanently.
2435              */
2436             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2437                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2438                 unsigned char option;
2439                 oldi = i;
2440                 switch ((unsigned char) buf[++i]) {
2441                   case TN_WILL:
2442                     if (appData.debugMode)
2443                       fprintf(debugFP, "\n<WILL ");
2444                     switch (option = (unsigned char) buf[++i]) {
2445                       case TN_ECHO:
2446                         if (appData.debugMode)
2447                           fprintf(debugFP, "ECHO ");
2448                         /* Reply only if this is a change, according
2449                            to the protocol rules. */
2450                         if (remoteEchoOption) break;
2451                         if (appData.localLineEditing &&
2452                             atoi(appData.icsPort) == TN_PORT) {
2453                             TelnetRequest(TN_DONT, TN_ECHO);
2454                         } else {
2455                             EchoOff();
2456                             TelnetRequest(TN_DO, TN_ECHO);
2457                             remoteEchoOption = TRUE;
2458                         }
2459                         break;
2460                       default:
2461                         if (appData.debugMode)
2462                           fprintf(debugFP, "%d ", option);
2463                         /* Whatever this is, we don't want it. */
2464                         TelnetRequest(TN_DONT, option);
2465                         break;
2466                     }
2467                     break;
2468                   case TN_WONT:
2469                     if (appData.debugMode)
2470                       fprintf(debugFP, "\n<WONT ");
2471                     switch (option = (unsigned char) buf[++i]) {
2472                       case TN_ECHO:
2473                         if (appData.debugMode)
2474                           fprintf(debugFP, "ECHO ");
2475                         /* Reply only if this is a change, according
2476                            to the protocol rules. */
2477                         if (!remoteEchoOption) break;
2478                         EchoOn();
2479                         TelnetRequest(TN_DONT, TN_ECHO);
2480                         remoteEchoOption = FALSE;
2481                         break;
2482                       default:
2483                         if (appData.debugMode)
2484                           fprintf(debugFP, "%d ", (unsigned char) option);
2485                         /* Whatever this is, it must already be turned
2486                            off, because we never agree to turn on
2487                            anything non-default, so according to the
2488                            protocol rules, we don't reply. */
2489                         break;
2490                     }
2491                     break;
2492                   case TN_DO:
2493                     if (appData.debugMode)
2494                       fprintf(debugFP, "\n<DO ");
2495                     switch (option = (unsigned char) buf[++i]) {
2496                       default:
2497                         /* Whatever this is, we refuse to do it. */
2498                         if (appData.debugMode)
2499                           fprintf(debugFP, "%d ", option);
2500                         TelnetRequest(TN_WONT, option);
2501                         break;
2502                     }
2503                     break;
2504                   case TN_DONT:
2505                     if (appData.debugMode)
2506                       fprintf(debugFP, "\n<DONT ");
2507                     switch (option = (unsigned char) buf[++i]) {
2508                       default:
2509                         if (appData.debugMode)
2510                           fprintf(debugFP, "%d ", option);
2511                         /* Whatever this is, we are already not doing
2512                            it, because we never agree to do anything
2513                            non-default, so according to the protocol
2514                            rules, we don't reply. */
2515                         break;
2516                     }
2517                     break;
2518                   case TN_IAC:
2519                     if (appData.debugMode)
2520                       fprintf(debugFP, "\n<IAC ");
2521                     /* Doubled IAC; pass it through */
2522                     i--;
2523                     break;
2524                   default:
2525                     if (appData.debugMode)
2526                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2527                     /* Drop all other telnet commands on the floor */
2528                     break;
2529                 }
2530                 if (oldi > next_out)
2531                   SendToPlayer(&buf[next_out], oldi - next_out);
2532                 if (++i > next_out)
2533                   next_out = i;
2534                 continue;
2535             }
2536                 
2537             /* OK, this at least will *usually* work */
2538             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2539                 loggedOn = TRUE;
2540             }
2541             
2542             if (loggedOn && !intfSet) {
2543                 if (ics_type == ICS_ICC) {
2544                   sprintf(str,
2545                           "/set-quietly interface %s\n/set-quietly style 12\n",
2546                           programVersion);
2547                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2548                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2549                 } else if (ics_type == ICS_CHESSNET) {
2550                   sprintf(str, "/style 12\n");
2551                 } else {
2552                   strcpy(str, "alias $ @\n$set interface ");
2553                   strcat(str, programVersion);
2554                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2555                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2556                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2557 #ifdef WIN32
2558                   strcat(str, "$iset nohighlight 1\n");
2559 #endif
2560                   strcat(str, "$iset lock 1\n$style 12\n");
2561                 }
2562                 SendToICS(str);
2563                 NotifyFrontendLogin();
2564                 intfSet = TRUE;
2565             }
2566
2567             if (started == STARTED_COMMENT) {
2568                 /* Accumulate characters in comment */
2569                 parse[parse_pos++] = buf[i];
2570                 if (buf[i] == '\n') {
2571                     parse[parse_pos] = NULLCHAR;
2572                     if(chattingPartner>=0) {
2573                         char mess[MSG_SIZ];
2574                         sprintf(mess, "%s%s", talker, parse);
2575                         OutputChatMessage(chattingPartner, mess);
2576                         chattingPartner = -1;
2577                         next_out = i+1; // [HGM] suppress printing in ICS window
2578                     } else
2579                     if(!suppressKibitz) // [HGM] kibitz
2580                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2581                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2582                         int nrDigit = 0, nrAlph = 0, j;
2583                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2584                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2585                         parse[parse_pos] = NULLCHAR;
2586                         // try to be smart: if it does not look like search info, it should go to
2587                         // ICS interaction window after all, not to engine-output window.
2588                         for(j=0; j<parse_pos; j++) { // count letters and digits
2589                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2590                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2591                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2592                         }
2593                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2594                             int depth=0; float score;
2595                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2596                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2597                                 pvInfoList[forwardMostMove-1].depth = depth;
2598                                 pvInfoList[forwardMostMove-1].score = 100*score;
2599                             }
2600                             OutputKibitz(suppressKibitz, parse);
2601                         } else {
2602                             char tmp[MSG_SIZ];
2603                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2604                             SendToPlayer(tmp, strlen(tmp));
2605                         }
2606                         next_out = i+1; // [HGM] suppress printing in ICS window
2607                     }
2608                     started = STARTED_NONE;
2609                 } else {
2610                     /* Don't match patterns against characters in comment */
2611                     i++;
2612                     continue;
2613                 }
2614             }
2615             if (started == STARTED_CHATTER) {
2616                 if (buf[i] != '\n') {
2617                     /* Don't match patterns against characters in chatter */
2618                     i++;
2619                     continue;
2620                 }
2621                 started = STARTED_NONE;
2622                 if(suppressKibitz) next_out = i+1;
2623             }
2624
2625             /* Kludge to deal with rcmd protocol */
2626             if (firstTime && looking_at(buf, &i, "\001*")) {
2627                 DisplayFatalError(&buf[1], 0, 1);
2628                 continue;
2629             } else {
2630                 firstTime = FALSE;
2631             }
2632
2633             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2634                 ics_type = ICS_ICC;
2635                 ics_prefix = "/";
2636                 if (appData.debugMode)
2637                   fprintf(debugFP, "ics_type %d\n", ics_type);
2638                 continue;
2639             }
2640             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2641                 ics_type = ICS_FICS;
2642                 ics_prefix = "$";
2643                 if (appData.debugMode)
2644                   fprintf(debugFP, "ics_type %d\n", ics_type);
2645                 continue;
2646             }
2647             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2648                 ics_type = ICS_CHESSNET;
2649                 ics_prefix = "/";
2650                 if (appData.debugMode)
2651                   fprintf(debugFP, "ics_type %d\n", ics_type);
2652                 continue;
2653             }
2654
2655             if (!loggedOn &&
2656                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2657                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2658                  looking_at(buf, &i, "will be \"*\""))) {
2659               strcpy(ics_handle, star_match[0]);
2660               continue;
2661             }
2662
2663             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2664               char buf[MSG_SIZ];
2665               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2666               DisplayIcsInteractionTitle(buf);
2667               have_set_title = TRUE;
2668             }
2669
2670             /* skip finger notes */
2671             if (started == STARTED_NONE &&
2672                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2673                  (buf[i] == '1' && buf[i+1] == '0')) &&
2674                 buf[i+2] == ':' && buf[i+3] == ' ') {
2675               started = STARTED_CHATTER;
2676               i += 3;
2677               continue;
2678             }
2679
2680             oldi = i;
2681             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2682             if(appData.seekGraph) {
2683                 if(soughtPending && MatchSoughtLine(buf+i)) {
2684                     i = strstr(buf+i, "rated") - buf;
2685                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2686                     next_out = leftover_start = i;
2687                     started = STARTED_CHATTER;
2688                     suppressKibitz = TRUE;
2689                     continue;
2690                 }
2691                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2692                         && looking_at(buf, &i, "* ads displayed")) {
2693                     soughtPending = FALSE;
2694                     seekGraphUp = TRUE;
2695                     DrawSeekGraph();
2696                     continue;
2697                 }
2698                 if(appData.autoRefresh) {
2699                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2700                         int s = (ics_type == ICS_ICC); // ICC format differs
2701                         if(seekGraphUp)
2702                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2703                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2704                         looking_at(buf, &i, "*% "); // eat prompt
2705                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2706                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2707                         next_out = i; // suppress
2708                         continue;
2709                     }
2710                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2711                         char *p = star_match[0];
2712                         while(*p) {
2713                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2714                             while(*p && *p++ != ' '); // next
2715                         }
2716                         looking_at(buf, &i, "*% "); // eat prompt
2717                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2718                         next_out = i;
2719                         continue;
2720                     }
2721                 }
2722             }
2723
2724             /* skip formula vars */
2725             if (started == STARTED_NONE &&
2726                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2727               started = STARTED_CHATTER;
2728               i += 3;
2729               continue;
2730             }
2731
2732             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2733             if (appData.autoKibitz && started == STARTED_NONE && 
2734                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2735                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2736                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2737                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2738                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2739                         suppressKibitz = TRUE;
2740                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2741                         next_out = i;
2742                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2743                                 && (gameMode == IcsPlayingWhite)) ||
2744                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2745                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2746                             started = STARTED_CHATTER; // own kibitz we simply discard
2747                         else {
2748                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2749                             parse_pos = 0; parse[0] = NULLCHAR;
2750                             savingComment = TRUE;
2751                             suppressKibitz = gameMode != IcsObserving ? 2 :
2752                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2753                         } 
2754                         continue;
2755                 } else
2756                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2757                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2758                          && atoi(star_match[0])) {
2759                     // suppress the acknowledgements of our own autoKibitz
2760                     char *p;
2761                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2762                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2763                     SendToPlayer(star_match[0], strlen(star_match[0]));
2764                     if(looking_at(buf, &i, "*% ")) // eat prompt
2765                         suppressKibitz = FALSE;
2766                     next_out = i;
2767                     continue;
2768                 }
2769             } // [HGM] kibitz: end of patch
2770
2771             // [HGM] chat: intercept tells by users for which we have an open chat window
2772             channel = -1;
2773             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2774                                            looking_at(buf, &i, "* whispers:") ||
2775                                            looking_at(buf, &i, "* kibitzes:") ||
2776                                            looking_at(buf, &i, "* shouts:") ||
2777                                            looking_at(buf, &i, "* c-shouts:") ||
2778                                            looking_at(buf, &i, "--> * ") ||
2779                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2780                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2781                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2782                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2783                 int p;
2784                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2785                 chattingPartner = -1;
2786
2787                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2788                 for(p=0; p<MAX_CHAT; p++) {
2789                     if(channel == atoi(chatPartner[p])) {
2790                     talker[0] = '['; strcat(talker, "] ");
2791                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2792                     chattingPartner = p; break;
2793                     }
2794                 } else
2795                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2796                 for(p=0; p<MAX_CHAT; p++) {
2797                     if(!strcmp("kibitzes", chatPartner[p])) {
2798                         talker[0] = '['; strcat(talker, "] ");
2799                         chattingPartner = p; break;
2800                     }
2801                 } else
2802                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2803                 for(p=0; p<MAX_CHAT; p++) {
2804                     if(!strcmp("whispers", chatPartner[p])) {
2805                         talker[0] = '['; strcat(talker, "] ");
2806                         chattingPartner = p; break;
2807                     }
2808                 } else
2809                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2810                   if(buf[i-8] == '-' && buf[i-3] == 't')
2811                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2812                     if(!strcmp("c-shouts", chatPartner[p])) {
2813                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2814                         chattingPartner = p; break;
2815                     }
2816                   }
2817                   if(chattingPartner < 0)
2818                   for(p=0; p<MAX_CHAT; p++) {
2819                     if(!strcmp("shouts", chatPartner[p])) {
2820                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2821                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2822                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2823                         chattingPartner = p; break;
2824                     }
2825                   }
2826                 }
2827                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2828                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2829                     talker[0] = 0; Colorize(ColorTell, FALSE);
2830                     chattingPartner = p; break;
2831                 }
2832                 if(chattingPartner<0) i = oldi; else {
2833                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2834                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2835                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2836                     started = STARTED_COMMENT;
2837                     parse_pos = 0; parse[0] = NULLCHAR;
2838                     savingComment = 3 + chattingPartner; // counts as TRUE
2839                     suppressKibitz = TRUE;
2840                     continue;
2841                 }
2842             } // [HGM] chat: end of patch
2843
2844             if (appData.zippyTalk || appData.zippyPlay) {
2845                 /* [DM] Backup address for color zippy lines */
2846                 backup = i;
2847 #if ZIPPY
2848                if (loggedOn == TRUE)
2849                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2850                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2851 #endif
2852             } // [DM] 'else { ' deleted
2853                 if (
2854                     /* Regular tells and says */
2855                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2856                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2857                     looking_at(buf, &i, "* says: ") ||
2858                     /* Don't color "message" or "messages" output */
2859                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2860                     looking_at(buf, &i, "*. * at *:*: ") ||
2861                     looking_at(buf, &i, "--* (*:*): ") ||
2862                     /* Message notifications (same color as tells) */
2863                     looking_at(buf, &i, "* has left a message ") ||
2864                     looking_at(buf, &i, "* just sent you a message:\n") ||
2865                     /* Whispers and kibitzes */
2866                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2867                     looking_at(buf, &i, "* kibitzes: ") ||
2868                     /* Channel tells */
2869                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2870
2871                   if (tkind == 1 && strchr(star_match[0], ':')) {
2872                       /* Avoid "tells you:" spoofs in channels */
2873                      tkind = 3;
2874                   }
2875                   if (star_match[0][0] == NULLCHAR ||
2876                       strchr(star_match[0], ' ') ||
2877                       (tkind == 3 && strchr(star_match[1], ' '))) {
2878                     /* Reject bogus matches */
2879                     i = oldi;
2880                   } else {
2881                     if (appData.colorize) {
2882                       if (oldi > next_out) {
2883                         SendToPlayer(&buf[next_out], oldi - next_out);
2884                         next_out = oldi;
2885                       }
2886                       switch (tkind) {
2887                       case 1:
2888                         Colorize(ColorTell, FALSE);
2889                         curColor = ColorTell;
2890                         break;
2891                       case 2:
2892                         Colorize(ColorKibitz, FALSE);
2893                         curColor = ColorKibitz;
2894                         break;
2895                       case 3:
2896                         p = strrchr(star_match[1], '(');
2897                         if (p == NULL) {
2898                           p = star_match[1];
2899                         } else {
2900                           p++;
2901                         }
2902                         if (atoi(p) == 1) {
2903                           Colorize(ColorChannel1, FALSE);
2904                           curColor = ColorChannel1;
2905                         } else {
2906                           Colorize(ColorChannel, FALSE);
2907                           curColor = ColorChannel;
2908                         }
2909                         break;
2910                       case 5:
2911                         curColor = ColorNormal;
2912                         break;
2913                       }
2914                     }
2915                     if (started == STARTED_NONE && appData.autoComment &&
2916                         (gameMode == IcsObserving ||
2917                          gameMode == IcsPlayingWhite ||
2918                          gameMode == IcsPlayingBlack)) {
2919                       parse_pos = i - oldi;
2920                       memcpy(parse, &buf[oldi], parse_pos);
2921                       parse[parse_pos] = NULLCHAR;
2922                       started = STARTED_COMMENT;
2923                       savingComment = TRUE;
2924                     } else {
2925                       started = STARTED_CHATTER;
2926                       savingComment = FALSE;
2927                     }
2928                     loggedOn = TRUE;
2929                     continue;
2930                   }
2931                 }
2932
2933                 if (looking_at(buf, &i, "* s-shouts: ") ||
2934                     looking_at(buf, &i, "* c-shouts: ")) {
2935                     if (appData.colorize) {
2936                         if (oldi > next_out) {
2937                             SendToPlayer(&buf[next_out], oldi - next_out);
2938                             next_out = oldi;
2939                         }
2940                         Colorize(ColorSShout, FALSE);
2941                         curColor = ColorSShout;
2942                     }
2943                     loggedOn = TRUE;
2944                     started = STARTED_CHATTER;
2945                     continue;
2946                 }
2947
2948                 if (looking_at(buf, &i, "--->")) {
2949                     loggedOn = TRUE;
2950                     continue;
2951                 }
2952
2953                 if (looking_at(buf, &i, "* shouts: ") ||
2954                     looking_at(buf, &i, "--> ")) {
2955                     if (appData.colorize) {
2956                         if (oldi > next_out) {
2957                             SendToPlayer(&buf[next_out], oldi - next_out);
2958                             next_out = oldi;
2959                         }
2960                         Colorize(ColorShout, FALSE);
2961                         curColor = ColorShout;
2962                     }
2963                     loggedOn = TRUE;
2964                     started = STARTED_CHATTER;
2965                     continue;
2966                 }
2967
2968                 if (looking_at( buf, &i, "Challenge:")) {
2969                     if (appData.colorize) {
2970                         if (oldi > next_out) {
2971                             SendToPlayer(&buf[next_out], oldi - next_out);
2972                             next_out = oldi;
2973                         }
2974                         Colorize(ColorChallenge, FALSE);
2975                         curColor = ColorChallenge;
2976                     }
2977                     loggedOn = TRUE;
2978                     continue;
2979                 }
2980
2981                 if (looking_at(buf, &i, "* offers you") ||
2982                     looking_at(buf, &i, "* offers to be") ||
2983                     looking_at(buf, &i, "* would like to") ||
2984                     looking_at(buf, &i, "* requests to") ||
2985                     looking_at(buf, &i, "Your opponent offers") ||
2986                     looking_at(buf, &i, "Your opponent requests")) {
2987
2988                     if (appData.colorize) {
2989                         if (oldi > next_out) {
2990                             SendToPlayer(&buf[next_out], oldi - next_out);
2991                             next_out = oldi;
2992                         }
2993                         Colorize(ColorRequest, FALSE);
2994                         curColor = ColorRequest;
2995                     }
2996                     continue;
2997                 }
2998
2999                 if (looking_at(buf, &i, "* (*) seeking")) {
3000                     if (appData.colorize) {
3001                         if (oldi > next_out) {
3002                             SendToPlayer(&buf[next_out], oldi - next_out);
3003                             next_out = oldi;
3004                         }
3005                         Colorize(ColorSeek, FALSE);
3006                         curColor = ColorSeek;
3007                     }
3008                     continue;
3009             }
3010
3011             if (looking_at(buf, &i, "\\   ")) {
3012                 if (prevColor != ColorNormal) {
3013                     if (oldi > next_out) {
3014                         SendToPlayer(&buf[next_out], oldi - next_out);
3015                         next_out = oldi;
3016                     }
3017                     Colorize(prevColor, TRUE);
3018                     curColor = prevColor;
3019                 }
3020                 if (savingComment) {
3021                     parse_pos = i - oldi;
3022                     memcpy(parse, &buf[oldi], parse_pos);
3023                     parse[parse_pos] = NULLCHAR;
3024                     started = STARTED_COMMENT;
3025                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3026                         chattingPartner = savingComment - 3; // kludge to remember the box
3027                 } else {
3028                     started = STARTED_CHATTER;
3029                 }
3030                 continue;
3031             }
3032
3033             if (looking_at(buf, &i, "Black Strength :") ||
3034                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3035                 looking_at(buf, &i, "<10>") ||
3036                 looking_at(buf, &i, "#@#")) {
3037                 /* Wrong board style */
3038                 loggedOn = TRUE;
3039                 SendToICS(ics_prefix);
3040                 SendToICS("set style 12\n");
3041                 SendToICS(ics_prefix);
3042                 SendToICS("refresh\n");
3043                 continue;
3044             }
3045             
3046             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3047                 ICSInitScript();
3048                 have_sent_ICS_logon = 1;
3049                 continue;
3050             }
3051               
3052             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3053                 (looking_at(buf, &i, "\n<12> ") ||
3054                  looking_at(buf, &i, "<12> "))) {
3055                 loggedOn = TRUE;
3056                 if (oldi > next_out) {
3057                     SendToPlayer(&buf[next_out], oldi - next_out);
3058                 }
3059                 next_out = i;
3060                 started = STARTED_BOARD;
3061                 parse_pos = 0;
3062                 continue;
3063             }
3064
3065             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3066                 looking_at(buf, &i, "<b1> ")) {
3067                 if (oldi > next_out) {
3068                     SendToPlayer(&buf[next_out], oldi - next_out);
3069                 }
3070                 next_out = i;
3071                 started = STARTED_HOLDINGS;
3072                 parse_pos = 0;
3073                 continue;
3074             }
3075
3076             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3077                 loggedOn = TRUE;
3078                 /* Header for a move list -- first line */
3079
3080                 switch (ics_getting_history) {
3081                   case H_FALSE:
3082                     switch (gameMode) {
3083                       case IcsIdle:
3084                       case BeginningOfGame:
3085                         /* User typed "moves" or "oldmoves" while we
3086                            were idle.  Pretend we asked for these
3087                            moves and soak them up so user can step
3088                            through them and/or save them.
3089                            */
3090                         Reset(FALSE, TRUE);
3091                         gameMode = IcsObserving;
3092                         ModeHighlight();
3093                         ics_gamenum = -1;
3094                         ics_getting_history = H_GOT_UNREQ_HEADER;
3095                         break;
3096                       case EditGame: /*?*/
3097                       case EditPosition: /*?*/
3098                         /* Should above feature work in these modes too? */
3099                         /* For now it doesn't */
3100                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3101                         break;
3102                       default:
3103                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3104                         break;
3105                     }
3106                     break;
3107                   case H_REQUESTED:
3108                     /* Is this the right one? */
3109                     if (gameInfo.white && gameInfo.black &&
3110                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3111                         strcmp(gameInfo.black, star_match[2]) == 0) {
3112                         /* All is well */
3113                         ics_getting_history = H_GOT_REQ_HEADER;
3114                     }
3115                     break;
3116                   case H_GOT_REQ_HEADER:
3117                   case H_GOT_UNREQ_HEADER:
3118                   case H_GOT_UNWANTED_HEADER:
3119                   case H_GETTING_MOVES:
3120                     /* Should not happen */
3121                     DisplayError(_("Error gathering move list: two headers"), 0);
3122                     ics_getting_history = H_FALSE;
3123                     break;
3124                 }
3125
3126                 /* Save player ratings into gameInfo if needed */
3127                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3128                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3129                     (gameInfo.whiteRating == -1 ||
3130                      gameInfo.blackRating == -1)) {
3131
3132                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3133                     gameInfo.blackRating = string_to_rating(star_match[3]);
3134                     if (appData.debugMode)
3135                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3136                               gameInfo.whiteRating, gameInfo.blackRating);
3137                 }
3138                 continue;
3139             }
3140
3141             if (looking_at(buf, &i,
3142               "* * match, initial time: * minute*, increment: * second")) {
3143                 /* Header for a move list -- second line */
3144                 /* Initial board will follow if this is a wild game */
3145                 if (gameInfo.event != NULL) free(gameInfo.event);
3146                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3147                 gameInfo.event = StrSave(str);
3148                 /* [HGM] we switched variant. Translate boards if needed. */
3149                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3150                 continue;
3151             }
3152
3153             if (looking_at(buf, &i, "Move  ")) {
3154                 /* Beginning of a move list */
3155                 switch (ics_getting_history) {
3156                   case H_FALSE:
3157                     /* Normally should not happen */
3158                     /* Maybe user hit reset while we were parsing */
3159                     break;
3160                   case H_REQUESTED:
3161                     /* Happens if we are ignoring a move list that is not
3162                      * the one we just requested.  Common if the user
3163                      * tries to observe two games without turning off
3164                      * getMoveList */
3165                     break;
3166                   case H_GETTING_MOVES:
3167                     /* Should not happen */
3168                     DisplayError(_("Error gathering move list: nested"), 0);
3169                     ics_getting_history = H_FALSE;
3170                     break;
3171                   case H_GOT_REQ_HEADER:
3172                     ics_getting_history = H_GETTING_MOVES;
3173                     started = STARTED_MOVES;
3174                     parse_pos = 0;
3175                     if (oldi > next_out) {
3176                         SendToPlayer(&buf[next_out], oldi - next_out);
3177                     }
3178                     break;
3179                   case H_GOT_UNREQ_HEADER:
3180                     ics_getting_history = H_GETTING_MOVES;
3181                     started = STARTED_MOVES_NOHIDE;
3182                     parse_pos = 0;
3183                     break;
3184                   case H_GOT_UNWANTED_HEADER:
3185                     ics_getting_history = H_FALSE;
3186                     break;
3187                 }
3188                 continue;
3189             }                           
3190             
3191             if (looking_at(buf, &i, "% ") ||
3192                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3193                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3194                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3195                     soughtPending = FALSE;
3196                     seekGraphUp = TRUE;
3197                     DrawSeekGraph();
3198                 }
3199                 if(suppressKibitz) next_out = i;
3200                 savingComment = FALSE;
3201                 suppressKibitz = 0;
3202                 switch (started) {
3203                   case STARTED_MOVES:
3204                   case STARTED_MOVES_NOHIDE:
3205                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3206                     parse[parse_pos + i - oldi] = NULLCHAR;
3207                     ParseGameHistory(parse);
3208 #if ZIPPY
3209                     if (appData.zippyPlay && first.initDone) {
3210                         FeedMovesToProgram(&first, forwardMostMove);
3211                         if (gameMode == IcsPlayingWhite) {
3212                             if (WhiteOnMove(forwardMostMove)) {
3213                                 if (first.sendTime) {
3214                                   if (first.useColors) {
3215                                     SendToProgram("black\n", &first); 
3216                                   }
3217                                   SendTimeRemaining(&first, TRUE);
3218                                 }
3219                                 if (first.useColors) {
3220                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3221                                 }
3222                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3223                                 first.maybeThinking = TRUE;
3224                             } else {
3225                                 if (first.usePlayother) {
3226                                   if (first.sendTime) {
3227                                     SendTimeRemaining(&first, TRUE);
3228                                   }
3229                                   SendToProgram("playother\n", &first);
3230                                   firstMove = FALSE;
3231                                 } else {
3232                                   firstMove = TRUE;
3233                                 }
3234                             }
3235                         } else if (gameMode == IcsPlayingBlack) {
3236                             if (!WhiteOnMove(forwardMostMove)) {
3237                                 if (first.sendTime) {
3238                                   if (first.useColors) {
3239                                     SendToProgram("white\n", &first);
3240                                   }
3241                                   SendTimeRemaining(&first, FALSE);
3242                                 }
3243                                 if (first.useColors) {
3244                                   SendToProgram("black\n", &first);
3245                                 }
3246                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3247                                 first.maybeThinking = TRUE;
3248                             } else {
3249                                 if (first.usePlayother) {
3250                                   if (first.sendTime) {
3251                                     SendTimeRemaining(&first, FALSE);
3252                                   }
3253                                   SendToProgram("playother\n", &first);
3254                                   firstMove = FALSE;
3255                                 } else {
3256                                   firstMove = TRUE;
3257                                 }
3258                             }
3259                         }                       
3260                     }
3261 #endif
3262                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3263                         /* Moves came from oldmoves or moves command
3264                            while we weren't doing anything else.
3265                            */
3266                         currentMove = forwardMostMove;
3267                         ClearHighlights();/*!!could figure this out*/
3268                         flipView = appData.flipView;
3269                         DrawPosition(TRUE, boards[currentMove]);
3270                         DisplayBothClocks();
3271                         sprintf(str, "%s vs. %s",
3272                                 gameInfo.white, gameInfo.black);
3273                         DisplayTitle(str);
3274                         gameMode = IcsIdle;
3275                     } else {
3276                         /* Moves were history of an active game */
3277                         if (gameInfo.resultDetails != NULL) {
3278                             free(gameInfo.resultDetails);
3279                             gameInfo.resultDetails = NULL;
3280                         }
3281                     }
3282                     HistorySet(parseList, backwardMostMove,
3283                                forwardMostMove, currentMove-1);
3284                     DisplayMove(currentMove - 1);
3285                     if (started == STARTED_MOVES) next_out = i;
3286                     started = STARTED_NONE;
3287                     ics_getting_history = H_FALSE;
3288                     break;
3289
3290                   case STARTED_OBSERVE:
3291                     started = STARTED_NONE;
3292                     SendToICS(ics_prefix);
3293                     SendToICS("refresh\n");
3294                     break;
3295
3296                   default:
3297                     break;
3298                 }
3299                 if(bookHit) { // [HGM] book: simulate book reply
3300                     static char bookMove[MSG_SIZ]; // a bit generous?
3301
3302                     programStats.nodes = programStats.depth = programStats.time = 
3303                     programStats.score = programStats.got_only_move = 0;
3304                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3305
3306                     strcpy(bookMove, "move ");
3307                     strcat(bookMove, bookHit);
3308                     HandleMachineMove(bookMove, &first);
3309                 }
3310                 continue;
3311             }
3312             
3313             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3314                  started == STARTED_HOLDINGS ||
3315                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3316                 /* Accumulate characters in move list or board */
3317                 parse[parse_pos++] = buf[i];
3318             }
3319             
3320             /* Start of game messages.  Mostly we detect start of game
3321                when the first board image arrives.  On some versions
3322                of the ICS, though, we need to do a "refresh" after starting
3323                to observe in order to get the current board right away. */
3324             if (looking_at(buf, &i, "Adding game * to observation list")) {
3325                 started = STARTED_OBSERVE;
3326                 continue;
3327             }
3328
3329             /* Handle auto-observe */
3330             if (appData.autoObserve &&
3331                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3332                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3333                 char *player;
3334                 /* Choose the player that was highlighted, if any. */
3335                 if (star_match[0][0] == '\033' ||
3336                     star_match[1][0] != '\033') {
3337                     player = star_match[0];
3338                 } else {
3339                     player = star_match[2];
3340                 }
3341                 sprintf(str, "%sobserve %s\n",
3342                         ics_prefix, StripHighlightAndTitle(player));
3343                 SendToICS(str);
3344
3345                 /* Save ratings from notify string */
3346                 strcpy(player1Name, star_match[0]);
3347                 player1Rating = string_to_rating(star_match[1]);
3348                 strcpy(player2Name, star_match[2]);
3349                 player2Rating = string_to_rating(star_match[3]);
3350
3351                 if (appData.debugMode)
3352                   fprintf(debugFP, 
3353                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3354                           player1Name, player1Rating,
3355                           player2Name, player2Rating);
3356
3357                 continue;
3358             }
3359
3360             /* Deal with automatic examine mode after a game,
3361                and with IcsObserving -> IcsExamining transition */
3362             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3363                 looking_at(buf, &i, "has made you an examiner of game *")) {
3364
3365                 int gamenum = atoi(star_match[0]);
3366                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3367                     gamenum == ics_gamenum) {
3368                     /* We were already playing or observing this game;
3369                        no need to refetch history */
3370                     gameMode = IcsExamining;
3371                     if (pausing) {
3372                         pauseExamForwardMostMove = forwardMostMove;
3373                     } else if (currentMove < forwardMostMove) {
3374                         ForwardInner(forwardMostMove);
3375                     }
3376                 } else {
3377                     /* I don't think this case really can happen */
3378                     SendToICS(ics_prefix);
3379                     SendToICS("refresh\n");
3380                 }
3381                 continue;
3382             }    
3383             
3384             /* Error messages */
3385 //          if (ics_user_moved) {
3386             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3387                 if (looking_at(buf, &i, "Illegal move") ||
3388                     looking_at(buf, &i, "Not a legal move") ||
3389                     looking_at(buf, &i, "Your king is in check") ||
3390                     looking_at(buf, &i, "It isn't your turn") ||
3391                     looking_at(buf, &i, "It is not your move")) {
3392                     /* Illegal move */
3393                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3394                         currentMove = forwardMostMove-1;
3395                         DisplayMove(currentMove - 1); /* before DMError */
3396                         DrawPosition(FALSE, boards[currentMove]);
3397                         SwitchClocks(forwardMostMove-1); // [HGM] race
3398                         DisplayBothClocks();
3399                     }
3400                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3401                     ics_user_moved = 0;
3402                     continue;
3403                 }
3404             }
3405
3406             if (looking_at(buf, &i, "still have time") ||
3407                 looking_at(buf, &i, "not out of time") ||
3408                 looking_at(buf, &i, "either player is out of time") ||
3409                 looking_at(buf, &i, "has timeseal; checking")) {
3410                 /* We must have called his flag a little too soon */
3411                 whiteFlag = blackFlag = FALSE;
3412                 continue;
3413             }
3414
3415             if (looking_at(buf, &i, "added * seconds to") ||
3416                 looking_at(buf, &i, "seconds were added to")) {
3417                 /* Update the clocks */
3418                 SendToICS(ics_prefix);
3419                 SendToICS("refresh\n");
3420                 continue;
3421             }
3422
3423             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3424                 ics_clock_paused = TRUE;
3425                 StopClocks();
3426                 continue;
3427             }
3428
3429             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3430                 ics_clock_paused = FALSE;
3431                 StartClocks();
3432                 continue;
3433             }
3434
3435             /* Grab player ratings from the Creating: message.
3436                Note we have to check for the special case when
3437                the ICS inserts things like [white] or [black]. */
3438             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3439                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3440                 /* star_matches:
3441                    0    player 1 name (not necessarily white)
3442                    1    player 1 rating
3443                    2    empty, white, or black (IGNORED)
3444                    3    player 2 name (not necessarily black)
3445                    4    player 2 rating
3446                    
3447                    The names/ratings are sorted out when the game
3448                    actually starts (below).
3449                 */
3450                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3451                 player1Rating = string_to_rating(star_match[1]);
3452                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3453                 player2Rating = string_to_rating(star_match[4]);
3454
3455                 if (appData.debugMode)
3456                   fprintf(debugFP, 
3457                           "Ratings from 'Creating:' %s %d, %s %d\n",
3458                           player1Name, player1Rating,
3459                           player2Name, player2Rating);
3460
3461                 continue;
3462             }
3463             
3464             /* Improved generic start/end-of-game messages */
3465             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3466                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3467                 /* If tkind == 0: */
3468                 /* star_match[0] is the game number */
3469                 /*           [1] is the white player's name */
3470                 /*           [2] is the black player's name */
3471                 /* For end-of-game: */
3472                 /*           [3] is the reason for the game end */
3473                 /*           [4] is a PGN end game-token, preceded by " " */
3474                 /* For start-of-game: */
3475                 /*           [3] begins with "Creating" or "Continuing" */
3476                 /*           [4] is " *" or empty (don't care). */
3477                 int gamenum = atoi(star_match[0]);
3478                 char *whitename, *blackname, *why, *endtoken;
3479                 ChessMove endtype = (ChessMove) 0;
3480
3481                 if (tkind == 0) {
3482                   whitename = star_match[1];
3483                   blackname = star_match[2];
3484                   why = star_match[3];
3485                   endtoken = star_match[4];
3486                 } else {
3487                   whitename = star_match[1];
3488                   blackname = star_match[3];
3489                   why = star_match[5];
3490                   endtoken = star_match[6];
3491                 }
3492
3493                 /* Game start messages */
3494                 if (strncmp(why, "Creating ", 9) == 0 ||
3495                     strncmp(why, "Continuing ", 11) == 0) {
3496                     gs_gamenum = gamenum;
3497                     strcpy(gs_kind, strchr(why, ' ') + 1);
3498                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3499 #if ZIPPY
3500                     if (appData.zippyPlay) {
3501                         ZippyGameStart(whitename, blackname);
3502                     }
3503 #endif /*ZIPPY*/
3504                     partnerBoardValid = FALSE; // [HGM] bughouse
3505                     continue;
3506                 }
3507
3508                 /* Game end messages */
3509                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3510                     ics_gamenum != gamenum) {
3511                     continue;
3512                 }
3513                 while (endtoken[0] == ' ') endtoken++;
3514                 switch (endtoken[0]) {
3515                   case '*':
3516                   default:
3517                     endtype = GameUnfinished;
3518                     break;
3519                   case '0':
3520                     endtype = BlackWins;
3521                     break;
3522                   case '1':
3523                     if (endtoken[1] == '/')
3524                       endtype = GameIsDrawn;
3525                     else
3526                       endtype = WhiteWins;
3527                     break;
3528                 }
3529                 GameEnds(endtype, why, GE_ICS);
3530 #if ZIPPY
3531                 if (appData.zippyPlay && first.initDone) {
3532                     ZippyGameEnd(endtype, why);
3533                     if (first.pr == NULL) {
3534                       /* Start the next process early so that we'll
3535                          be ready for the next challenge */
3536                       StartChessProgram(&first);
3537                     }
3538                     /* Send "new" early, in case this command takes
3539                        a long time to finish, so that we'll be ready
3540                        for the next challenge. */
3541                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3542                     Reset(TRUE, TRUE);
3543                 }
3544 #endif /*ZIPPY*/
3545                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3546                 continue;
3547             }
3548
3549             if (looking_at(buf, &i, "Removing game * from observation") ||
3550                 looking_at(buf, &i, "no longer observing game *") ||
3551                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3552                 if (gameMode == IcsObserving &&
3553                     atoi(star_match[0]) == ics_gamenum)
3554                   {
3555                       /* icsEngineAnalyze */
3556                       if (appData.icsEngineAnalyze) {
3557                             ExitAnalyzeMode();
3558                             ModeHighlight();
3559                       }
3560                       StopClocks();
3561                       gameMode = IcsIdle;
3562                       ics_gamenum = -1;
3563                       ics_user_moved = FALSE;
3564                   }
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i, "no longer examining game *")) {
3569                 if (gameMode == IcsExamining &&
3570                     atoi(star_match[0]) == ics_gamenum)
3571                   {
3572                       gameMode = IcsIdle;
3573                       ics_gamenum = -1;
3574                       ics_user_moved = FALSE;
3575                   }
3576                 continue;
3577             }
3578
3579             /* Advance leftover_start past any newlines we find,
3580                so only partial lines can get reparsed */
3581             if (looking_at(buf, &i, "\n")) {
3582                 prevColor = curColor;
3583                 if (curColor != ColorNormal) {
3584                     if (oldi > next_out) {
3585                         SendToPlayer(&buf[next_out], oldi - next_out);
3586                         next_out = oldi;
3587                     }
3588                     Colorize(ColorNormal, FALSE);
3589                     curColor = ColorNormal;
3590                 }
3591                 if (started == STARTED_BOARD) {
3592                     started = STARTED_NONE;
3593                     parse[parse_pos] = NULLCHAR;
3594                     ParseBoard12(parse);
3595                     ics_user_moved = 0;
3596
3597                     /* Send premove here */
3598                     if (appData.premove) {
3599                       char str[MSG_SIZ];
3600                       if (currentMove == 0 &&
3601                           gameMode == IcsPlayingWhite &&
3602                           appData.premoveWhite) {
3603                         sprintf(str, "%s\n", appData.premoveWhiteText);
3604                         if (appData.debugMode)
3605                           fprintf(debugFP, "Sending premove:\n");
3606                         SendToICS(str);
3607                       } else if (currentMove == 1 &&
3608                                  gameMode == IcsPlayingBlack &&
3609                                  appData.premoveBlack) {
3610                         sprintf(str, "%s\n", appData.premoveBlackText);
3611                         if (appData.debugMode)
3612                           fprintf(debugFP, "Sending premove:\n");
3613                         SendToICS(str);
3614                       } else if (gotPremove) {
3615                         gotPremove = 0;
3616                         ClearPremoveHighlights();
3617                         if (appData.debugMode)
3618                           fprintf(debugFP, "Sending premove:\n");
3619                           UserMoveEvent(premoveFromX, premoveFromY, 
3620                                         premoveToX, premoveToY, 
3621                                         premovePromoChar);
3622                       }
3623                     }
3624
3625                     /* Usually suppress following prompt */
3626                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3627                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3628                         if (looking_at(buf, &i, "*% ")) {
3629                             savingComment = FALSE;
3630                             suppressKibitz = 0;
3631                         }
3632                     }
3633                     next_out = i;
3634                 } else if (started == STARTED_HOLDINGS) {
3635                     int gamenum;
3636                     char new_piece[MSG_SIZ];
3637                     started = STARTED_NONE;
3638                     parse[parse_pos] = NULLCHAR;
3639                     if (appData.debugMode)
3640                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3641                                                         parse, currentMove);
3642                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3643                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3644                         if (gameInfo.variant == VariantNormal) {
3645                           /* [HGM] We seem to switch variant during a game!
3646                            * Presumably no holdings were displayed, so we have
3647                            * to move the position two files to the right to
3648                            * create room for them!
3649                            */
3650                           VariantClass newVariant;
3651                           switch(gameInfo.boardWidth) { // base guess on board width
3652                                 case 9:  newVariant = VariantShogi; break;
3653                                 case 10: newVariant = VariantGreat; break;
3654                                 default: newVariant = VariantCrazyhouse; break;
3655                           }
3656                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3657                           /* Get a move list just to see the header, which
3658                              will tell us whether this is really bug or zh */
3659                           if (ics_getting_history == H_FALSE) {
3660                             ics_getting_history = H_REQUESTED;
3661                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3662                             SendToICS(str);
3663                           }
3664                         }
3665                         new_piece[0] = NULLCHAR;
3666                         sscanf(parse, "game %d white [%s black [%s <- %s",
3667                                &gamenum, white_holding, black_holding,
3668                                new_piece);
3669                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3670                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3671                         /* [HGM] copy holdings to board holdings area */
3672                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3673                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3674                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3675 #if ZIPPY
3676                         if (appData.zippyPlay && first.initDone) {
3677                             ZippyHoldings(white_holding, black_holding,
3678                                           new_piece);
3679                         }
3680 #endif /*ZIPPY*/
3681                         if (tinyLayout || smallLayout) {
3682                             char wh[16], bh[16];
3683                             PackHolding(wh, white_holding);
3684                             PackHolding(bh, black_holding);
3685                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3686                                     gameInfo.white, gameInfo.black);
3687                         } else {
3688                             sprintf(str, "%s [%s] vs. %s [%s]",
3689                                     gameInfo.white, white_holding,
3690                                     gameInfo.black, black_holding);
3691                         }
3692                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3693                         DrawPosition(FALSE, boards[currentMove]);
3694                         DisplayTitle(str);
3695                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3696                         sscanf(parse, "game %d white [%s black [%s <- %s",
3697                                &gamenum, white_holding, black_holding,
3698                                new_piece);
3699                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3700                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3701                         /* [HGM] copy holdings to partner-board holdings area */
3702                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3703                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3704                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3705                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3706                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3707                       }
3708                     }
3709                     /* Suppress following prompt */
3710                     if (looking_at(buf, &i, "*% ")) {
3711                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3712                         savingComment = FALSE;
3713                         suppressKibitz = 0;
3714                     }
3715                     next_out = i;
3716                 }
3717                 continue;
3718             }
3719
3720             i++;                /* skip unparsed character and loop back */
3721         }
3722         
3723         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3724 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3725 //          SendToPlayer(&buf[next_out], i - next_out);
3726             started != STARTED_HOLDINGS && leftover_start > next_out) {
3727             SendToPlayer(&buf[next_out], leftover_start - next_out);
3728             next_out = i;
3729         }
3730         
3731         leftover_len = buf_len - leftover_start;
3732         /* if buffer ends with something we couldn't parse,
3733            reparse it after appending the next read */
3734         
3735     } else if (count == 0) {
3736         RemoveInputSource(isr);
3737         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3738     } else {
3739         DisplayFatalError(_("Error reading from ICS"), error, 1);
3740     }
3741 }
3742
3743
3744 /* Board style 12 looks like this:
3745    
3746    <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
3747    
3748  * The "<12> " is stripped before it gets to this routine.  The two
3749  * trailing 0's (flip state and clock ticking) are later addition, and
3750  * some chess servers may not have them, or may have only the first.
3751  * Additional trailing fields may be added in the future.  
3752  */
3753
3754 #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"
3755
3756 #define RELATION_OBSERVING_PLAYED    0
3757 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3758 #define RELATION_PLAYING_MYMOVE      1
3759 #define RELATION_PLAYING_NOTMYMOVE  -1
3760 #define RELATION_EXAMINING           2
3761 #define RELATION_ISOLATED_BOARD     -3
3762 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3763
3764 void
3765 ParseBoard12(string)
3766      char *string;
3767
3768     GameMode newGameMode;
3769     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3770     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3771     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3772     char to_play, board_chars[200];
3773     char move_str[500], str[500], elapsed_time[500];
3774     char black[32], white[32];
3775     Board board;
3776     int prevMove = currentMove;
3777     int ticking = 2;
3778     ChessMove moveType;
3779     int fromX, fromY, toX, toY;
3780     char promoChar;
3781     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3782     char *bookHit = NULL; // [HGM] book
3783     Boolean weird = FALSE, reqFlag = FALSE;
3784
3785     fromX = fromY = toX = toY = -1;
3786     
3787     newGame = FALSE;
3788
3789     if (appData.debugMode)
3790       fprintf(debugFP, _("Parsing board: %s\n"), string);
3791
3792     move_str[0] = NULLCHAR;
3793     elapsed_time[0] = NULLCHAR;
3794     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3795         int  i = 0, j;
3796         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3797             if(string[i] == ' ') { ranks++; files = 0; }
3798             else files++;
3799             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3800             i++;
3801         }
3802         for(j = 0; j <i; j++) board_chars[j] = string[j];
3803         board_chars[i] = '\0';
3804         string += i + 1;
3805     }
3806     n = sscanf(string, PATTERN, &to_play, &double_push,
3807                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3808                &gamenum, white, black, &relation, &basetime, &increment,
3809                &white_stren, &black_stren, &white_time, &black_time,
3810                &moveNum, str, elapsed_time, move_str, &ics_flip,
3811                &ticking);
3812
3813     if (n < 21) {
3814         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3815         DisplayError(str, 0);
3816         return;
3817     }
3818
3819     /* Convert the move number to internal form */
3820     moveNum = (moveNum - 1) * 2;
3821     if (to_play == 'B') moveNum++;
3822     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3823       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3824                         0, 1);
3825       return;
3826     }
3827     
3828     switch (relation) {
3829       case RELATION_OBSERVING_PLAYED:
3830       case RELATION_OBSERVING_STATIC:
3831         if (gamenum == -1) {
3832             /* Old ICC buglet */
3833             relation = RELATION_OBSERVING_STATIC;
3834         }
3835         newGameMode = IcsObserving;
3836         break;
3837       case RELATION_PLAYING_MYMOVE:
3838       case RELATION_PLAYING_NOTMYMOVE:
3839         newGameMode =
3840           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3841             IcsPlayingWhite : IcsPlayingBlack;
3842         break;
3843       case RELATION_EXAMINING:
3844         newGameMode = IcsExamining;
3845         break;
3846       case RELATION_ISOLATED_BOARD:
3847       default:
3848         /* Just display this board.  If user was doing something else,
3849            we will forget about it until the next board comes. */ 
3850         newGameMode = IcsIdle;
3851         break;
3852       case RELATION_STARTING_POSITION:
3853         newGameMode = gameMode;
3854         break;
3855     }
3856     
3857     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3858          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3859       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3860       char *toSqr;
3861       for (k = 0; k < ranks; k++) {
3862         for (j = 0; j < files; j++)
3863           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3864         if(gameInfo.holdingsWidth > 1) {
3865              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3866              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3867         }
3868       }
3869       CopyBoard(partnerBoard, board);
3870       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3871         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3872         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3873       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3874       if(toSqr = strchr(str, '-')) {
3875         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3876         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3877       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3878       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3879       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3880       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3881       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3882       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3883                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3884       DisplayMessage(partnerStatus, "");
3885         partnerBoardValid = TRUE;
3886       return;
3887     }
3888
3889     /* Modify behavior for initial board display on move listing
3890        of wild games.
3891        */
3892     switch (ics_getting_history) {
3893       case H_FALSE:
3894       case H_REQUESTED:
3895         break;
3896       case H_GOT_REQ_HEADER:
3897       case H_GOT_UNREQ_HEADER:
3898         /* This is the initial position of the current game */
3899         gamenum = ics_gamenum;
3900         moveNum = 0;            /* old ICS bug workaround */
3901         if (to_play == 'B') {
3902           startedFromSetupPosition = TRUE;
3903           blackPlaysFirst = TRUE;
3904           moveNum = 1;
3905           if (forwardMostMove == 0) forwardMostMove = 1;
3906           if (backwardMostMove == 0) backwardMostMove = 1;
3907           if (currentMove == 0) currentMove = 1;
3908         }
3909         newGameMode = gameMode;
3910         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3911         break;
3912       case H_GOT_UNWANTED_HEADER:
3913         /* This is an initial board that we don't want */
3914         return;
3915       case H_GETTING_MOVES:
3916         /* Should not happen */
3917         DisplayError(_("Error gathering move list: extra board"), 0);
3918         ics_getting_history = H_FALSE;
3919         return;
3920     }
3921
3922    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3923                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3924      /* [HGM] We seem to have switched variant unexpectedly
3925       * Try to guess new variant from board size
3926       */
3927           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3928           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3929           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3930           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3931           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3932           if(!weird) newVariant = VariantNormal;
3933           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3934           /* Get a move list just to see the header, which
3935              will tell us whether this is really bug or zh */
3936           if (ics_getting_history == H_FALSE) {
3937             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3938             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3939             SendToICS(str);
3940           }
3941     }
3942     
3943     /* Take action if this is the first board of a new game, or of a
3944        different game than is currently being displayed.  */
3945     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3946         relation == RELATION_ISOLATED_BOARD) {
3947         
3948         /* Forget the old game and get the history (if any) of the new one */
3949         if (gameMode != BeginningOfGame) {
3950           Reset(TRUE, TRUE);
3951         }
3952         newGame = TRUE;
3953         if (appData.autoRaiseBoard) BoardToTop();
3954         prevMove = -3;
3955         if (gamenum == -1) {
3956             newGameMode = IcsIdle;
3957         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3958                    appData.getMoveList && !reqFlag) {
3959             /* Need to get game history */
3960             ics_getting_history = H_REQUESTED;
3961             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3962             SendToICS(str);
3963         }
3964         
3965         /* Initially flip the board to have black on the bottom if playing
3966            black or if the ICS flip flag is set, but let the user change
3967            it with the Flip View button. */
3968         flipView = appData.autoFlipView ? 
3969           (newGameMode == IcsPlayingBlack) || ics_flip :
3970           appData.flipView;
3971         
3972         /* Done with values from previous mode; copy in new ones */
3973         gameMode = newGameMode;
3974         ModeHighlight();
3975         ics_gamenum = gamenum;
3976         if (gamenum == gs_gamenum) {
3977             int klen = strlen(gs_kind);
3978             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3979             sprintf(str, "ICS %s", gs_kind);
3980             gameInfo.event = StrSave(str);
3981         } else {
3982             gameInfo.event = StrSave("ICS game");
3983         }
3984         gameInfo.site = StrSave(appData.icsHost);
3985         gameInfo.date = PGNDate();
3986         gameInfo.round = StrSave("-");
3987         gameInfo.white = StrSave(white);
3988         gameInfo.black = StrSave(black);
3989         timeControl = basetime * 60 * 1000;
3990         timeControl_2 = 0;
3991         timeIncrement = increment * 1000;
3992         movesPerSession = 0;
3993         gameInfo.timeControl = TimeControlTagValue();
3994         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3995   if (appData.debugMode) {
3996     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3997     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3998     setbuf(debugFP, NULL);
3999   }
4000
4001         gameInfo.outOfBook = NULL;
4002         
4003         /* Do we have the ratings? */
4004         if (strcmp(player1Name, white) == 0 &&
4005             strcmp(player2Name, black) == 0) {
4006             if (appData.debugMode)
4007               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4008                       player1Rating, player2Rating);
4009             gameInfo.whiteRating = player1Rating;
4010             gameInfo.blackRating = player2Rating;
4011         } else if (strcmp(player2Name, white) == 0 &&
4012                    strcmp(player1Name, black) == 0) {
4013             if (appData.debugMode)
4014               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4015                       player2Rating, player1Rating);
4016             gameInfo.whiteRating = player2Rating;
4017             gameInfo.blackRating = player1Rating;
4018         }
4019         player1Name[0] = player2Name[0] = NULLCHAR;
4020
4021         /* Silence shouts if requested */
4022         if (appData.quietPlay &&
4023             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4024             SendToICS(ics_prefix);
4025             SendToICS("set shout 0\n");
4026         }
4027     }
4028     
4029     /* Deal with midgame name changes */
4030     if (!newGame) {
4031         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4032             if (gameInfo.white) free(gameInfo.white);
4033             gameInfo.white = StrSave(white);
4034         }
4035         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4036             if (gameInfo.black) free(gameInfo.black);
4037             gameInfo.black = StrSave(black);
4038         }
4039     }
4040     
4041     /* Throw away game result if anything actually changes in examine mode */
4042     if (gameMode == IcsExamining && !newGame) {
4043         gameInfo.result = GameUnfinished;
4044         if (gameInfo.resultDetails != NULL) {
4045             free(gameInfo.resultDetails);
4046             gameInfo.resultDetails = NULL;
4047         }
4048     }
4049     
4050     /* In pausing && IcsExamining mode, we ignore boards coming
4051        in if they are in a different variation than we are. */
4052     if (pauseExamInvalid) return;
4053     if (pausing && gameMode == IcsExamining) {
4054         if (moveNum <= pauseExamForwardMostMove) {
4055             pauseExamInvalid = TRUE;
4056             forwardMostMove = pauseExamForwardMostMove;
4057             return;
4058         }
4059     }
4060     
4061   if (appData.debugMode) {
4062     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4063   }
4064     /* Parse the board */
4065     for (k = 0; k < ranks; k++) {
4066       for (j = 0; j < files; j++)
4067         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4068       if(gameInfo.holdingsWidth > 1) {
4069            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4070            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4071       }
4072     }
4073     CopyBoard(boards[moveNum], board);
4074     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4075     if (moveNum == 0) {
4076         startedFromSetupPosition =
4077           !CompareBoards(board, initialPosition);
4078         if(startedFromSetupPosition)
4079             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4080     }
4081
4082     /* [HGM] Set castling rights. Take the outermost Rooks,
4083        to make it also work for FRC opening positions. Note that board12
4084        is really defective for later FRC positions, as it has no way to
4085        indicate which Rook can castle if they are on the same side of King.
4086        For the initial position we grant rights to the outermost Rooks,
4087        and remember thos rights, and we then copy them on positions
4088        later in an FRC game. This means WB might not recognize castlings with
4089        Rooks that have moved back to their original position as illegal,
4090        but in ICS mode that is not its job anyway.
4091     */
4092     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4093     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4094
4095         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4096             if(board[0][i] == WhiteRook) j = i;
4097         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4098         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4099             if(board[0][i] == WhiteRook) j = i;
4100         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4101         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4102             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4103         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4104         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4105             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4106         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4107
4108         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4109         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4110             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4111         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4112             if(board[BOARD_HEIGHT-1][k] == bKing)
4113                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4114         if(gameInfo.variant == VariantTwoKings) {
4115             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4116             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4117             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4118         }
4119     } else { int r;
4120         r = boards[moveNum][CASTLING][0] = initialRights[0];
4121         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4122         r = boards[moveNum][CASTLING][1] = initialRights[1];
4123         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4124         r = boards[moveNum][CASTLING][3] = initialRights[3];
4125         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4126         r = boards[moveNum][CASTLING][4] = initialRights[4];
4127         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4128         /* wildcastle kludge: always assume King has rights */
4129         r = boards[moveNum][CASTLING][2] = initialRights[2];
4130         r = boards[moveNum][CASTLING][5] = initialRights[5];
4131     }
4132     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4133     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4134
4135     
4136     if (ics_getting_history == H_GOT_REQ_HEADER ||
4137         ics_getting_history == H_GOT_UNREQ_HEADER) {
4138         /* This was an initial position from a move list, not
4139            the current position */
4140         return;
4141     }
4142     
4143     /* Update currentMove and known move number limits */
4144     newMove = newGame || moveNum > forwardMostMove;
4145
4146     if (newGame) {
4147         forwardMostMove = backwardMostMove = currentMove = moveNum;
4148         if (gameMode == IcsExamining && moveNum == 0) {
4149           /* Workaround for ICS limitation: we are not told the wild
4150              type when starting to examine a game.  But if we ask for
4151              the move list, the move list header will tell us */
4152             ics_getting_history = H_REQUESTED;
4153             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4154             SendToICS(str);
4155         }
4156     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4157                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4158 #if ZIPPY
4159         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4160         /* [HGM] applied this also to an engine that is silently watching        */
4161         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4162             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4163             gameInfo.variant == currentlyInitializedVariant) {
4164           takeback = forwardMostMove - moveNum;
4165           for (i = 0; i < takeback; i++) {
4166             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4167             SendToProgram("undo\n", &first);
4168           }
4169         }
4170 #endif
4171
4172         forwardMostMove = moveNum;
4173         if (!pausing || currentMove > forwardMostMove)
4174           currentMove = forwardMostMove;
4175     } else {
4176         /* New part of history that is not contiguous with old part */ 
4177         if (pausing && gameMode == IcsExamining) {
4178             pauseExamInvalid = TRUE;
4179             forwardMostMove = pauseExamForwardMostMove;
4180             return;
4181         }
4182         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4183 #if ZIPPY
4184             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4185                 // [HGM] when we will receive the move list we now request, it will be
4186                 // fed to the engine from the first move on. So if the engine is not
4187                 // in the initial position now, bring it there.
4188                 InitChessProgram(&first, 0);
4189             }
4190 #endif
4191             ics_getting_history = H_REQUESTED;
4192             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4193             SendToICS(str);
4194         }
4195         forwardMostMove = backwardMostMove = currentMove = moveNum;
4196     }
4197     
4198     /* Update the clocks */
4199     if (strchr(elapsed_time, '.')) {
4200       /* Time is in ms */
4201       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4202       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4203     } else {
4204       /* Time is in seconds */
4205       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4206       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4207     }
4208       
4209
4210 #if ZIPPY
4211     if (appData.zippyPlay && newGame &&
4212         gameMode != IcsObserving && gameMode != IcsIdle &&
4213         gameMode != IcsExamining)
4214       ZippyFirstBoard(moveNum, basetime, increment);
4215 #endif
4216     
4217     /* Put the move on the move list, first converting
4218        to canonical algebraic form. */
4219     if (moveNum > 0) {
4220   if (appData.debugMode) {
4221     if (appData.debugMode) { int f = forwardMostMove;
4222         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4223                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4224                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4225     }
4226     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4227     fprintf(debugFP, "moveNum = %d\n", moveNum);
4228     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4229     setbuf(debugFP, NULL);
4230   }
4231         if (moveNum <= backwardMostMove) {
4232             /* We don't know what the board looked like before
4233                this move.  Punt. */
4234             strcpy(parseList[moveNum - 1], move_str);
4235             strcat(parseList[moveNum - 1], " ");
4236             strcat(parseList[moveNum - 1], elapsed_time);
4237             moveList[moveNum - 1][0] = NULLCHAR;
4238         } else if (strcmp(move_str, "none") == 0) {
4239             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4240             /* Again, we don't know what the board looked like;
4241                this is really the start of the game. */
4242             parseList[moveNum - 1][0] = NULLCHAR;
4243             moveList[moveNum - 1][0] = NULLCHAR;
4244             backwardMostMove = moveNum;
4245             startedFromSetupPosition = TRUE;
4246             fromX = fromY = toX = toY = -1;
4247         } else {
4248           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4249           //                 So we parse the long-algebraic move string in stead of the SAN move
4250           int valid; char buf[MSG_SIZ], *prom;
4251
4252           // str looks something like "Q/a1-a2"; kill the slash
4253           if(str[1] == '/') 
4254                 sprintf(buf, "%c%s", str[0], str+2);
4255           else  strcpy(buf, str); // might be castling
4256           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4257                 strcat(buf, prom); // long move lacks promo specification!
4258           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4259                 if(appData.debugMode) 
4260                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4261                 strcpy(move_str, buf);
4262           }
4263           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4264                                 &fromX, &fromY, &toX, &toY, &promoChar)
4265                || ParseOneMove(buf, moveNum - 1, &moveType,
4266                                 &fromX, &fromY, &toX, &toY, &promoChar);
4267           // end of long SAN patch
4268           if (valid) {
4269             (void) CoordsToAlgebraic(boards[moveNum - 1],
4270                                      PosFlags(moveNum - 1),
4271                                      fromY, fromX, toY, toX, promoChar,
4272                                      parseList[moveNum-1]);
4273             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4274               case MT_NONE:
4275               case MT_STALEMATE:
4276               default:
4277                 break;
4278               case MT_CHECK:
4279                 if(gameInfo.variant != VariantShogi)
4280                     strcat(parseList[moveNum - 1], "+");
4281                 break;
4282               case MT_CHECKMATE:
4283               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4284                 strcat(parseList[moveNum - 1], "#");
4285                 break;
4286             }
4287             strcat(parseList[moveNum - 1], " ");
4288             strcat(parseList[moveNum - 1], elapsed_time);
4289             /* currentMoveString is set as a side-effect of ParseOneMove */
4290             strcpy(moveList[moveNum - 1], currentMoveString);
4291             strcat(moveList[moveNum - 1], "\n");
4292           } else {
4293             /* Move from ICS was illegal!?  Punt. */
4294   if (appData.debugMode) {
4295     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4296     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4297   }
4298             strcpy(parseList[moveNum - 1], move_str);
4299             strcat(parseList[moveNum - 1], " ");
4300             strcat(parseList[moveNum - 1], elapsed_time);
4301             moveList[moveNum - 1][0] = NULLCHAR;
4302             fromX = fromY = toX = toY = -1;
4303           }
4304         }
4305   if (appData.debugMode) {
4306     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4307     setbuf(debugFP, NULL);
4308   }
4309
4310 #if ZIPPY
4311         /* Send move to chess program (BEFORE animating it). */
4312         if (appData.zippyPlay && !newGame && newMove && 
4313            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4314
4315             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4316                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4317                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4318                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4319                             move_str);
4320                     DisplayError(str, 0);
4321                 } else {
4322                     if (first.sendTime) {
4323                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4324                     }
4325                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4326                     if (firstMove && !bookHit) {
4327                         firstMove = FALSE;
4328                         if (first.useColors) {
4329                           SendToProgram(gameMode == IcsPlayingWhite ?
4330                                         "white\ngo\n" :
4331                                         "black\ngo\n", &first);
4332                         } else {
4333                           SendToProgram("go\n", &first);
4334                         }
4335                         first.maybeThinking = TRUE;
4336                     }
4337                 }
4338             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4339               if (moveList[moveNum - 1][0] == NULLCHAR) {
4340                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4341                 DisplayError(str, 0);
4342               } else {
4343                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4344                 SendMoveToProgram(moveNum - 1, &first);
4345               }
4346             }
4347         }
4348 #endif
4349     }
4350
4351     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4352         /* If move comes from a remote source, animate it.  If it
4353            isn't remote, it will have already been animated. */
4354         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4355             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4356         }
4357         if (!pausing && appData.highlightLastMove) {
4358             SetHighlights(fromX, fromY, toX, toY);
4359         }
4360     }
4361     
4362     /* Start the clocks */
4363     whiteFlag = blackFlag = FALSE;
4364     appData.clockMode = !(basetime == 0 && increment == 0);
4365     if (ticking == 0) {
4366       ics_clock_paused = TRUE;
4367       StopClocks();
4368     } else if (ticking == 1) {
4369       ics_clock_paused = FALSE;
4370     }
4371     if (gameMode == IcsIdle ||
4372         relation == RELATION_OBSERVING_STATIC ||
4373         relation == RELATION_EXAMINING ||
4374         ics_clock_paused)
4375       DisplayBothClocks();
4376     else
4377       StartClocks();
4378     
4379     /* Display opponents and material strengths */
4380     if (gameInfo.variant != VariantBughouse &&
4381         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4382         if (tinyLayout || smallLayout) {
4383             if(gameInfo.variant == VariantNormal)
4384                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4385                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4386                     basetime, increment);
4387             else
4388                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4389                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4390                     basetime, increment, (int) gameInfo.variant);
4391         } else {
4392             if(gameInfo.variant == VariantNormal)
4393                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4394                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4395                     basetime, increment);
4396             else
4397                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4398                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4399                     basetime, increment, VariantName(gameInfo.variant));
4400         }
4401         DisplayTitle(str);
4402   if (appData.debugMode) {
4403     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4404   }
4405     }
4406
4407
4408     /* Display the board */
4409     if (!pausing && !appData.noGUI) {
4410       
4411       if (appData.premove)
4412           if (!gotPremove || 
4413              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4414              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4415               ClearPremoveHighlights();
4416
4417       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4418         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4419       DrawPosition(j, boards[currentMove]);
4420
4421       DisplayMove(moveNum - 1);
4422       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4423             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4424               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4425         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4426       }
4427     }
4428
4429     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4430 #if ZIPPY
4431     if(bookHit) { // [HGM] book: simulate book reply
4432         static char bookMove[MSG_SIZ]; // a bit generous?
4433
4434         programStats.nodes = programStats.depth = programStats.time = 
4435         programStats.score = programStats.got_only_move = 0;
4436         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4437
4438         strcpy(bookMove, "move ");
4439         strcat(bookMove, bookHit);
4440         HandleMachineMove(bookMove, &first);
4441     }
4442 #endif
4443 }
4444
4445 void
4446 GetMoveListEvent()
4447 {
4448     char buf[MSG_SIZ];
4449     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4450         ics_getting_history = H_REQUESTED;
4451         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4452         SendToICS(buf);
4453     }
4454 }
4455
4456 void
4457 AnalysisPeriodicEvent(force)
4458      int force;
4459 {
4460     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4461          && !force) || !appData.periodicUpdates)
4462       return;
4463
4464     /* Send . command to Crafty to collect stats */
4465     SendToProgram(".\n", &first);
4466
4467     /* Don't send another until we get a response (this makes
4468        us stop sending to old Crafty's which don't understand
4469        the "." command (sending illegal cmds resets node count & time,
4470        which looks bad)) */
4471     programStats.ok_to_send = 0;
4472 }
4473
4474 void ics_update_width(new_width)
4475         int new_width;
4476 {
4477         ics_printf("set width %d\n", new_width);
4478 }
4479
4480 void
4481 SendMoveToProgram(moveNum, cps)
4482      int moveNum;
4483      ChessProgramState *cps;
4484 {
4485     char buf[MSG_SIZ];
4486
4487     if (cps->useUsermove) {
4488       SendToProgram("usermove ", cps);
4489     }
4490     if (cps->useSAN) {
4491       char *space;
4492       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4493         int len = space - parseList[moveNum];
4494         memcpy(buf, parseList[moveNum], len);
4495         buf[len++] = '\n';
4496         buf[len] = NULLCHAR;
4497       } else {
4498         sprintf(buf, "%s\n", parseList[moveNum]);
4499       }
4500       SendToProgram(buf, cps);
4501     } else {
4502       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4503         AlphaRank(moveList[moveNum], 4);
4504         SendToProgram(moveList[moveNum], cps);
4505         AlphaRank(moveList[moveNum], 4); // and back
4506       } else
4507       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4508        * the engine. It would be nice to have a better way to identify castle 
4509        * moves here. */
4510       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4511                                                                          && cps->useOOCastle) {
4512         int fromX = moveList[moveNum][0] - AAA; 
4513         int fromY = moveList[moveNum][1] - ONE;
4514         int toX = moveList[moveNum][2] - AAA; 
4515         int toY = moveList[moveNum][3] - ONE;
4516         if((boards[moveNum][fromY][fromX] == WhiteKing 
4517             && boards[moveNum][toY][toX] == WhiteRook)
4518            || (boards[moveNum][fromY][fromX] == BlackKing 
4519                && boards[moveNum][toY][toX] == BlackRook)) {
4520           if(toX > fromX) SendToProgram("O-O\n", cps);
4521           else SendToProgram("O-O-O\n", cps);
4522         }
4523         else SendToProgram(moveList[moveNum], cps);
4524       }
4525       else SendToProgram(moveList[moveNum], cps);
4526       /* End of additions by Tord */
4527     }
4528
4529     /* [HGM] setting up the opening has brought engine in force mode! */
4530     /*       Send 'go' if we are in a mode where machine should play. */
4531     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4532         (gameMode == TwoMachinesPlay   ||
4533 #if ZIPPY
4534          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4535 #endif
4536          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4537         SendToProgram("go\n", cps);
4538   if (appData.debugMode) {
4539     fprintf(debugFP, "(extra)\n");
4540   }
4541     }
4542     setboardSpoiledMachineBlack = 0;
4543 }
4544
4545 void
4546 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4547      ChessMove moveType;
4548      int fromX, fromY, toX, toY;
4549 {
4550     char user_move[MSG_SIZ];
4551
4552     switch (moveType) {
4553       default:
4554         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4555                 (int)moveType, fromX, fromY, toX, toY);
4556         DisplayError(user_move + strlen("say "), 0);
4557         break;
4558       case WhiteKingSideCastle:
4559       case BlackKingSideCastle:
4560       case WhiteQueenSideCastleWild:
4561       case BlackQueenSideCastleWild:
4562       /* PUSH Fabien */
4563       case WhiteHSideCastleFR:
4564       case BlackHSideCastleFR:
4565       /* POP Fabien */
4566         sprintf(user_move, "o-o\n");
4567         break;
4568       case WhiteQueenSideCastle:
4569       case BlackQueenSideCastle:
4570       case WhiteKingSideCastleWild:
4571       case BlackKingSideCastleWild:
4572       /* PUSH Fabien */
4573       case WhiteASideCastleFR:
4574       case BlackASideCastleFR:
4575       /* POP Fabien */
4576         sprintf(user_move, "o-o-o\n");
4577         break;
4578       case WhiteNonPromotion:
4579       case BlackNonPromotion:
4580         sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4581         break;
4582       case WhitePromotionQueen:
4583       case BlackPromotionQueen:
4584       case WhitePromotionRook:
4585       case BlackPromotionRook:
4586       case WhitePromotionBishop:
4587       case BlackPromotionBishop:
4588       case WhitePromotionKnight:
4589       case BlackPromotionKnight:
4590       case WhitePromotionKing:
4591       case BlackPromotionKing:
4592       case WhitePromotionChancellor:
4593       case BlackPromotionChancellor:
4594       case WhitePromotionArchbishop:
4595       case BlackPromotionArchbishop:
4596         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4597             sprintf(user_move, "%c%c%c%c=%c\n",
4598                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4599                 PieceToChar(WhiteFerz));
4600         else if(gameInfo.variant == VariantGreat)
4601             sprintf(user_move, "%c%c%c%c=%c\n",
4602                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4603                 PieceToChar(WhiteMan));
4604         else
4605             sprintf(user_move, "%c%c%c%c=%c\n",
4606                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4607                 PieceToChar(PromoPiece(moveType)));
4608         break;
4609       case WhiteDrop:
4610       case BlackDrop:
4611         sprintf(user_move, "%c@%c%c\n",
4612                 ToUpper(PieceToChar((ChessSquare) fromX)),
4613                 AAA + toX, ONE + toY);
4614         break;
4615       case NormalMove:
4616       case WhiteCapturesEnPassant:
4617       case BlackCapturesEnPassant:
4618       case IllegalMove:  /* could be a variant we don't quite understand */
4619         sprintf(user_move, "%c%c%c%c\n",
4620                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4621         break;
4622     }
4623     SendToICS(user_move);
4624     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4625         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4626 }
4627
4628 void
4629 UploadGameEvent()
4630 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4631     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4632     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4633     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4634         DisplayError("You cannot do this while you are playing or observing", 0);
4635         return;
4636     }
4637     if(gameMode != IcsExamining) { // is this ever not the case?
4638         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4639
4640         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4641             sprintf(command, "match %s", ics_handle);
4642         } else { // on FICS we must first go to general examine mode
4643             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4644         }
4645         if(gameInfo.variant != VariantNormal) {
4646             // try figure out wild number, as xboard names are not always valid on ICS
4647             for(i=1; i<=36; i++) {
4648                 sprintf(buf, "wild/%d", i);
4649                 if(StringToVariant(buf) == gameInfo.variant) break;
4650             }
4651             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4652             else if(i == 22) sprintf(buf, "%s fr\n", command);
4653             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4654         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4655         SendToICS(ics_prefix);
4656         SendToICS(buf);
4657         if(startedFromSetupPosition || backwardMostMove != 0) {
4658           fen = PositionToFEN(backwardMostMove, NULL);
4659           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4660             sprintf(buf, "loadfen %s\n", fen);
4661             SendToICS(buf);
4662           } else { // FICS: everything has to set by separate bsetup commands
4663             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4664             sprintf(buf, "bsetup fen %s\n", fen);
4665             SendToICS(buf);
4666             if(!WhiteOnMove(backwardMostMove)) {
4667                 SendToICS("bsetup tomove black\n");
4668             }
4669             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4670             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4671             SendToICS(buf);
4672             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4673             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4674             SendToICS(buf);
4675             i = boards[backwardMostMove][EP_STATUS];
4676             if(i >= 0) { // set e.p.
4677                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4678                 SendToICS(buf);
4679             }
4680             bsetup++;
4681           }
4682         }
4683       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4684             SendToICS("bsetup done\n"); // switch to normal examining.
4685     }
4686     for(i = backwardMostMove; i<last; i++) {
4687         char buf[20];
4688         sprintf(buf, "%s\n", parseList[i]);
4689         SendToICS(buf);
4690     }
4691     SendToICS(ics_prefix);
4692     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4693 }
4694
4695 void
4696 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4697      int rf, ff, rt, ft;
4698      char promoChar;
4699      char move[7];
4700 {
4701     if (rf == DROP_RANK) {
4702         sprintf(move, "%c@%c%c\n",
4703                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4704     } else {
4705         if (promoChar == 'x' || promoChar == NULLCHAR) {
4706             sprintf(move, "%c%c%c%c\n",
4707                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4708         } else {
4709             sprintf(move, "%c%c%c%c%c\n",
4710                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4711         }
4712     }
4713 }
4714
4715 void
4716 ProcessICSInitScript(f)
4717      FILE *f;
4718 {
4719     char buf[MSG_SIZ];
4720
4721     while (fgets(buf, MSG_SIZ, f)) {
4722         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4723     }
4724
4725     fclose(f);
4726 }
4727
4728
4729 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4730 void
4731 AlphaRank(char *move, int n)
4732 {
4733 //    char *p = move, c; int x, y;
4734
4735     if (appData.debugMode) {
4736         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4737     }
4738
4739     if(move[1]=='*' && 
4740        move[2]>='0' && move[2]<='9' &&
4741        move[3]>='a' && move[3]<='x'    ) {
4742         move[1] = '@';
4743         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4744         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4745     } else
4746     if(move[0]>='0' && move[0]<='9' &&
4747        move[1]>='a' && move[1]<='x' &&
4748        move[2]>='0' && move[2]<='9' &&
4749        move[3]>='a' && move[3]<='x'    ) {
4750         /* input move, Shogi -> normal */
4751         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4752         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4753         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4754         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4755     } else
4756     if(move[1]=='@' &&
4757        move[3]>='0' && move[3]<='9' &&
4758        move[2]>='a' && move[2]<='x'    ) {
4759         move[1] = '*';
4760         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4761         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4762     } else
4763     if(
4764        move[0]>='a' && move[0]<='x' &&
4765        move[3]>='0' && move[3]<='9' &&
4766        move[2]>='a' && move[2]<='x'    ) {
4767          /* output move, normal -> Shogi */
4768         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4769         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4770         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4771         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4772         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4773     }
4774     if (appData.debugMode) {
4775         fprintf(debugFP, "   out = '%s'\n", move);
4776     }
4777 }
4778
4779 char yy_textstr[8000];
4780
4781 /* Parser for moves from gnuchess, ICS, or user typein box */
4782 Boolean
4783 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4784      char *move;
4785      int moveNum;
4786      ChessMove *moveType;
4787      int *fromX, *fromY, *toX, *toY;
4788      char *promoChar;
4789 {       
4790     if (appData.debugMode) {
4791         fprintf(debugFP, "move to parse: %s\n", move);
4792     }
4793     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4794
4795     switch (*moveType) {
4796       case WhitePromotionChancellor:
4797       case BlackPromotionChancellor:
4798       case WhitePromotionArchbishop:
4799       case BlackPromotionArchbishop:
4800       case WhitePromotionQueen:
4801       case BlackPromotionQueen:
4802       case WhitePromotionRook:
4803       case BlackPromotionRook:
4804       case WhitePromotionBishop:
4805       case BlackPromotionBishop:
4806       case WhitePromotionKnight:
4807       case BlackPromotionKnight:
4808       case WhitePromotionKing:
4809       case BlackPromotionKing:
4810       case WhiteNonPromotion:
4811       case BlackNonPromotion:
4812       case NormalMove:
4813       case WhiteCapturesEnPassant:
4814       case BlackCapturesEnPassant:
4815       case WhiteKingSideCastle:
4816       case WhiteQueenSideCastle:
4817       case BlackKingSideCastle:
4818       case BlackQueenSideCastle:
4819       case WhiteKingSideCastleWild:
4820       case WhiteQueenSideCastleWild:
4821       case BlackKingSideCastleWild:
4822       case BlackQueenSideCastleWild:
4823       /* Code added by Tord: */
4824       case WhiteHSideCastleFR:
4825       case WhiteASideCastleFR:
4826       case BlackHSideCastleFR:
4827       case BlackASideCastleFR:
4828       /* End of code added by Tord */
4829       case IllegalMove:         /* bug or odd chess variant */
4830         *fromX = currentMoveString[0] - AAA;
4831         *fromY = currentMoveString[1] - ONE;
4832         *toX = currentMoveString[2] - AAA;
4833         *toY = currentMoveString[3] - ONE;
4834         *promoChar = currentMoveString[4];
4835         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4836             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4837     if (appData.debugMode) {
4838         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4839     }
4840             *fromX = *fromY = *toX = *toY = 0;
4841             return FALSE;
4842         }
4843         if (appData.testLegality) {
4844           return (*moveType != IllegalMove);
4845         } else {
4846           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4847                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4848         }
4849
4850       case WhiteDrop:
4851       case BlackDrop:
4852         *fromX = *moveType == WhiteDrop ?
4853           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4854           (int) CharToPiece(ToLower(currentMoveString[0]));
4855         *fromY = DROP_RANK;
4856         *toX = currentMoveString[2] - AAA;
4857         *toY = currentMoveString[3] - ONE;
4858         *promoChar = NULLCHAR;
4859         return TRUE;
4860
4861       case AmbiguousMove:
4862       case ImpossibleMove:
4863       case (ChessMove) 0:       /* end of file */
4864       case ElapsedTime:
4865       case Comment:
4866       case PGNTag:
4867       case NAG:
4868       case WhiteWins:
4869       case BlackWins:
4870       case GameIsDrawn:
4871       default:
4872     if (appData.debugMode) {
4873         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4874     }
4875         /* bug? */
4876         *fromX = *fromY = *toX = *toY = 0;
4877         *promoChar = NULLCHAR;
4878         return FALSE;
4879     }
4880 }
4881
4882
4883 void
4884 ParsePV(char *pv, Boolean storeComments)
4885 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4886   int fromX, fromY, toX, toY; char promoChar;
4887   ChessMove moveType;
4888   Boolean valid;
4889   int nr = 0;
4890
4891   endPV = forwardMostMove;
4892   do {
4893     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4894     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4895     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4896 if(appData.debugMode){
4897 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
4898 }
4899     if(!valid && nr == 0 &&
4900        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4901         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4902         // Hande case where played move is different from leading PV move
4903         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4904         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4905         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4906         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4907           endPV += 2; // if position different, keep this
4908           moveList[endPV-1][0] = fromX + AAA;
4909           moveList[endPV-1][1] = fromY + ONE;
4910           moveList[endPV-1][2] = toX + AAA;
4911           moveList[endPV-1][3] = toY + ONE;
4912           parseList[endPV-1][0] = NULLCHAR;
4913           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4914         }
4915       }
4916     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4917     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4918     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4919     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4920         valid++; // allow comments in PV
4921         continue;
4922     }
4923     nr++;
4924     if(endPV+1 > framePtr) break; // no space, truncate
4925     if(!valid) break;
4926     endPV++;
4927     CopyBoard(boards[endPV], boards[endPV-1]);
4928     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4929     moveList[endPV-1][0] = fromX + AAA;
4930     moveList[endPV-1][1] = fromY + ONE;
4931     moveList[endPV-1][2] = toX + AAA;
4932     moveList[endPV-1][3] = toY + ONE;
4933     if(storeComments)
4934         CoordsToAlgebraic(boards[endPV - 1],
4935                              PosFlags(endPV - 1),
4936                              fromY, fromX, toY, toX, promoChar,
4937                              parseList[endPV - 1]);
4938     else
4939         parseList[endPV-1][0] = NULLCHAR;
4940   } while(valid);
4941   currentMove = endPV;
4942   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4943   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4944                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4945   DrawPosition(TRUE, boards[currentMove]);
4946 }
4947
4948 static int lastX, lastY;
4949
4950 Boolean
4951 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4952 {
4953         int startPV;
4954         char *p;
4955
4956         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4957         lastX = x; lastY = y;
4958         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4959         startPV = index;
4960         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4961         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4962         index = startPV;
4963         do{ while(buf[index] && buf[index] != '\n') index++;
4964         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4965         buf[index] = 0;
4966         ParsePV(buf+startPV, FALSE);
4967         *start = startPV; *end = index-1;
4968         return TRUE;
4969 }
4970
4971 Boolean
4972 LoadPV(int x, int y)
4973 { // called on right mouse click to load PV
4974   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4975   lastX = x; lastY = y;
4976   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4977   return TRUE;
4978 }
4979
4980 void
4981 UnLoadPV()
4982 {
4983   if(endPV < 0) return;
4984   endPV = -1;
4985   currentMove = forwardMostMove;
4986   ClearPremoveHighlights();
4987   DrawPosition(TRUE, boards[currentMove]);
4988 }
4989
4990 void
4991 MovePV(int x, int y, int h)
4992 { // step through PV based on mouse coordinates (called on mouse move)
4993   int margin = h>>3, step = 0;
4994
4995   if(endPV < 0) return;
4996   // we must somehow check if right button is still down (might be released off board!)
4997   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4998   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4999   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5000   if(!step) return;
5001   lastX = x; lastY = y;
5002   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5003   currentMove += step;
5004   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5005   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5006                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5007   DrawPosition(FALSE, boards[currentMove]);
5008 }
5009
5010
5011 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5012 // All positions will have equal probability, but the current method will not provide a unique
5013 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5014 #define DARK 1
5015 #define LITE 2
5016 #define ANY 3
5017
5018 int squaresLeft[4];
5019 int piecesLeft[(int)BlackPawn];
5020 int seed, nrOfShuffles;
5021
5022 void GetPositionNumber()
5023 {       // sets global variable seed
5024         int i;
5025
5026         seed = appData.defaultFrcPosition;
5027         if(seed < 0) { // randomize based on time for negative FRC position numbers
5028                 for(i=0; i<50; i++) seed += random();
5029                 seed = random() ^ random() >> 8 ^ random() << 8;
5030                 if(seed<0) seed = -seed;
5031         }
5032 }
5033
5034 int put(Board board, int pieceType, int rank, int n, int shade)
5035 // put the piece on the (n-1)-th empty squares of the given shade
5036 {
5037         int i;
5038
5039         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5040                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5041                         board[rank][i] = (ChessSquare) pieceType;
5042                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5043                         squaresLeft[ANY]--;
5044                         piecesLeft[pieceType]--; 
5045                         return i;
5046                 }
5047         }
5048         return -1;
5049 }
5050
5051
5052 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5053 // calculate where the next piece goes, (any empty square), and put it there
5054 {
5055         int i;
5056
5057         i = seed % squaresLeft[shade];
5058         nrOfShuffles *= squaresLeft[shade];
5059         seed /= squaresLeft[shade];
5060         put(board, pieceType, rank, i, shade);
5061 }
5062
5063 void AddTwoPieces(Board board, int pieceType, int rank)
5064 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5065 {
5066         int i, n=squaresLeft[ANY], j=n-1, k;
5067
5068         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5069         i = seed % k;  // pick one
5070         nrOfShuffles *= k;
5071         seed /= k;
5072         while(i >= j) i -= j--;
5073         j = n - 1 - j; i += j;
5074         put(board, pieceType, rank, j, ANY);
5075         put(board, pieceType, rank, i, ANY);
5076 }
5077
5078 void SetUpShuffle(Board board, int number)
5079 {
5080         int i, p, first=1;
5081
5082         GetPositionNumber(); nrOfShuffles = 1;
5083
5084         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5085         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5086         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5087
5088         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5089
5090         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5091             p = (int) board[0][i];
5092             if(p < (int) BlackPawn) piecesLeft[p] ++;
5093             board[0][i] = EmptySquare;
5094         }
5095
5096         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5097             // shuffles restricted to allow normal castling put KRR first
5098             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5099                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5100             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5101                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5102             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5103                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5104             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5105                 put(board, WhiteRook, 0, 0, ANY);
5106             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5107         }
5108
5109         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5110             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5111             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5112                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5113                 while(piecesLeft[p] >= 2) {
5114                     AddOnePiece(board, p, 0, LITE);
5115                     AddOnePiece(board, p, 0, DARK);
5116                 }
5117                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5118             }
5119
5120         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5121             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5122             // but we leave King and Rooks for last, to possibly obey FRC restriction
5123             if(p == (int)WhiteRook) continue;
5124             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5125             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5126         }
5127
5128         // now everything is placed, except perhaps King (Unicorn) and Rooks
5129
5130         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5131             // Last King gets castling rights
5132             while(piecesLeft[(int)WhiteUnicorn]) {
5133                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5134                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5135             }
5136
5137             while(piecesLeft[(int)WhiteKing]) {
5138                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5139                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5140             }
5141
5142
5143         } else {
5144             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5145             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5146         }
5147
5148         // Only Rooks can be left; simply place them all
5149         while(piecesLeft[(int)WhiteRook]) {
5150                 i = put(board, WhiteRook, 0, 0, ANY);
5151                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5152                         if(first) {
5153                                 first=0;
5154                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5155                         }
5156                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5157                 }
5158         }
5159         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5160             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5161         }
5162
5163         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5164 }
5165
5166 int SetCharTable( char *table, const char * map )
5167 /* [HGM] moved here from winboard.c because of its general usefulness */
5168 /*       Basically a safe strcpy that uses the last character as King */
5169 {
5170     int result = FALSE; int NrPieces;
5171
5172     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5173                     && NrPieces >= 12 && !(NrPieces&1)) {
5174         int i; /* [HGM] Accept even length from 12 to 34 */
5175
5176         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5177         for( i=0; i<NrPieces/2-1; i++ ) {
5178             table[i] = map[i];
5179             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5180         }
5181         table[(int) WhiteKing]  = map[NrPieces/2-1];
5182         table[(int) BlackKing]  = map[NrPieces-1];
5183
5184         result = TRUE;
5185     }
5186
5187     return result;
5188 }
5189
5190 void Prelude(Board board)
5191 {       // [HGM] superchess: random selection of exo-pieces
5192         int i, j, k; ChessSquare p; 
5193         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5194
5195         GetPositionNumber(); // use FRC position number
5196
5197         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5198             SetCharTable(pieceToChar, appData.pieceToCharTable);
5199             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5200                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5201         }
5202
5203         j = seed%4;                 seed /= 4; 
5204         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5205         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5206         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5207         j = seed%3 + (seed%3 >= j); seed /= 3; 
5208         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5209         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5210         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5211         j = seed%3;                 seed /= 3; 
5212         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5213         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5214         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5215         j = seed%2 + (seed%2 >= j); seed /= 2; 
5216         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5217         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5218         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5219         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5220         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5221         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5222         put(board, exoPieces[0],    0, 0, ANY);
5223         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5224 }
5225
5226 void
5227 InitPosition(redraw)
5228      int redraw;
5229 {
5230     ChessSquare (* pieces)[BOARD_FILES];
5231     int i, j, pawnRow, overrule,
5232     oldx = gameInfo.boardWidth,
5233     oldy = gameInfo.boardHeight,
5234     oldh = gameInfo.holdingsWidth,
5235     oldv = gameInfo.variant;
5236
5237     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5238
5239     /* [AS] Initialize pv info list [HGM] and game status */
5240     {
5241         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5242             pvInfoList[i].depth = 0;
5243             boards[i][EP_STATUS] = EP_NONE;
5244             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5245         }
5246
5247         initialRulePlies = 0; /* 50-move counter start */
5248
5249         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5250         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5251     }
5252
5253     
5254     /* [HGM] logic here is completely changed. In stead of full positions */
5255     /* the initialized data only consist of the two backranks. The switch */
5256     /* selects which one we will use, which is than copied to the Board   */
5257     /* initialPosition, which for the rest is initialized by Pawns and    */
5258     /* empty squares. This initial position is then copied to boards[0],  */
5259     /* possibly after shuffling, so that it remains available.            */
5260
5261     gameInfo.holdingsWidth = 0; /* default board sizes */
5262     gameInfo.boardWidth    = 8;
5263     gameInfo.boardHeight   = 8;
5264     gameInfo.holdingsSize  = 0;
5265     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5266     for(i=0; i<BOARD_FILES-2; i++)
5267       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5268     initialPosition[EP_STATUS] = EP_NONE;
5269     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5270     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5271          SetCharTable(pieceNickName, appData.pieceNickNames);
5272     else SetCharTable(pieceNickName, "............");
5273
5274     switch (gameInfo.variant) {
5275     case VariantFischeRandom:
5276       shuffleOpenings = TRUE;
5277     default:
5278       pieces = FIDEArray;
5279       break;
5280     case VariantShatranj:
5281       pieces = ShatranjArray;
5282       nrCastlingRights = 0;
5283       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5284       break;
5285     case VariantMakruk:
5286       pieces = makrukArray;
5287       nrCastlingRights = 0;
5288       startedFromSetupPosition = TRUE;
5289       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5290       break;
5291     case VariantTwoKings:
5292       pieces = twoKingsArray;
5293       break;
5294     case VariantCapaRandom:
5295       shuffleOpenings = TRUE;
5296     case VariantCapablanca:
5297       pieces = CapablancaArray;
5298       gameInfo.boardWidth = 10;
5299       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5300       break;
5301     case VariantGothic:
5302       pieces = GothicArray;
5303       gameInfo.boardWidth = 10;
5304       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5305       break;
5306     case VariantJanus:
5307       pieces = JanusArray;
5308       gameInfo.boardWidth = 10;
5309       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5310       nrCastlingRights = 6;
5311         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5312         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5313         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5314         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5315         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5316         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5317       break;
5318     case VariantFalcon:
5319       pieces = FalconArray;
5320       gameInfo.boardWidth = 10;
5321       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5322       break;
5323     case VariantXiangqi:
5324       pieces = XiangqiArray;
5325       gameInfo.boardWidth  = 9;
5326       gameInfo.boardHeight = 10;
5327       nrCastlingRights = 0;
5328       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5329       break;
5330     case VariantShogi:
5331       pieces = ShogiArray;
5332       gameInfo.boardWidth  = 9;
5333       gameInfo.boardHeight = 9;
5334       gameInfo.holdingsSize = 7;
5335       nrCastlingRights = 0;
5336       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5337       break;
5338     case VariantCourier:
5339       pieces = CourierArray;
5340       gameInfo.boardWidth  = 12;
5341       nrCastlingRights = 0;
5342       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5343       break;
5344     case VariantKnightmate:
5345       pieces = KnightmateArray;
5346       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5347       break;
5348     case VariantFairy:
5349       pieces = fairyArray;
5350       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5351       break;
5352     case VariantGreat:
5353       pieces = GreatArray;
5354       gameInfo.boardWidth = 10;
5355       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5356       gameInfo.holdingsSize = 8;
5357       break;
5358     case VariantSuper:
5359       pieces = FIDEArray;
5360       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5361       gameInfo.holdingsSize = 8;
5362       startedFromSetupPosition = TRUE;
5363       break;
5364     case VariantCrazyhouse:
5365     case VariantBughouse:
5366       pieces = FIDEArray;
5367       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5368       gameInfo.holdingsSize = 5;
5369       break;
5370     case VariantWildCastle:
5371       pieces = FIDEArray;
5372       /* !!?shuffle with kings guaranteed to be on d or e file */
5373       shuffleOpenings = 1;
5374       break;
5375     case VariantNoCastle:
5376       pieces = FIDEArray;
5377       nrCastlingRights = 0;
5378       /* !!?unconstrained back-rank shuffle */
5379       shuffleOpenings = 1;
5380       break;
5381     }
5382
5383     overrule = 0;
5384     if(appData.NrFiles >= 0) {
5385         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5386         gameInfo.boardWidth = appData.NrFiles;
5387     }
5388     if(appData.NrRanks >= 0) {
5389         gameInfo.boardHeight = appData.NrRanks;
5390     }
5391     if(appData.holdingsSize >= 0) {
5392         i = appData.holdingsSize;
5393         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5394         gameInfo.holdingsSize = i;
5395     }
5396     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5397     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5398         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5399
5400     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5401     if(pawnRow < 1) pawnRow = 1;
5402     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5403
5404     /* User pieceToChar list overrules defaults */
5405     if(appData.pieceToCharTable != NULL)
5406         SetCharTable(pieceToChar, appData.pieceToCharTable);
5407
5408     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5409
5410         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5411             s = (ChessSquare) 0; /* account holding counts in guard band */
5412         for( i=0; i<BOARD_HEIGHT; i++ )
5413             initialPosition[i][j] = s;
5414
5415         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5416         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5417         initialPosition[pawnRow][j] = WhitePawn;
5418         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5419         if(gameInfo.variant == VariantXiangqi) {
5420             if(j&1) {
5421                 initialPosition[pawnRow][j] = 
5422                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5423                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5424                    initialPosition[2][j] = WhiteCannon;
5425                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5426                 }
5427             }
5428         }
5429         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5430     }
5431     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5432
5433             j=BOARD_LEFT+1;
5434             initialPosition[1][j] = WhiteBishop;
5435             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5436             j=BOARD_RGHT-2;
5437             initialPosition[1][j] = WhiteRook;
5438             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5439     }
5440
5441     if( nrCastlingRights == -1) {
5442         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5443         /*       This sets default castling rights from none to normal corners   */
5444         /* Variants with other castling rights must set them themselves above    */
5445         nrCastlingRights = 6;
5446        
5447         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5448         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5449         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5450         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5451         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5452         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5453      }
5454
5455      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5456      if(gameInfo.variant == VariantGreat) { // promotion commoners
5457         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5458         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5459         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5460         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5461      }
5462   if (appData.debugMode) {
5463     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5464   }
5465     if(shuffleOpenings) {
5466         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5467         startedFromSetupPosition = TRUE;
5468     }
5469     if(startedFromPositionFile) {
5470       /* [HGM] loadPos: use PositionFile for every new game */
5471       CopyBoard(initialPosition, filePosition);
5472       for(i=0; i<nrCastlingRights; i++)
5473           initialRights[i] = filePosition[CASTLING][i];
5474       startedFromSetupPosition = TRUE;
5475     }
5476
5477     CopyBoard(boards[0], initialPosition);
5478
5479     if(oldx != gameInfo.boardWidth ||
5480        oldy != gameInfo.boardHeight ||
5481        oldh != gameInfo.holdingsWidth
5482 #ifdef GOTHIC
5483        || oldv == VariantGothic ||        // For licensing popups
5484        gameInfo.variant == VariantGothic
5485 #endif
5486 #ifdef FALCON
5487        || oldv == VariantFalcon ||
5488        gameInfo.variant == VariantFalcon
5489 #endif
5490                                          )
5491             InitDrawingSizes(-2 ,0);
5492
5493     if (redraw)
5494       DrawPosition(TRUE, boards[currentMove]);
5495 }
5496
5497 void
5498 SendBoard(cps, moveNum)
5499      ChessProgramState *cps;
5500      int moveNum;
5501 {
5502     char message[MSG_SIZ];
5503     
5504     if (cps->useSetboard) {
5505       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5506       sprintf(message, "setboard %s\n", fen);
5507       SendToProgram(message, cps);
5508       free(fen);
5509
5510     } else {
5511       ChessSquare *bp;
5512       int i, j;
5513       /* Kludge to set black to move, avoiding the troublesome and now
5514        * deprecated "black" command.
5515        */
5516       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5517
5518       SendToProgram("edit\n", cps);
5519       SendToProgram("#\n", cps);
5520       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5521         bp = &boards[moveNum][i][BOARD_LEFT];
5522         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5523           if ((int) *bp < (int) BlackPawn) {
5524             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5525                     AAA + j, ONE + i);
5526             if(message[0] == '+' || message[0] == '~') {
5527                 sprintf(message, "%c%c%c+\n",
5528                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5529                         AAA + j, ONE + i);
5530             }
5531             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5532                 message[1] = BOARD_RGHT   - 1 - j + '1';
5533                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5534             }
5535             SendToProgram(message, cps);
5536           }
5537         }
5538       }
5539     
5540       SendToProgram("c\n", cps);
5541       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5542         bp = &boards[moveNum][i][BOARD_LEFT];
5543         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5544           if (((int) *bp != (int) EmptySquare)
5545               && ((int) *bp >= (int) BlackPawn)) {
5546             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5547                     AAA + j, ONE + i);
5548             if(message[0] == '+' || message[0] == '~') {
5549                 sprintf(message, "%c%c%c+\n",
5550                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5551                         AAA + j, ONE + i);
5552             }
5553             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5554                 message[1] = BOARD_RGHT   - 1 - j + '1';
5555                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5556             }
5557             SendToProgram(message, cps);
5558           }
5559         }
5560       }
5561     
5562       SendToProgram(".\n", cps);
5563     }
5564     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5565 }
5566
5567 static int autoQueen; // [HGM] oneclick
5568
5569 int
5570 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5571 {
5572     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5573     /* [HGM] add Shogi promotions */
5574     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5575     ChessSquare piece;
5576     ChessMove moveType;
5577     Boolean premove;
5578
5579     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5580     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5581
5582     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5583       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5584         return FALSE;
5585
5586     piece = boards[currentMove][fromY][fromX];
5587     if(gameInfo.variant == VariantShogi) {
5588         promotionZoneSize = 3;
5589         highestPromotingPiece = (int)WhiteFerz;
5590     } else if(gameInfo.variant == VariantMakruk) {
5591         promotionZoneSize = 3;
5592     }
5593
5594     // next weed out all moves that do not touch the promotion zone at all
5595     if((int)piece >= BlackPawn) {
5596         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5597              return FALSE;
5598         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5599     } else {
5600         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5601            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5602     }
5603
5604     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5605
5606     // weed out mandatory Shogi promotions
5607     if(gameInfo.variant == VariantShogi) {
5608         if(piece >= BlackPawn) {
5609             if(toY == 0 && piece == BlackPawn ||
5610                toY == 0 && piece == BlackQueen ||
5611                toY <= 1 && piece == BlackKnight) {
5612                 *promoChoice = '+';
5613                 return FALSE;
5614             }
5615         } else {
5616             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5617                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5618                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5619                 *promoChoice = '+';
5620                 return FALSE;
5621             }
5622         }
5623     }
5624
5625     // weed out obviously illegal Pawn moves
5626     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5627         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5628         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5629         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5630         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5631         // note we are not allowed to test for valid (non-)capture, due to premove
5632     }
5633
5634     // we either have a choice what to promote to, or (in Shogi) whether to promote
5635     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5636         *promoChoice = PieceToChar(BlackFerz);  // no choice
5637         return FALSE;
5638     }
5639     if(autoQueen) { // predetermined
5640         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5641              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5642         else *promoChoice = PieceToChar(BlackQueen);
5643         return FALSE;
5644     }
5645
5646     // suppress promotion popup on illegal moves that are not premoves
5647     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5648               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5649     if(appData.testLegality && !premove) {
5650         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5651                         fromY, fromX, toY, toX, NULLCHAR);
5652         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5653            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5654             return FALSE;
5655     }
5656
5657     return TRUE;
5658 }
5659
5660 int
5661 InPalace(row, column)
5662      int row, column;
5663 {   /* [HGM] for Xiangqi */
5664     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5665          column < (BOARD_WIDTH + 4)/2 &&
5666          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5667     return FALSE;
5668 }
5669
5670 int
5671 PieceForSquare (x, y)
5672      int x;
5673      int y;
5674 {
5675   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5676      return -1;
5677   else
5678      return boards[currentMove][y][x];
5679 }
5680
5681 int
5682 OKToStartUserMove(x, y)
5683      int x, y;
5684 {
5685     ChessSquare from_piece;
5686     int white_piece;
5687
5688     if (matchMode) return FALSE;
5689     if (gameMode == EditPosition) return TRUE;
5690
5691     if (x >= 0 && y >= 0)
5692       from_piece = boards[currentMove][y][x];
5693     else
5694       from_piece = EmptySquare;
5695
5696     if (from_piece == EmptySquare) return FALSE;
5697
5698     white_piece = (int)from_piece >= (int)WhitePawn &&
5699       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5700
5701     switch (gameMode) {
5702       case PlayFromGameFile:
5703       case AnalyzeFile:
5704       case TwoMachinesPlay:
5705       case EndOfGame:
5706         return FALSE;
5707
5708       case IcsObserving:
5709       case IcsIdle:
5710         return FALSE;
5711
5712       case MachinePlaysWhite:
5713       case IcsPlayingBlack:
5714         if (appData.zippyPlay) return FALSE;
5715         if (white_piece) {
5716             DisplayMoveError(_("You are playing Black"));
5717             return FALSE;
5718         }
5719         break;
5720
5721       case MachinePlaysBlack:
5722       case IcsPlayingWhite:
5723         if (appData.zippyPlay) return FALSE;
5724         if (!white_piece) {
5725             DisplayMoveError(_("You are playing White"));
5726             return FALSE;
5727         }
5728         break;
5729
5730       case EditGame:
5731         if (!white_piece && WhiteOnMove(currentMove)) {
5732             DisplayMoveError(_("It is White's turn"));
5733             return FALSE;
5734         }           
5735         if (white_piece && !WhiteOnMove(currentMove)) {
5736             DisplayMoveError(_("It is Black's turn"));
5737             return FALSE;
5738         }           
5739         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5740             /* Editing correspondence game history */
5741             /* Could disallow this or prompt for confirmation */
5742             cmailOldMove = -1;
5743         }
5744         break;
5745
5746       case BeginningOfGame:
5747         if (appData.icsActive) return FALSE;
5748         if (!appData.noChessProgram) {
5749             if (!white_piece) {
5750                 DisplayMoveError(_("You are playing White"));
5751                 return FALSE;
5752             }
5753         }
5754         break;
5755         
5756       case Training:
5757         if (!white_piece && WhiteOnMove(currentMove)) {
5758             DisplayMoveError(_("It is White's turn"));
5759             return FALSE;
5760         }           
5761         if (white_piece && !WhiteOnMove(currentMove)) {
5762             DisplayMoveError(_("It is Black's turn"));
5763             return FALSE;
5764         }           
5765         break;
5766
5767       default:
5768       case IcsExamining:
5769         break;
5770     }
5771     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5772         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5773         && gameMode != AnalyzeFile && gameMode != Training) {
5774         DisplayMoveError(_("Displayed position is not current"));
5775         return FALSE;
5776     }
5777     return TRUE;
5778 }
5779
5780 Boolean
5781 OnlyMove(int *x, int *y, Boolean captures) {
5782     DisambiguateClosure cl;
5783     if (appData.zippyPlay) return FALSE;
5784     switch(gameMode) {
5785       case MachinePlaysBlack:
5786       case IcsPlayingWhite:
5787       case BeginningOfGame:
5788         if(!WhiteOnMove(currentMove)) return FALSE;
5789         break;
5790       case MachinePlaysWhite:
5791       case IcsPlayingBlack:
5792         if(WhiteOnMove(currentMove)) return FALSE;
5793         break;
5794       default:
5795         return FALSE;
5796     }
5797     cl.pieceIn = EmptySquare; 
5798     cl.rfIn = *y;
5799     cl.ffIn = *x;
5800     cl.rtIn = -1;
5801     cl.ftIn = -1;
5802     cl.promoCharIn = NULLCHAR;
5803     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5804     if( cl.kind == NormalMove ||
5805         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5806         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5807         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5808         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5809       fromX = cl.ff;
5810       fromY = cl.rf;
5811       *x = cl.ft;
5812       *y = cl.rt;
5813       return TRUE;
5814     }
5815     if(cl.kind != ImpossibleMove) return FALSE;
5816     cl.pieceIn = EmptySquare;
5817     cl.rfIn = -1;
5818     cl.ffIn = -1;
5819     cl.rtIn = *y;
5820     cl.ftIn = *x;
5821     cl.promoCharIn = NULLCHAR;
5822     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5823     if( cl.kind == NormalMove ||
5824         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5825         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5826         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5827         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5828       fromX = cl.ff;
5829       fromY = cl.rf;
5830       *x = cl.ft;
5831       *y = cl.rt;
5832       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5833       return TRUE;
5834     }
5835     return FALSE;
5836 }
5837
5838 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5839 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5840 int lastLoadGameUseList = FALSE;
5841 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5842 ChessMove lastLoadGameStart = (ChessMove) 0;
5843
5844 void
5845 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5846      int fromX, fromY, toX, toY;
5847      int promoChar;
5848 {
5849     ChessMove moveType;
5850     ChessSquare pdown, pup;
5851
5852     /* Check if the user is playing in turn.  This is complicated because we
5853        let the user "pick up" a piece before it is his turn.  So the piece he
5854        tried to pick up may have been captured by the time he puts it down!
5855        Therefore we use the color the user is supposed to be playing in this
5856        test, not the color of the piece that is currently on the starting
5857        square---except in EditGame mode, where the user is playing both
5858        sides; fortunately there the capture race can't happen.  (It can
5859        now happen in IcsExamining mode, but that's just too bad.  The user
5860        will get a somewhat confusing message in that case.)
5861        */
5862
5863     switch (gameMode) {
5864       case PlayFromGameFile:
5865       case AnalyzeFile:
5866       case TwoMachinesPlay:
5867       case EndOfGame:
5868       case IcsObserving:
5869       case IcsIdle:
5870         /* We switched into a game mode where moves are not accepted,
5871            perhaps while the mouse button was down. */
5872         return;
5873
5874       case MachinePlaysWhite:
5875         /* User is moving for Black */
5876         if (WhiteOnMove(currentMove)) {
5877             DisplayMoveError(_("It is White's turn"));
5878             return;
5879         }
5880         break;
5881
5882       case MachinePlaysBlack:
5883         /* User is moving for White */
5884         if (!WhiteOnMove(currentMove)) {
5885             DisplayMoveError(_("It is Black's turn"));
5886             return;
5887         }
5888         break;
5889
5890       case EditGame:
5891       case IcsExamining:
5892       case BeginningOfGame:
5893       case AnalyzeMode:
5894       case Training:
5895         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5896             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5897             /* User is moving for Black */
5898             if (WhiteOnMove(currentMove)) {
5899                 DisplayMoveError(_("It is White's turn"));
5900                 return;
5901             }
5902         } else {
5903             /* User is moving for White */
5904             if (!WhiteOnMove(currentMove)) {
5905                 DisplayMoveError(_("It is Black's turn"));
5906                 return;
5907             }
5908         }
5909         break;
5910
5911       case IcsPlayingBlack:
5912         /* User is moving for Black */
5913         if (WhiteOnMove(currentMove)) {
5914             if (!appData.premove) {
5915                 DisplayMoveError(_("It is White's turn"));
5916             } else if (toX >= 0 && toY >= 0) {
5917                 premoveToX = toX;
5918                 premoveToY = toY;
5919                 premoveFromX = fromX;
5920                 premoveFromY = fromY;
5921                 premovePromoChar = promoChar;
5922                 gotPremove = 1;
5923                 if (appData.debugMode) 
5924                     fprintf(debugFP, "Got premove: fromX %d,"
5925                             "fromY %d, toX %d, toY %d\n",
5926                             fromX, fromY, toX, toY);
5927             }
5928             return;
5929         }
5930         break;
5931
5932       case IcsPlayingWhite:
5933         /* User is moving for White */
5934         if (!WhiteOnMove(currentMove)) {
5935             if (!appData.premove) {
5936                 DisplayMoveError(_("It is Black's turn"));
5937             } else if (toX >= 0 && toY >= 0) {
5938                 premoveToX = toX;
5939                 premoveToY = toY;
5940                 premoveFromX = fromX;
5941                 premoveFromY = fromY;
5942                 premovePromoChar = promoChar;
5943                 gotPremove = 1;
5944                 if (appData.debugMode) 
5945                     fprintf(debugFP, "Got premove: fromX %d,"
5946                             "fromY %d, toX %d, toY %d\n",
5947                             fromX, fromY, toX, toY);
5948             }
5949             return;
5950         }
5951         break;
5952
5953       default:
5954         break;
5955
5956       case EditPosition:
5957         /* EditPosition, empty square, or different color piece;
5958            click-click move is possible */
5959         if (toX == -2 || toY == -2) {
5960             boards[0][fromY][fromX] = EmptySquare;
5961             DrawPosition(FALSE, boards[currentMove]);
5962             return;
5963         } else if (toX >= 0 && toY >= 0) {
5964             boards[0][toY][toX] = boards[0][fromY][fromX];
5965             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5966                 if(boards[0][fromY][0] != EmptySquare) {
5967                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5968                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5969                 }
5970             } else
5971             if(fromX == BOARD_RGHT+1) {
5972                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5973                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5974                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5975                 }
5976             } else
5977             boards[0][fromY][fromX] = EmptySquare;
5978             DrawPosition(FALSE, boards[currentMove]);
5979             return;
5980         }
5981         return;
5982     }
5983
5984     if(toX < 0 || toY < 0) return;
5985     pdown = boards[currentMove][fromY][fromX];
5986     pup = boards[currentMove][toY][toX];
5987
5988     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
5989     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5990          if( pup != EmptySquare ) return;
5991          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5992            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5993                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5994            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5995            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5996            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5997            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5998          fromY = DROP_RANK;
5999     } else
6000
6001     /* [HGM] always test for legality, to get promotion info */
6002     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6003                                          fromY, fromX, toY, toX, promoChar);
6004     /* [HGM] but possibly ignore an IllegalMove result */
6005     if (appData.testLegality) {
6006         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6007             DisplayMoveError(_("Illegal move"));
6008             return;
6009         }
6010     }
6011
6012     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6013 }
6014
6015 /* Common tail of UserMoveEvent and DropMenuEvent */
6016 int
6017 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6018      ChessMove moveType;
6019      int fromX, fromY, toX, toY;
6020      /*char*/int promoChar;
6021 {
6022     char *bookHit = 0;
6023
6024     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
6025         // [HGM] superchess: suppress promotions to non-available piece
6026         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6027         if(WhiteOnMove(currentMove)) {
6028             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6029         } else {
6030             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6031         }
6032     }
6033
6034     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6035        move type in caller when we know the move is a legal promotion */
6036     if(moveType == NormalMove && promoChar)
6037         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6038
6039     /* [HGM] <popupFix> The following if has been moved here from
6040        UserMoveEvent(). Because it seemed to belong here (why not allow
6041        piece drops in training games?), and because it can only be
6042        performed after it is known to what we promote. */
6043     if (gameMode == Training) {
6044       /* compare the move played on the board to the next move in the
6045        * game. If they match, display the move and the opponent's response. 
6046        * If they don't match, display an error message.
6047        */
6048       int saveAnimate;
6049       Board testBoard;
6050       CopyBoard(testBoard, boards[currentMove]);
6051       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6052
6053       if (CompareBoards(testBoard, boards[currentMove+1])) {
6054         ForwardInner(currentMove+1);
6055
6056         /* Autoplay the opponent's response.
6057          * if appData.animate was TRUE when Training mode was entered,
6058          * the response will be animated.
6059          */
6060         saveAnimate = appData.animate;
6061         appData.animate = animateTraining;
6062         ForwardInner(currentMove+1);
6063         appData.animate = saveAnimate;
6064
6065         /* check for the end of the game */
6066         if (currentMove >= forwardMostMove) {
6067           gameMode = PlayFromGameFile;
6068           ModeHighlight();
6069           SetTrainingModeOff();
6070           DisplayInformation(_("End of game"));
6071         }
6072       } else {
6073         DisplayError(_("Incorrect move"), 0);
6074       }
6075       return 1;
6076     }
6077
6078   /* Ok, now we know that the move is good, so we can kill
6079      the previous line in Analysis Mode */
6080   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6081                                 && currentMove < forwardMostMove) {
6082     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6083   }
6084
6085   /* If we need the chess program but it's dead, restart it */
6086   ResurrectChessProgram();
6087
6088   /* A user move restarts a paused game*/
6089   if (pausing)
6090     PauseEvent();
6091
6092   thinkOutput[0] = NULLCHAR;
6093
6094   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6095
6096   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6097
6098   if (gameMode == BeginningOfGame) {
6099     if (appData.noChessProgram) {
6100       gameMode = EditGame;
6101       SetGameInfo();
6102     } else {
6103       char buf[MSG_SIZ];
6104       gameMode = MachinePlaysBlack;
6105       StartClocks();
6106       SetGameInfo();
6107       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6108       DisplayTitle(buf);
6109       if (first.sendName) {
6110         sprintf(buf, "name %s\n", gameInfo.white);
6111         SendToProgram(buf, &first);
6112       }
6113       StartClocks();
6114     }
6115     ModeHighlight();
6116   }
6117
6118   /* Relay move to ICS or chess engine */
6119   if (appData.icsActive) {
6120     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6121         gameMode == IcsExamining) {
6122       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6123         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6124         SendToICS("draw ");
6125         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6126       }
6127       // also send plain move, in case ICS does not understand atomic claims
6128       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6129       ics_user_moved = 1;
6130     }
6131   } else {
6132     if (first.sendTime && (gameMode == BeginningOfGame ||
6133                            gameMode == MachinePlaysWhite ||
6134                            gameMode == MachinePlaysBlack)) {
6135       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6136     }
6137     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6138          // [HGM] book: if program might be playing, let it use book
6139         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6140         first.maybeThinking = TRUE;
6141     } else SendMoveToProgram(forwardMostMove-1, &first);
6142     if (currentMove == cmailOldMove + 1) {
6143       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6144     }
6145   }
6146
6147   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6148
6149   switch (gameMode) {
6150   case EditGame:
6151     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6152     case MT_NONE:
6153     case MT_CHECK:
6154       break;
6155     case MT_CHECKMATE:
6156     case MT_STAINMATE:
6157       if (WhiteOnMove(currentMove)) {
6158         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6159       } else {
6160         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6161       }
6162       break;
6163     case MT_STALEMATE:
6164       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6165       break;
6166     }
6167     break;
6168     
6169   case MachinePlaysBlack:
6170   case MachinePlaysWhite:
6171     /* disable certain menu options while machine is thinking */
6172     SetMachineThinkingEnables();
6173     break;
6174
6175   default:
6176     break;
6177   }
6178
6179   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6180         
6181   if(bookHit) { // [HGM] book: simulate book reply
6182         static char bookMove[MSG_SIZ]; // a bit generous?
6183
6184         programStats.nodes = programStats.depth = programStats.time = 
6185         programStats.score = programStats.got_only_move = 0;
6186         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6187
6188         strcpy(bookMove, "move ");
6189         strcat(bookMove, bookHit);
6190         HandleMachineMove(bookMove, &first);
6191   }
6192   return 1;
6193 }
6194
6195 void
6196 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6197      Board board;
6198      int flags;
6199      ChessMove kind;
6200      int rf, ff, rt, ft;
6201      VOIDSTAR closure;
6202 {
6203     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6204     Markers *m = (Markers *) closure;
6205     if(rf == fromY && ff == fromX)
6206         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6207                          || kind == WhiteCapturesEnPassant
6208                          || kind == BlackCapturesEnPassant);
6209     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6210 }
6211
6212 void
6213 MarkTargetSquares(int clear)
6214 {
6215   int x, y;
6216   if(!appData.markers || !appData.highlightDragging || 
6217      !appData.testLegality || gameMode == EditPosition) return;
6218   if(clear) {
6219     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6220   } else {
6221     int capt = 0;
6222     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6223     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6224       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6225       if(capt)
6226       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6227     }
6228   }
6229   DrawPosition(TRUE, NULL);
6230 }
6231
6232 void LeftClick(ClickType clickType, int xPix, int yPix)
6233 {
6234     int x, y;
6235     Boolean saveAnimate;
6236     static int second = 0, promotionChoice = 0, dragging = 0;
6237     char promoChoice = NULLCHAR;
6238
6239     if(appData.seekGraph && appData.icsActive && loggedOn &&
6240         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6241         SeekGraphClick(clickType, xPix, yPix, 0);
6242         return;
6243     }
6244
6245     if (clickType == Press) ErrorPopDown();
6246     MarkTargetSquares(1);
6247
6248     x = EventToSquare(xPix, BOARD_WIDTH);
6249     y = EventToSquare(yPix, BOARD_HEIGHT);
6250     if (!flipView && y >= 0) {
6251         y = BOARD_HEIGHT - 1 - y;
6252     }
6253     if (flipView && x >= 0) {
6254         x = BOARD_WIDTH - 1 - x;
6255     }
6256
6257     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6258         if(clickType == Release) return; // ignore upclick of click-click destination
6259         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6260         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6261         if(gameInfo.holdingsWidth && 
6262                 (WhiteOnMove(currentMove) 
6263                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6264                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6265             // click in right holdings, for determining promotion piece
6266             ChessSquare p = boards[currentMove][y][x];
6267             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6268             if(p != EmptySquare) {
6269                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6270                 fromX = fromY = -1;
6271                 return;
6272             }
6273         }
6274         DrawPosition(FALSE, boards[currentMove]);
6275         return;
6276     }
6277
6278     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6279     if(clickType == Press
6280             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6281               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6282               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6283         return;
6284
6285     autoQueen = appData.alwaysPromoteToQueen;
6286
6287     if (fromX == -1) {
6288       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6289         if (clickType == Press) {
6290             /* First square */
6291             if (OKToStartUserMove(x, y)) {
6292                 fromX = x;
6293                 fromY = y;
6294                 second = 0;
6295                 MarkTargetSquares(0);
6296                 DragPieceBegin(xPix, yPix); dragging = 1;
6297                 if (appData.highlightDragging) {
6298                     SetHighlights(x, y, -1, -1);
6299                 }
6300             }
6301         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6302             DragPieceEnd(xPix, yPix); dragging = 0;
6303             DrawPosition(FALSE, NULL);
6304         }
6305         return;
6306       }
6307     }
6308
6309     /* fromX != -1 */
6310     if (clickType == Press && gameMode != EditPosition) {
6311         ChessSquare fromP;
6312         ChessSquare toP;
6313         int frc;
6314
6315         // ignore off-board to clicks
6316         if(y < 0 || x < 0) return;
6317
6318         /* Check if clicking again on the same color piece */
6319         fromP = boards[currentMove][fromY][fromX];
6320         toP = boards[currentMove][y][x];
6321         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6322         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6323              WhitePawn <= toP && toP <= WhiteKing &&
6324              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6325              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6326             (BlackPawn <= fromP && fromP <= BlackKing && 
6327              BlackPawn <= toP && toP <= BlackKing &&
6328              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6329              !(fromP == BlackKing && toP == BlackRook && frc))) {
6330             /* Clicked again on same color piece -- changed his mind */
6331             second = (x == fromX && y == fromY);
6332            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6333             if (appData.highlightDragging) {
6334                 SetHighlights(x, y, -1, -1);
6335             } else {
6336                 ClearHighlights();
6337             }
6338             if (OKToStartUserMove(x, y)) {
6339                 fromX = x;
6340                 fromY = y; dragging = 1;
6341                 MarkTargetSquares(0);
6342                 DragPieceBegin(xPix, yPix);
6343             }
6344             return;
6345            }
6346         }
6347         // ignore clicks on holdings
6348         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6349     }
6350
6351     if (clickType == Release && x == fromX && y == fromY) {
6352         DragPieceEnd(xPix, yPix); dragging = 0;
6353         if (appData.animateDragging) {
6354             /* Undo animation damage if any */
6355             DrawPosition(FALSE, NULL);
6356         }
6357         if (second) {
6358             /* Second up/down in same square; just abort move */
6359             second = 0;
6360             fromX = fromY = -1;
6361             ClearHighlights();
6362             gotPremove = 0;
6363             ClearPremoveHighlights();
6364         } else {
6365             /* First upclick in same square; start click-click mode */
6366             SetHighlights(x, y, -1, -1);
6367         }
6368         return;
6369     }
6370
6371     /* we now have a different from- and (possibly off-board) to-square */
6372     /* Completed move */
6373     toX = x;
6374     toY = y;
6375     saveAnimate = appData.animate;
6376     if (clickType == Press) {
6377         /* Finish clickclick move */
6378         if (appData.animate || appData.highlightLastMove) {
6379             SetHighlights(fromX, fromY, toX, toY);
6380         } else {
6381             ClearHighlights();
6382         }
6383     } else {
6384         /* Finish drag move */
6385         if (appData.highlightLastMove) {
6386             SetHighlights(fromX, fromY, toX, toY);
6387         } else {
6388             ClearHighlights();
6389         }
6390         DragPieceEnd(xPix, yPix); dragging = 0;
6391         /* Don't animate move and drag both */
6392         appData.animate = FALSE;
6393     }
6394
6395     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6396     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6397         ChessSquare piece = boards[currentMove][fromY][fromX];
6398         if(gameMode == EditPosition && piece != EmptySquare &&
6399            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6400             int n;
6401              
6402             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6403                 n = PieceToNumber(piece - (int)BlackPawn);
6404                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6405                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6406                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6407             } else
6408             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6409                 n = PieceToNumber(piece);
6410                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6411                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6412                 boards[currentMove][n][BOARD_WIDTH-2]++;
6413             }
6414             boards[currentMove][fromY][fromX] = EmptySquare;
6415         }
6416         ClearHighlights();
6417         fromX = fromY = -1;
6418         DrawPosition(TRUE, boards[currentMove]);
6419         return;
6420     }
6421
6422     // off-board moves should not be highlighted
6423     if(x < 0 || x < 0) ClearHighlights();
6424
6425     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6426         SetHighlights(fromX, fromY, toX, toY);
6427         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6428             // [HGM] super: promotion to captured piece selected from holdings
6429             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6430             promotionChoice = TRUE;
6431             // kludge follows to temporarily execute move on display, without promoting yet
6432             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6433             boards[currentMove][toY][toX] = p;
6434             DrawPosition(FALSE, boards[currentMove]);
6435             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6436             boards[currentMove][toY][toX] = q;
6437             DisplayMessage("Click in holdings to choose piece", "");
6438             return;
6439         }
6440         PromotionPopUp();
6441     } else {
6442         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6443         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6444         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6445         fromX = fromY = -1;
6446     }
6447     appData.animate = saveAnimate;
6448     if (appData.animate || appData.animateDragging) {
6449         /* Undo animation damage if needed */
6450         DrawPosition(FALSE, NULL);
6451     }
6452 }
6453
6454 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6455 {   // front-end-free part taken out of PieceMenuPopup
6456     int whichMenu; int xSqr, ySqr;
6457
6458     if(seekGraphUp) { // [HGM] seekgraph
6459         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6460         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6461         return -2;
6462     }
6463
6464     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6465          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6466         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6467         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6468         if(action == Press)   {
6469             originalFlip = flipView;
6470             flipView = !flipView; // temporarily flip board to see game from partners perspective
6471             DrawPosition(TRUE, partnerBoard);
6472             DisplayMessage(partnerStatus, "");
6473             partnerUp = TRUE;
6474         } else if(action == Release) {
6475             flipView = originalFlip;
6476             DrawPosition(TRUE, boards[currentMove]);
6477             partnerUp = FALSE;
6478         }
6479         return -2;
6480     }
6481
6482     xSqr = EventToSquare(x, BOARD_WIDTH);
6483     ySqr = EventToSquare(y, BOARD_HEIGHT);
6484     if (action == Release) UnLoadPV(); // [HGM] pv
6485     if (action != Press) return -2; // return code to be ignored
6486     switch (gameMode) {
6487       case IcsExamining:
6488         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6489       case EditPosition:
6490         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6491         if (xSqr < 0 || ySqr < 0) return -1;\r
6492         whichMenu = 0; // edit-position menu
6493         break;
6494       case IcsObserving:
6495         if(!appData.icsEngineAnalyze) return -1;
6496       case IcsPlayingWhite:
6497       case IcsPlayingBlack:
6498         if(!appData.zippyPlay) goto noZip;
6499       case AnalyzeMode:
6500       case AnalyzeFile:
6501       case MachinePlaysWhite:
6502       case MachinePlaysBlack:
6503       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6504         if (!appData.dropMenu) {
6505           LoadPV(x, y);
6506           return 2; // flag front-end to grab mouse events
6507         }
6508         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6509            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6510       case EditGame:
6511       noZip:
6512         if (xSqr < 0 || ySqr < 0) return -1;
6513         if (!appData.dropMenu || appData.testLegality &&
6514             gameInfo.variant != VariantBughouse &&
6515             gameInfo.variant != VariantCrazyhouse) return -1;
6516         whichMenu = 1; // drop menu
6517         break;
6518       default:
6519         return -1;
6520     }
6521
6522     if (((*fromX = xSqr) < 0) ||
6523         ((*fromY = ySqr) < 0)) {
6524         *fromX = *fromY = -1;
6525         return -1;
6526     }
6527     if (flipView)
6528       *fromX = BOARD_WIDTH - 1 - *fromX;
6529     else
6530       *fromY = BOARD_HEIGHT - 1 - *fromY;
6531
6532     return whichMenu;
6533 }
6534
6535 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6536 {
6537 //    char * hint = lastHint;
6538     FrontEndProgramStats stats;
6539
6540     stats.which = cps == &first ? 0 : 1;
6541     stats.depth = cpstats->depth;
6542     stats.nodes = cpstats->nodes;
6543     stats.score = cpstats->score;
6544     stats.time = cpstats->time;
6545     stats.pv = cpstats->movelist;
6546     stats.hint = lastHint;
6547     stats.an_move_index = 0;
6548     stats.an_move_count = 0;
6549
6550     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6551         stats.hint = cpstats->move_name;
6552         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6553         stats.an_move_count = cpstats->nr_moves;
6554     }
6555
6556     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6557
6558     SetProgramStats( &stats );
6559 }
6560
6561 void
6562 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6563 {       // count all piece types
6564         int p, f, r;
6565         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6566         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6567         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6568                 p = board[r][f];
6569                 pCnt[p]++;
6570                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6571                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6572                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6573                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6574                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6575                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6576         }
6577 }
6578
6579 int
6580 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6581 {
6582         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6583         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6584                    
6585         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6586         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6587         if(myPawns == 2 && nMine == 3) // KPP
6588             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6589         if(myPawns == 1 && nMine == 2) // KP
6590             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6591         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6592             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6593         if(myPawns) return FALSE;
6594         if(pCnt[WhiteRook+side])
6595             return pCnt[BlackRook-side] || 
6596                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6597                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6598                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6599         if(pCnt[WhiteCannon+side]) {
6600             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6601             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6602         }
6603         if(pCnt[WhiteKnight+side])
6604             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6605         return FALSE;
6606 }
6607
6608 int
6609 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6610 {
6611         VariantClass v = gameInfo.variant;
6612
6613         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6614         if(v == VariantShatranj) return TRUE; // always winnable through baring
6615         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6616         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6617
6618         if(v == VariantXiangqi) {
6619                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6620
6621                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6622                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6623                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6624                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6625                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6626                 if(stale) // we have at least one last-rank P plus perhaps C
6627                     return majors // KPKX
6628                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6629                 else // KCA*E*
6630                     return pCnt[WhiteFerz+side] // KCAK
6631                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6632                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6633                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6634
6635         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6636                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6637                 
6638                 if(nMine == 1) return FALSE; // bare King
6639                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
6640                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6641                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6642                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6643                 if(pCnt[WhiteKnight+side])
6644                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] + 
6645                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6646                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6647                 if(nBishops)
6648                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6649                 if(pCnt[WhiteAlfil+side])
6650                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6651                 if(pCnt[WhiteWazir+side])
6652                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6653         }
6654
6655         return TRUE;
6656 }
6657
6658 int
6659 Adjudicate(ChessProgramState *cps)
6660 {       // [HGM] some adjudications useful with buggy engines
6661         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6662         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6663         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6664         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6665         int k, count = 0; static int bare = 1;
6666         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6667         Boolean canAdjudicate = !appData.icsActive;
6668
6669         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6670         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6671             if( appData.testLegality )
6672             {   /* [HGM] Some more adjudications for obstinate engines */
6673                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6674                 static int moveCount = 6;
6675                 ChessMove result;
6676                 char *reason = NULL;
6677
6678                 /* Count what is on board. */
6679                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6680
6681                 /* Some material-based adjudications that have to be made before stalemate test */
6682                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6683                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6684                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6685                      if(canAdjudicate && appData.checkMates) {
6686                          if(engineOpponent)
6687                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6688                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6689                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6690                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6691                          return 1;
6692                      }
6693                 }
6694
6695                 /* Bare King in Shatranj (loses) or Losers (wins) */
6696                 if( nrW == 1 || nrB == 1) {
6697                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6698                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6699                      if(canAdjudicate && appData.checkMates) {
6700                          if(engineOpponent)
6701                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6702                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6703                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6704                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6705                          return 1;
6706                      }
6707                   } else
6708                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6709                   {    /* bare King */
6710                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6711                         if(canAdjudicate && appData.checkMates) {
6712                             /* but only adjudicate if adjudication enabled */
6713                             if(engineOpponent)
6714                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6715                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6716                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn, 
6717                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6718                             return 1;
6719                         }
6720                   }
6721                 } else bare = 1;
6722
6723
6724             // don't wait for engine to announce game end if we can judge ourselves
6725             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6726               case MT_CHECK:
6727                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6728                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6729                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6730                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6731                             checkCnt++;
6732                         if(checkCnt >= 2) {
6733                             reason = "Xboard adjudication: 3rd check";
6734                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6735                             break;
6736                         }
6737                     }
6738                 }
6739               case MT_NONE:
6740               default:
6741                 break;
6742               case MT_STALEMATE:
6743               case MT_STAINMATE:
6744                 reason = "Xboard adjudication: Stalemate";
6745                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6746                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6747                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6748                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6749                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6750                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6751                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6752                                                                         EP_CHECKMATE : EP_WINS);
6753                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6754                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6755                 }
6756                 break;
6757               case MT_CHECKMATE:
6758                 reason = "Xboard adjudication: Checkmate";
6759                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6760                 break;
6761             }
6762
6763                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6764                     case EP_STALEMATE:
6765                         result = GameIsDrawn; break;
6766                     case EP_CHECKMATE:
6767                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6768                     case EP_WINS:
6769                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6770                     default:
6771                         result = (ChessMove) 0;
6772                 }
6773                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6774                     if(engineOpponent)
6775                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6776                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6777                     GameEnds( result, reason, GE_XBOARD );
6778                     return 1;
6779                 }
6780
6781                 /* Next absolutely insufficient mating material. */
6782                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6783                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6784                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6785
6786                      /* always flag draws, for judging claims */
6787                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6788
6789                      if(canAdjudicate && appData.materialDraws) {
6790                          /* but only adjudicate them if adjudication enabled */
6791                          if(engineOpponent) {
6792                            SendToProgram("force\n", engineOpponent); // suppress reply
6793                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6794                          }
6795                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6796                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6797                          return 1;
6798                      }
6799                 }
6800
6801                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6802                 if(gameInfo.variant == VariantXiangqi ?
6803                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6804                  : nrW + nrB == 4 && 
6805                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6806                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6807                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6808                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6809                    ) ) {
6810                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6811                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6812                           if(engineOpponent) {
6813                             SendToProgram("force\n", engineOpponent); // suppress reply
6814                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6815                           }
6816                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6817                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6818                           return 1;
6819                      }
6820                 } else moveCount = 6;
6821             }
6822         }
6823           
6824         if (appData.debugMode) { int i;
6825             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6826                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6827                     appData.drawRepeats);
6828             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6829               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6830             
6831         }
6832
6833         // Repetition draws and 50-move rule can be applied independently of legality testing
6834
6835                 /* Check for rep-draws */
6836                 count = 0;
6837                 for(k = forwardMostMove-2;
6838                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6839                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6840                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6841                     k-=2)
6842                 {   int rights=0;
6843                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6844                         /* compare castling rights */
6845                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6846                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6847                                 rights++; /* King lost rights, while rook still had them */
6848                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6849                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6850                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6851                                    rights++; /* but at least one rook lost them */
6852                         }
6853                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6854                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6855                                 rights++; 
6856                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6857                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6858                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6859                                    rights++;
6860                         }
6861                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6862                             && appData.drawRepeats > 1) {
6863                              /* adjudicate after user-specified nr of repeats */
6864                              int result = GameIsDrawn;
6865                              char *details = "XBoard adjudication: repetition draw";
6866                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6867                                 // [HGM] xiangqi: check for forbidden perpetuals
6868                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6869                                 for(m=forwardMostMove; m>k; m-=2) {
6870                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6871                                         ourPerpetual = 0; // the current mover did not always check
6872                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6873                                         hisPerpetual = 0; // the opponent did not always check
6874                                 }
6875                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6876                                                                         ourPerpetual, hisPerpetual);
6877                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6878                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6879                                     details = "Xboard adjudication: perpetual checking";
6880                                 } else
6881                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6882                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6883                                 } else
6884                                 // Now check for perpetual chases
6885                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6886                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6887                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6888                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6889                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6890                                         details = "Xboard adjudication: perpetual chasing";
6891                                     } else
6892                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6893                                         break; // Abort repetition-checking loop.
6894                                 }
6895                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6896                              }
6897                              if(engineOpponent) {
6898                                SendToProgram("force\n", engineOpponent); // suppress reply
6899                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6900                              }
6901                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6902                              GameEnds( result, details, GE_XBOARD );
6903                              return 1;
6904                         }
6905                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6906                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6907                     }
6908                 }
6909
6910                 /* Now we test for 50-move draws. Determine ply count */
6911                 count = forwardMostMove;
6912                 /* look for last irreversble move */
6913                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6914                     count--;
6915                 /* if we hit starting position, add initial plies */
6916                 if( count == backwardMostMove )
6917                     count -= initialRulePlies;
6918                 count = forwardMostMove - count; 
6919                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6920                         // adjust reversible move counter for checks in Xiangqi
6921                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6922                         if(i < backwardMostMove) i = backwardMostMove;
6923                         while(i <= forwardMostMove) {
6924                                 lastCheck = inCheck; // check evasion does not count
6925                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6926                                 if(inCheck || lastCheck) count--; // check does not count
6927                                 i++;
6928                         }
6929                 }
6930                 if( count >= 100)
6931                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6932                          /* this is used to judge if draw claims are legal */
6933                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6934                          if(engineOpponent) {
6935                            SendToProgram("force\n", engineOpponent); // suppress reply
6936                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6937                          }
6938                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6939                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6940                          return 1;
6941                 }
6942
6943                 /* if draw offer is pending, treat it as a draw claim
6944                  * when draw condition present, to allow engines a way to
6945                  * claim draws before making their move to avoid a race
6946                  * condition occurring after their move
6947                  */
6948                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6949                          char *p = NULL;
6950                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6951                              p = "Draw claim: 50-move rule";
6952                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6953                              p = "Draw claim: 3-fold repetition";
6954                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6955                              p = "Draw claim: insufficient mating material";
6956                          if( p != NULL && canAdjudicate) {
6957                              if(engineOpponent) {
6958                                SendToProgram("force\n", engineOpponent); // suppress reply
6959                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6960                              }
6961                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6962                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6963                              return 1;
6964                          }
6965                 }
6966
6967                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6968                     if(engineOpponent) {
6969                       SendToProgram("force\n", engineOpponent); // suppress reply
6970                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6971                     }
6972                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6973                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6974                     return 1;
6975                 }
6976         return 0;
6977 }
6978
6979 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6980 {   // [HGM] book: this routine intercepts moves to simulate book replies
6981     char *bookHit = NULL;
6982
6983     //first determine if the incoming move brings opponent into his book
6984     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6985         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6986     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6987     if(bookHit != NULL && !cps->bookSuspend) {
6988         // make sure opponent is not going to reply after receiving move to book position
6989         SendToProgram("force\n", cps);
6990         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6991     }
6992     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6993     // now arrange restart after book miss
6994     if(bookHit) {
6995         // after a book hit we never send 'go', and the code after the call to this routine
6996         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6997         char buf[MSG_SIZ];
6998         sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
6999         SendToProgram(buf, cps);
7000         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7001     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7002         SendToProgram("go\n", cps);
7003         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7004     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7005         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7006             SendToProgram("go\n", cps); 
7007         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7008     }
7009     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7010 }
7011
7012 char *savedMessage;
7013 ChessProgramState *savedState;
7014 void DeferredBookMove(void)
7015 {
7016         if(savedState->lastPing != savedState->lastPong)
7017                     ScheduleDelayedEvent(DeferredBookMove, 10);
7018         else
7019         HandleMachineMove(savedMessage, savedState);
7020 }
7021
7022 void
7023 HandleMachineMove(message, cps)
7024      char *message;
7025      ChessProgramState *cps;
7026 {
7027     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7028     char realname[MSG_SIZ];
7029     int fromX, fromY, toX, toY;
7030     ChessMove moveType;
7031     char promoChar;
7032     char *p;
7033     int machineWhite;
7034     char *bookHit;
7035
7036     cps->userError = 0;
7037
7038 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7039     /*
7040      * Kludge to ignore BEL characters
7041      */
7042     while (*message == '\007') message++;
7043
7044     /*
7045      * [HGM] engine debug message: ignore lines starting with '#' character
7046      */
7047     if(cps->debug && *message == '#') return;
7048
7049     /*
7050      * Look for book output
7051      */
7052     if (cps == &first && bookRequested) {
7053         if (message[0] == '\t' || message[0] == ' ') {
7054             /* Part of the book output is here; append it */
7055             strcat(bookOutput, message);
7056             strcat(bookOutput, "  \n");
7057             return;
7058         } else if (bookOutput[0] != NULLCHAR) {
7059             /* All of book output has arrived; display it */
7060             char *p = bookOutput;
7061             while (*p != NULLCHAR) {
7062                 if (*p == '\t') *p = ' ';
7063                 p++;
7064             }
7065             DisplayInformation(bookOutput);
7066             bookRequested = FALSE;
7067             /* Fall through to parse the current output */
7068         }
7069     }
7070
7071     /*
7072      * Look for machine move.
7073      */
7074     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7075         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7076     {
7077         /* This method is only useful on engines that support ping */
7078         if (cps->lastPing != cps->lastPong) {
7079           if (gameMode == BeginningOfGame) {
7080             /* Extra move from before last new; ignore */
7081             if (appData.debugMode) {
7082                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7083             }
7084           } else {
7085             if (appData.debugMode) {
7086                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7087                         cps->which, gameMode);
7088             }
7089
7090             SendToProgram("undo\n", cps);
7091           }
7092           return;
7093         }
7094
7095         switch (gameMode) {
7096           case BeginningOfGame:
7097             /* Extra move from before last reset; ignore */
7098             if (appData.debugMode) {
7099                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7100             }
7101             return;
7102
7103           case EndOfGame:
7104           case IcsIdle:
7105           default:
7106             /* Extra move after we tried to stop.  The mode test is
7107                not a reliable way of detecting this problem, but it's
7108                the best we can do on engines that don't support ping.
7109             */
7110             if (appData.debugMode) {
7111                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7112                         cps->which, gameMode);
7113             }
7114             SendToProgram("undo\n", cps);
7115             return;
7116
7117           case MachinePlaysWhite:
7118           case IcsPlayingWhite:
7119             machineWhite = TRUE;
7120             break;
7121
7122           case MachinePlaysBlack:
7123           case IcsPlayingBlack:
7124             machineWhite = FALSE;
7125             break;
7126
7127           case TwoMachinesPlay:
7128             machineWhite = (cps->twoMachinesColor[0] == 'w');
7129             break;
7130         }
7131         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7132             if (appData.debugMode) {
7133                 fprintf(debugFP,
7134                         "Ignoring move out of turn by %s, gameMode %d"
7135                         ", forwardMost %d\n",
7136                         cps->which, gameMode, forwardMostMove);
7137             }
7138             return;
7139         }
7140
7141     if (appData.debugMode) { int f = forwardMostMove;
7142         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7143                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7144                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7145     }
7146         if(cps->alphaRank) AlphaRank(machineMove, 4);
7147         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7148                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7149             /* Machine move could not be parsed; ignore it. */
7150             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7151                     machineMove, cps->which);
7152             DisplayError(buf1, 0);
7153             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7154                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7155             if (gameMode == TwoMachinesPlay) {
7156               GameEnds(machineWhite ? BlackWins : WhiteWins,
7157                        buf1, GE_XBOARD);
7158             }
7159             return;
7160         }
7161
7162         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7163         /* So we have to redo legality test with true e.p. status here,  */
7164         /* to make sure an illegal e.p. capture does not slip through,   */
7165         /* to cause a forfeit on a justified illegal-move complaint      */
7166         /* of the opponent.                                              */
7167         if( gameMode==TwoMachinesPlay && appData.testLegality
7168             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7169                                                               ) {
7170            ChessMove moveType;
7171            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7172                              fromY, fromX, toY, toX, promoChar);
7173             if (appData.debugMode) {
7174                 int i;
7175                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7176                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7177                 fprintf(debugFP, "castling rights\n");
7178             }
7179             if(moveType == IllegalMove) {
7180                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7181                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7182                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7183                            buf1, GE_XBOARD);
7184                 return;
7185            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7186            /* [HGM] Kludge to handle engines that send FRC-style castling
7187               when they shouldn't (like TSCP-Gothic) */
7188            switch(moveType) {
7189              case WhiteASideCastleFR:
7190              case BlackASideCastleFR:
7191                toX+=2;
7192                currentMoveString[2]++;
7193                break;
7194              case WhiteHSideCastleFR:
7195              case BlackHSideCastleFR:
7196                toX--;
7197                currentMoveString[2]--;
7198                break;
7199              default: ; // nothing to do, but suppresses warning of pedantic compilers
7200            }
7201         }
7202         hintRequested = FALSE;
7203         lastHint[0] = NULLCHAR;
7204         bookRequested = FALSE;
7205         /* Program may be pondering now */
7206         cps->maybeThinking = TRUE;
7207         if (cps->sendTime == 2) cps->sendTime = 1;
7208         if (cps->offeredDraw) cps->offeredDraw--;
7209
7210         /* currentMoveString is set as a side-effect of ParseOneMove */
7211         strcpy(machineMove, currentMoveString);
7212         strcat(machineMove, "\n");
7213         strcpy(moveList[forwardMostMove], machineMove);
7214
7215         /* [AS] Save move info*/
7216         pvInfoList[ forwardMostMove ].score = programStats.score;
7217         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7218         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7219
7220         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7221
7222         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7223         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7224             int count = 0;
7225
7226             while( count < adjudicateLossPlies ) {
7227                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7228
7229                 if( count & 1 ) {
7230                     score = -score; /* Flip score for winning side */
7231                 }
7232
7233                 if( score > adjudicateLossThreshold ) {
7234                     break;
7235                 }
7236
7237                 count++;
7238             }
7239
7240             if( count >= adjudicateLossPlies ) {
7241                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7242
7243                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7244                     "Xboard adjudication", 
7245                     GE_XBOARD );
7246
7247                 return;
7248             }
7249         }
7250
7251         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7252
7253 #if ZIPPY
7254         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7255             first.initDone) {
7256           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7257                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7258                 SendToICS("draw ");
7259                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7260           }
7261           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7262           ics_user_moved = 1;
7263           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7264                 char buf[3*MSG_SIZ];
7265
7266                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7267                         programStats.score / 100.,
7268                         programStats.depth,
7269                         programStats.time / 100.,
7270                         (unsigned int)programStats.nodes,
7271                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7272                         programStats.movelist);
7273                 SendToICS(buf);
7274 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7275           }
7276         }
7277 #endif
7278
7279         /* [AS] Clear stats for next move */
7280         ClearProgramStats();
7281         thinkOutput[0] = NULLCHAR;
7282         hiddenThinkOutputState = 0;
7283
7284         bookHit = NULL;
7285         if (gameMode == TwoMachinesPlay) {
7286             /* [HGM] relaying draw offers moved to after reception of move */
7287             /* and interpreting offer as claim if it brings draw condition */
7288             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7289                 SendToProgram("draw\n", cps->other);
7290             }
7291             if (cps->other->sendTime) {
7292                 SendTimeRemaining(cps->other,
7293                                   cps->other->twoMachinesColor[0] == 'w');
7294             }
7295             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7296             if (firstMove && !bookHit) {
7297                 firstMove = FALSE;
7298                 if (cps->other->useColors) {
7299                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7300                 }
7301                 SendToProgram("go\n", cps->other);
7302             }
7303             cps->other->maybeThinking = TRUE;
7304         }
7305
7306         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7307         
7308         if (!pausing && appData.ringBellAfterMoves) {
7309             RingBell();
7310         }
7311
7312         /* 
7313          * Reenable menu items that were disabled while
7314          * machine was thinking
7315          */
7316         if (gameMode != TwoMachinesPlay)
7317             SetUserThinkingEnables();
7318
7319         // [HGM] book: after book hit opponent has received move and is now in force mode
7320         // force the book reply into it, and then fake that it outputted this move by jumping
7321         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7322         if(bookHit) {
7323                 static char bookMove[MSG_SIZ]; // a bit generous?
7324
7325                 strcpy(bookMove, "move ");
7326                 strcat(bookMove, bookHit);
7327                 message = bookMove;
7328                 cps = cps->other;
7329                 programStats.nodes = programStats.depth = programStats.time = 
7330                 programStats.score = programStats.got_only_move = 0;
7331                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7332
7333                 if(cps->lastPing != cps->lastPong) {
7334                     savedMessage = message; // args for deferred call
7335                     savedState = cps;
7336                     ScheduleDelayedEvent(DeferredBookMove, 10);
7337                     return;
7338                 }
7339                 goto FakeBookMove;
7340         }
7341
7342         return;
7343     }
7344
7345     /* Set special modes for chess engines.  Later something general
7346      *  could be added here; for now there is just one kludge feature,
7347      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7348      *  when "xboard" is given as an interactive command.
7349      */
7350     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7351         cps->useSigint = FALSE;
7352         cps->useSigterm = FALSE;
7353     }
7354     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7355       ParseFeatures(message+8, cps);
7356       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7357     }
7358
7359     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7360      * want this, I was asked to put it in, and obliged.
7361      */
7362     if (!strncmp(message, "setboard ", 9)) {
7363         Board initial_position;
7364
7365         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7366
7367         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7368             DisplayError(_("Bad FEN received from engine"), 0);
7369             return ;
7370         } else {
7371            Reset(TRUE, FALSE);
7372            CopyBoard(boards[0], initial_position);
7373            initialRulePlies = FENrulePlies;
7374            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7375            else gameMode = MachinePlaysBlack;                 
7376            DrawPosition(FALSE, boards[currentMove]);
7377         }
7378         return;
7379     }
7380
7381     /*
7382      * Look for communication commands
7383      */
7384     if (!strncmp(message, "telluser ", 9)) {
7385         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7386         DisplayNote(message + 9);
7387         return;
7388     }
7389     if (!strncmp(message, "tellusererror ", 14)) {
7390         cps->userError = 1;
7391         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7392         DisplayError(message + 14, 0);
7393         return;
7394     }
7395     if (!strncmp(message, "tellopponent ", 13)) {
7396       if (appData.icsActive) {
7397         if (loggedOn) {
7398           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7399           SendToICS(buf1);
7400         }
7401       } else {
7402         DisplayNote(message + 13);
7403       }
7404       return;
7405     }
7406     if (!strncmp(message, "tellothers ", 11)) {
7407       if (appData.icsActive) {
7408         if (loggedOn) {
7409           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7410           SendToICS(buf1);
7411         }
7412       }
7413       return;
7414     }
7415     if (!strncmp(message, "tellall ", 8)) {
7416       if (appData.icsActive) {
7417         if (loggedOn) {
7418           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7419           SendToICS(buf1);
7420         }
7421       } else {
7422         DisplayNote(message + 8);
7423       }
7424       return;
7425     }
7426     if (strncmp(message, "warning", 7) == 0) {
7427         /* Undocumented feature, use tellusererror in new code */
7428         DisplayError(message, 0);
7429         return;
7430     }
7431     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7432         strcpy(realname, cps->tidy);
7433         strcat(realname, " query");
7434         AskQuestion(realname, buf2, buf1, cps->pr);
7435         return;
7436     }
7437     /* Commands from the engine directly to ICS.  We don't allow these to be 
7438      *  sent until we are logged on. Crafty kibitzes have been known to 
7439      *  interfere with the login process.
7440      */
7441     if (loggedOn) {
7442         if (!strncmp(message, "tellics ", 8)) {
7443             SendToICS(message + 8);
7444             SendToICS("\n");
7445             return;
7446         }
7447         if (!strncmp(message, "tellicsnoalias ", 15)) {
7448             SendToICS(ics_prefix);
7449             SendToICS(message + 15);
7450             SendToICS("\n");
7451             return;
7452         }
7453         /* The following are for backward compatibility only */
7454         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7455             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7456             SendToICS(ics_prefix);
7457             SendToICS(message);
7458             SendToICS("\n");
7459             return;
7460         }
7461     }
7462     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7463         return;
7464     }
7465     /*
7466      * If the move is illegal, cancel it and redraw the board.
7467      * Also deal with other error cases.  Matching is rather loose
7468      * here to accommodate engines written before the spec.
7469      */
7470     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7471         strncmp(message, "Error", 5) == 0) {
7472         if (StrStr(message, "name") || 
7473             StrStr(message, "rating") || StrStr(message, "?") ||
7474             StrStr(message, "result") || StrStr(message, "board") ||
7475             StrStr(message, "bk") || StrStr(message, "computer") ||
7476             StrStr(message, "variant") || StrStr(message, "hint") ||
7477             StrStr(message, "random") || StrStr(message, "depth") ||
7478             StrStr(message, "accepted")) {
7479             return;
7480         }
7481         if (StrStr(message, "protover")) {
7482           /* Program is responding to input, so it's apparently done
7483              initializing, and this error message indicates it is
7484              protocol version 1.  So we don't need to wait any longer
7485              for it to initialize and send feature commands. */
7486           FeatureDone(cps, 1);
7487           cps->protocolVersion = 1;
7488           return;
7489         }
7490         cps->maybeThinking = FALSE;
7491
7492         if (StrStr(message, "draw")) {
7493             /* Program doesn't have "draw" command */
7494             cps->sendDrawOffers = 0;
7495             return;
7496         }
7497         if (cps->sendTime != 1 &&
7498             (StrStr(message, "time") || StrStr(message, "otim"))) {
7499           /* Program apparently doesn't have "time" or "otim" command */
7500           cps->sendTime = 0;
7501           return;
7502         }
7503         if (StrStr(message, "analyze")) {
7504             cps->analysisSupport = FALSE;
7505             cps->analyzing = FALSE;
7506             Reset(FALSE, TRUE);
7507             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7508             DisplayError(buf2, 0);
7509             return;
7510         }
7511         if (StrStr(message, "(no matching move)st")) {
7512           /* Special kludge for GNU Chess 4 only */
7513           cps->stKludge = TRUE;
7514           SendTimeControl(cps, movesPerSession, timeControl,
7515                           timeIncrement, appData.searchDepth,
7516                           searchTime);
7517           return;
7518         }
7519         if (StrStr(message, "(no matching move)sd")) {
7520           /* Special kludge for GNU Chess 4 only */
7521           cps->sdKludge = TRUE;
7522           SendTimeControl(cps, movesPerSession, timeControl,
7523                           timeIncrement, appData.searchDepth,
7524                           searchTime);
7525           return;
7526         }
7527         if (!StrStr(message, "llegal")) {
7528             return;
7529         }
7530         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7531             gameMode == IcsIdle) return;
7532         if (forwardMostMove <= backwardMostMove) return;
7533         if (pausing) PauseEvent();
7534       if(appData.forceIllegal) {
7535             // [HGM] illegal: machine refused move; force position after move into it
7536           SendToProgram("force\n", cps);
7537           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7538                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7539                 // when black is to move, while there might be nothing on a2 or black
7540                 // might already have the move. So send the board as if white has the move.
7541                 // But first we must change the stm of the engine, as it refused the last move
7542                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7543                 if(WhiteOnMove(forwardMostMove)) {
7544                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7545                     SendBoard(cps, forwardMostMove); // kludgeless board
7546                 } else {
7547                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7548                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7549                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7550                 }
7551           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7552             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7553                  gameMode == TwoMachinesPlay)
7554               SendToProgram("go\n", cps);
7555             return;
7556       } else
7557         if (gameMode == PlayFromGameFile) {
7558             /* Stop reading this game file */
7559             gameMode = EditGame;
7560             ModeHighlight();
7561         }
7562         currentMove = forwardMostMove-1;
7563         DisplayMove(currentMove-1); /* before DisplayMoveError */
7564         SwitchClocks(forwardMostMove-1); // [HGM] race
7565         DisplayBothClocks();
7566         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7567                 parseList[currentMove], cps->which);
7568         DisplayMoveError(buf1);
7569         DrawPosition(FALSE, boards[currentMove]);
7570
7571         /* [HGM] illegal-move claim should forfeit game when Xboard */
7572         /* only passes fully legal moves                            */
7573         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7574             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7575                                 "False illegal-move claim", GE_XBOARD );
7576         }
7577         return;
7578     }
7579     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7580         /* Program has a broken "time" command that
7581            outputs a string not ending in newline.
7582            Don't use it. */
7583         cps->sendTime = 0;
7584     }
7585     
7586     /*
7587      * If chess program startup fails, exit with an error message.
7588      * Attempts to recover here are futile.
7589      */
7590     if ((StrStr(message, "unknown host") != NULL)
7591         || (StrStr(message, "No remote directory") != NULL)
7592         || (StrStr(message, "not found") != NULL)
7593         || (StrStr(message, "No such file") != NULL)
7594         || (StrStr(message, "can't alloc") != NULL)
7595         || (StrStr(message, "Permission denied") != NULL)) {
7596
7597         cps->maybeThinking = FALSE;
7598         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7599                 cps->which, cps->program, cps->host, message);
7600         RemoveInputSource(cps->isr);
7601         DisplayFatalError(buf1, 0, 1);
7602         return;
7603     }
7604     
7605     /* 
7606      * Look for hint output
7607      */
7608     if (sscanf(message, "Hint: %s", buf1) == 1) {
7609         if (cps == &first && hintRequested) {
7610             hintRequested = FALSE;
7611             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7612                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7613                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7614                                     PosFlags(forwardMostMove),
7615                                     fromY, fromX, toY, toX, promoChar, buf1);
7616                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7617                 DisplayInformation(buf2);
7618             } else {
7619                 /* Hint move could not be parsed!? */
7620               snprintf(buf2, sizeof(buf2),
7621                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7622                         buf1, cps->which);
7623                 DisplayError(buf2, 0);
7624             }
7625         } else {
7626             strcpy(lastHint, buf1);
7627         }
7628         return;
7629     }
7630
7631     /*
7632      * Ignore other messages if game is not in progress
7633      */
7634     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7635         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7636
7637     /*
7638      * look for win, lose, draw, or draw offer
7639      */
7640     if (strncmp(message, "1-0", 3) == 0) {
7641         char *p, *q, *r = "";
7642         p = strchr(message, '{');
7643         if (p) {
7644             q = strchr(p, '}');
7645             if (q) {
7646                 *q = NULLCHAR;
7647                 r = p + 1;
7648             }
7649         }
7650         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7651         return;
7652     } else if (strncmp(message, "0-1", 3) == 0) {
7653         char *p, *q, *r = "";
7654         p = strchr(message, '{');
7655         if (p) {
7656             q = strchr(p, '}');
7657             if (q) {
7658                 *q = NULLCHAR;
7659                 r = p + 1;
7660             }
7661         }
7662         /* Kludge for Arasan 4.1 bug */
7663         if (strcmp(r, "Black resigns") == 0) {
7664             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7665             return;
7666         }
7667         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7668         return;
7669     } else if (strncmp(message, "1/2", 3) == 0) {
7670         char *p, *q, *r = "";
7671         p = strchr(message, '{');
7672         if (p) {
7673             q = strchr(p, '}');
7674             if (q) {
7675                 *q = NULLCHAR;
7676                 r = p + 1;
7677             }
7678         }
7679             
7680         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7681         return;
7682
7683     } else if (strncmp(message, "White resign", 12) == 0) {
7684         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7685         return;
7686     } else if (strncmp(message, "Black resign", 12) == 0) {
7687         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7688         return;
7689     } else if (strncmp(message, "White matches", 13) == 0 ||
7690                strncmp(message, "Black matches", 13) == 0   ) {
7691         /* [HGM] ignore GNUShogi noises */
7692         return;
7693     } else if (strncmp(message, "White", 5) == 0 &&
7694                message[5] != '(' &&
7695                StrStr(message, "Black") == NULL) {
7696         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7697         return;
7698     } else if (strncmp(message, "Black", 5) == 0 &&
7699                message[5] != '(') {
7700         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7701         return;
7702     } else if (strcmp(message, "resign") == 0 ||
7703                strcmp(message, "computer resigns") == 0) {
7704         switch (gameMode) {
7705           case MachinePlaysBlack:
7706           case IcsPlayingBlack:
7707             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7708             break;
7709           case MachinePlaysWhite:
7710           case IcsPlayingWhite:
7711             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7712             break;
7713           case TwoMachinesPlay:
7714             if (cps->twoMachinesColor[0] == 'w')
7715               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7716             else
7717               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7718             break;
7719           default:
7720             /* can't happen */
7721             break;
7722         }
7723         return;
7724     } else if (strncmp(message, "opponent mates", 14) == 0) {
7725         switch (gameMode) {
7726           case MachinePlaysBlack:
7727           case IcsPlayingBlack:
7728             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7729             break;
7730           case MachinePlaysWhite:
7731           case IcsPlayingWhite:
7732             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7733             break;
7734           case TwoMachinesPlay:
7735             if (cps->twoMachinesColor[0] == 'w')
7736               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7737             else
7738               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7739             break;
7740           default:
7741             /* can't happen */
7742             break;
7743         }
7744         return;
7745     } else if (strncmp(message, "computer mates", 14) == 0) {
7746         switch (gameMode) {
7747           case MachinePlaysBlack:
7748           case IcsPlayingBlack:
7749             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7750             break;
7751           case MachinePlaysWhite:
7752           case IcsPlayingWhite:
7753             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7754             break;
7755           case TwoMachinesPlay:
7756             if (cps->twoMachinesColor[0] == 'w')
7757               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7758             else
7759               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7760             break;
7761           default:
7762             /* can't happen */
7763             break;
7764         }
7765         return;
7766     } else if (strncmp(message, "checkmate", 9) == 0) {
7767         if (WhiteOnMove(forwardMostMove)) {
7768             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7769         } else {
7770             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7771         }
7772         return;
7773     } else if (strstr(message, "Draw") != NULL ||
7774                strstr(message, "game is a draw") != NULL) {
7775         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7776         return;
7777     } else if (strstr(message, "offer") != NULL &&
7778                strstr(message, "draw") != NULL) {
7779 #if ZIPPY
7780         if (appData.zippyPlay && first.initDone) {
7781             /* Relay offer to ICS */
7782             SendToICS(ics_prefix);
7783             SendToICS("draw\n");
7784         }
7785 #endif
7786         cps->offeredDraw = 2; /* valid until this engine moves twice */
7787         if (gameMode == TwoMachinesPlay) {
7788             if (cps->other->offeredDraw) {
7789                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7790             /* [HGM] in two-machine mode we delay relaying draw offer      */
7791             /* until after we also have move, to see if it is really claim */
7792             }
7793         } else if (gameMode == MachinePlaysWhite ||
7794                    gameMode == MachinePlaysBlack) {
7795           if (userOfferedDraw) {
7796             DisplayInformation(_("Machine accepts your draw offer"));
7797             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7798           } else {
7799             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7800           }
7801         }
7802     }
7803
7804     
7805     /*
7806      * Look for thinking output
7807      */
7808     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7809           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7810                                 ) {
7811         int plylev, mvleft, mvtot, curscore, time;
7812         char mvname[MOVE_LEN];
7813         u64 nodes; // [DM]
7814         char plyext;
7815         int ignore = FALSE;
7816         int prefixHint = FALSE;
7817         mvname[0] = NULLCHAR;
7818
7819         switch (gameMode) {
7820           case MachinePlaysBlack:
7821           case IcsPlayingBlack:
7822             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7823             break;
7824           case MachinePlaysWhite:
7825           case IcsPlayingWhite:
7826             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7827             break;
7828           case AnalyzeMode:
7829           case AnalyzeFile:
7830             break;
7831           case IcsObserving: /* [DM] icsEngineAnalyze */
7832             if (!appData.icsEngineAnalyze) ignore = TRUE;
7833             break;
7834           case TwoMachinesPlay:
7835             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7836                 ignore = TRUE;
7837             }
7838             break;
7839           default:
7840             ignore = TRUE;
7841             break;
7842         }
7843
7844         if (!ignore) {
7845             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7846             buf1[0] = NULLCHAR;
7847             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7848                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7849
7850                 if (plyext != ' ' && plyext != '\t') {
7851                     time *= 100;
7852                 }
7853
7854                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7855                 if( cps->scoreIsAbsolute && 
7856                     ( gameMode == MachinePlaysBlack ||
7857                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7858                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7859                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7860                      !WhiteOnMove(currentMove)
7861                     ) )
7862                 {
7863                     curscore = -curscore;
7864                 }
7865
7866
7867                 tempStats.depth = plylev;
7868                 tempStats.nodes = nodes;
7869                 tempStats.time = time;
7870                 tempStats.score = curscore;
7871                 tempStats.got_only_move = 0;
7872
7873                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7874                         int ticklen;
7875
7876                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7877                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7878                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7879                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7880                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7881                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7882                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7883                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7884                 }
7885
7886                 /* Buffer overflow protection */
7887                 if (buf1[0] != NULLCHAR) {
7888                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7889                         && appData.debugMode) {
7890                         fprintf(debugFP,
7891                                 "PV is too long; using the first %u bytes.\n",
7892                                 (unsigned) sizeof(tempStats.movelist) - 1);
7893                     }
7894
7895                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7896                 } else {
7897                     sprintf(tempStats.movelist, " no PV\n");
7898                 }
7899
7900                 if (tempStats.seen_stat) {
7901                     tempStats.ok_to_send = 1;
7902                 }
7903
7904                 if (strchr(tempStats.movelist, '(') != NULL) {
7905                     tempStats.line_is_book = 1;
7906                     tempStats.nr_moves = 0;
7907                     tempStats.moves_left = 0;
7908                 } else {
7909                     tempStats.line_is_book = 0;
7910                 }
7911
7912                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7913                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7914
7915                 SendProgramStatsToFrontend( cps, &tempStats );
7916
7917                 /* 
7918                     [AS] Protect the thinkOutput buffer from overflow... this
7919                     is only useful if buf1 hasn't overflowed first!
7920                 */
7921                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7922                         plylev, 
7923                         (gameMode == TwoMachinesPlay ?
7924                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7925                         ((double) curscore) / 100.0,
7926                         prefixHint ? lastHint : "",
7927                         prefixHint ? " " : "" );
7928
7929                 if( buf1[0] != NULLCHAR ) {
7930                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7931
7932                     if( strlen(buf1) > max_len ) {
7933                         if( appData.debugMode) {
7934                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7935                         }
7936                         buf1[max_len+1] = '\0';
7937                     }
7938
7939                     strcat( thinkOutput, buf1 );
7940                 }
7941
7942                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7943                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7944                     DisplayMove(currentMove - 1);
7945                 }
7946                 return;
7947
7948             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7949                 /* crafty (9.25+) says "(only move) <move>"
7950                  * if there is only 1 legal move
7951                  */
7952                 sscanf(p, "(only move) %s", buf1);
7953                 sprintf(thinkOutput, "%s (only move)", buf1);
7954                 sprintf(programStats.movelist, "%s (only move)", buf1);
7955                 programStats.depth = 1;
7956                 programStats.nr_moves = 1;
7957                 programStats.moves_left = 1;
7958                 programStats.nodes = 1;
7959                 programStats.time = 1;
7960                 programStats.got_only_move = 1;
7961
7962                 /* Not really, but we also use this member to
7963                    mean "line isn't going to change" (Crafty
7964                    isn't searching, so stats won't change) */
7965                 programStats.line_is_book = 1;
7966
7967                 SendProgramStatsToFrontend( cps, &programStats );
7968                 
7969                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7970                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7971                     DisplayMove(currentMove - 1);
7972                 }
7973                 return;
7974             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7975                               &time, &nodes, &plylev, &mvleft,
7976                               &mvtot, mvname) >= 5) {
7977                 /* The stat01: line is from Crafty (9.29+) in response
7978                    to the "." command */
7979                 programStats.seen_stat = 1;
7980                 cps->maybeThinking = TRUE;
7981
7982                 if (programStats.got_only_move || !appData.periodicUpdates)
7983                   return;
7984
7985                 programStats.depth = plylev;
7986                 programStats.time = time;
7987                 programStats.nodes = nodes;
7988                 programStats.moves_left = mvleft;
7989                 programStats.nr_moves = mvtot;
7990                 strcpy(programStats.move_name, mvname);
7991                 programStats.ok_to_send = 1;
7992                 programStats.movelist[0] = '\0';
7993
7994                 SendProgramStatsToFrontend( cps, &programStats );
7995
7996                 return;
7997
7998             } else if (strncmp(message,"++",2) == 0) {
7999                 /* Crafty 9.29+ outputs this */
8000                 programStats.got_fail = 2;
8001                 return;
8002
8003             } else if (strncmp(message,"--",2) == 0) {
8004                 /* Crafty 9.29+ outputs this */
8005                 programStats.got_fail = 1;
8006                 return;
8007
8008             } else if (thinkOutput[0] != NULLCHAR &&
8009                        strncmp(message, "    ", 4) == 0) {
8010                 unsigned message_len;
8011
8012                 p = message;
8013                 while (*p && *p == ' ') p++;
8014
8015                 message_len = strlen( p );
8016
8017                 /* [AS] Avoid buffer overflow */
8018                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8019                     strcat(thinkOutput, " ");
8020                     strcat(thinkOutput, p);
8021                 }
8022
8023                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8024                     strcat(programStats.movelist, " ");
8025                     strcat(programStats.movelist, p);
8026                 }
8027
8028                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8029                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8030                     DisplayMove(currentMove - 1);
8031                 }
8032                 return;
8033             }
8034         }
8035         else {
8036             buf1[0] = NULLCHAR;
8037
8038             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8039                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
8040             {
8041                 ChessProgramStats cpstats;
8042
8043                 if (plyext != ' ' && plyext != '\t') {
8044                     time *= 100;
8045                 }
8046
8047                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8048                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8049                     curscore = -curscore;
8050                 }
8051
8052                 cpstats.depth = plylev;
8053                 cpstats.nodes = nodes;
8054                 cpstats.time = time;
8055                 cpstats.score = curscore;
8056                 cpstats.got_only_move = 0;
8057                 cpstats.movelist[0] = '\0';
8058
8059                 if (buf1[0] != NULLCHAR) {
8060                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8061                 }
8062
8063                 cpstats.ok_to_send = 0;
8064                 cpstats.line_is_book = 0;
8065                 cpstats.nr_moves = 0;
8066                 cpstats.moves_left = 0;
8067
8068                 SendProgramStatsToFrontend( cps, &cpstats );
8069             }
8070         }
8071     }
8072 }
8073
8074
8075 /* Parse a game score from the character string "game", and
8076    record it as the history of the current game.  The game
8077    score is NOT assumed to start from the standard position. 
8078    The display is not updated in any way.
8079    */
8080 void
8081 ParseGameHistory(game)
8082      char *game;
8083 {
8084     ChessMove moveType;
8085     int fromX, fromY, toX, toY, boardIndex;
8086     char promoChar;
8087     char *p, *q;
8088     char buf[MSG_SIZ];
8089
8090     if (appData.debugMode)
8091       fprintf(debugFP, "Parsing game history: %s\n", game);
8092
8093     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8094     gameInfo.site = StrSave(appData.icsHost);
8095     gameInfo.date = PGNDate();
8096     gameInfo.round = StrSave("-");
8097
8098     /* Parse out names of players */
8099     while (*game == ' ') game++;
8100     p = buf;
8101     while (*game != ' ') *p++ = *game++;
8102     *p = NULLCHAR;
8103     gameInfo.white = StrSave(buf);
8104     while (*game == ' ') game++;
8105     p = buf;
8106     while (*game != ' ' && *game != '\n') *p++ = *game++;
8107     *p = NULLCHAR;
8108     gameInfo.black = StrSave(buf);
8109
8110     /* Parse moves */
8111     boardIndex = blackPlaysFirst ? 1 : 0;
8112     yynewstr(game);
8113     for (;;) {
8114         yyboardindex = boardIndex;
8115         moveType = (ChessMove) yylex();
8116         switch (moveType) {
8117           case IllegalMove:             /* maybe suicide chess, etc. */
8118   if (appData.debugMode) {
8119     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8120     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8121     setbuf(debugFP, NULL);
8122   }
8123           case WhitePromotionChancellor:
8124           case BlackPromotionChancellor:
8125           case WhitePromotionArchbishop:
8126           case BlackPromotionArchbishop:
8127           case WhitePromotionQueen:
8128           case BlackPromotionQueen:
8129           case WhitePromotionRook:
8130           case BlackPromotionRook:
8131           case WhitePromotionBishop:
8132           case BlackPromotionBishop:
8133           case WhitePromotionKnight:
8134           case BlackPromotionKnight:
8135           case WhitePromotionKing:
8136           case BlackPromotionKing:
8137           case WhiteNonPromotion:
8138           case BlackNonPromotion:
8139           case NormalMove:
8140           case WhiteCapturesEnPassant:
8141           case BlackCapturesEnPassant:
8142           case WhiteKingSideCastle:
8143           case WhiteQueenSideCastle:
8144           case BlackKingSideCastle:
8145           case BlackQueenSideCastle:
8146           case WhiteKingSideCastleWild:
8147           case WhiteQueenSideCastleWild:
8148           case BlackKingSideCastleWild:
8149           case BlackQueenSideCastleWild:
8150           /* PUSH Fabien */
8151           case WhiteHSideCastleFR:
8152           case WhiteASideCastleFR:
8153           case BlackHSideCastleFR:
8154           case BlackASideCastleFR:
8155           /* POP Fabien */
8156             fromX = currentMoveString[0] - AAA;
8157             fromY = currentMoveString[1] - ONE;
8158             toX = currentMoveString[2] - AAA;
8159             toY = currentMoveString[3] - ONE;
8160             promoChar = currentMoveString[4];
8161             break;
8162           case WhiteDrop:
8163           case BlackDrop:
8164             fromX = moveType == WhiteDrop ?
8165               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8166             (int) CharToPiece(ToLower(currentMoveString[0]));
8167             fromY = DROP_RANK;
8168             toX = currentMoveString[2] - AAA;
8169             toY = currentMoveString[3] - ONE;
8170             promoChar = NULLCHAR;
8171             break;
8172           case AmbiguousMove:
8173             /* bug? */
8174             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8175   if (appData.debugMode) {
8176     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8177     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8178     setbuf(debugFP, NULL);
8179   }
8180             DisplayError(buf, 0);
8181             return;
8182           case ImpossibleMove:
8183             /* bug? */
8184             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8185   if (appData.debugMode) {
8186     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8187     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8188     setbuf(debugFP, NULL);
8189   }
8190             DisplayError(buf, 0);
8191             return;
8192           case (ChessMove) 0:   /* end of file */
8193             if (boardIndex < backwardMostMove) {
8194                 /* Oops, gap.  How did that happen? */
8195                 DisplayError(_("Gap in move list"), 0);
8196                 return;
8197             }
8198             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8199             if (boardIndex > forwardMostMove) {
8200                 forwardMostMove = boardIndex;
8201             }
8202             return;
8203           case ElapsedTime:
8204             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8205                 strcat(parseList[boardIndex-1], " ");
8206                 strcat(parseList[boardIndex-1], yy_text);
8207             }
8208             continue;
8209           case Comment:
8210           case PGNTag:
8211           case NAG:
8212           default:
8213             /* ignore */
8214             continue;
8215           case WhiteWins:
8216           case BlackWins:
8217           case GameIsDrawn:
8218           case GameUnfinished:
8219             if (gameMode == IcsExamining) {
8220                 if (boardIndex < backwardMostMove) {
8221                     /* Oops, gap.  How did that happen? */
8222                     return;
8223                 }
8224                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8225                 return;
8226             }
8227             gameInfo.result = moveType;
8228             p = strchr(yy_text, '{');
8229             if (p == NULL) p = strchr(yy_text, '(');
8230             if (p == NULL) {
8231                 p = yy_text;
8232                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8233             } else {
8234                 q = strchr(p, *p == '{' ? '}' : ')');
8235                 if (q != NULL) *q = NULLCHAR;
8236                 p++;
8237             }
8238             gameInfo.resultDetails = StrSave(p);
8239             continue;
8240         }
8241         if (boardIndex >= forwardMostMove &&
8242             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8243             backwardMostMove = blackPlaysFirst ? 1 : 0;
8244             return;
8245         }
8246         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8247                                  fromY, fromX, toY, toX, promoChar,
8248                                  parseList[boardIndex]);
8249         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8250         /* currentMoveString is set as a side-effect of yylex */
8251         strcpy(moveList[boardIndex], currentMoveString);
8252         strcat(moveList[boardIndex], "\n");
8253         boardIndex++;
8254         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8255         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8256           case MT_NONE:
8257           case MT_STALEMATE:
8258           default:
8259             break;
8260           case MT_CHECK:
8261             if(gameInfo.variant != VariantShogi)
8262                 strcat(parseList[boardIndex - 1], "+");
8263             break;
8264           case MT_CHECKMATE:
8265           case MT_STAINMATE:
8266             strcat(parseList[boardIndex - 1], "#");
8267             break;
8268         }
8269     }
8270 }
8271
8272
8273 /* Apply a move to the given board  */
8274 void
8275 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8276      int fromX, fromY, toX, toY;
8277      int promoChar;
8278      Board board;
8279 {
8280   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8281   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8282
8283     /* [HGM] compute & store e.p. status and castling rights for new position */
8284     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8285
8286       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8287       oldEP = (signed char)board[EP_STATUS];
8288       board[EP_STATUS] = EP_NONE;
8289
8290       if( board[toY][toX] != EmptySquare ) 
8291            board[EP_STATUS] = EP_CAPTURE;  
8292
8293   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8294   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8295        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8296          
8297   if (fromY == DROP_RANK) {
8298         /* must be first */
8299         piece = board[toY][toX] = (ChessSquare) fromX;
8300   } else {
8301       int i;
8302
8303       if( board[fromY][fromX] == WhitePawn ) {
8304            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8305                board[EP_STATUS] = EP_PAWN_MOVE;
8306            if( toY-fromY==2) {
8307                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8308                         gameInfo.variant != VariantBerolina || toX < fromX)
8309                       board[EP_STATUS] = toX | berolina;
8310                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8311                         gameInfo.variant != VariantBerolina || toX > fromX) 
8312                       board[EP_STATUS] = toX;
8313            }
8314       } else 
8315       if( board[fromY][fromX] == BlackPawn ) {
8316            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8317                board[EP_STATUS] = EP_PAWN_MOVE; 
8318            if( toY-fromY== -2) {
8319                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8320                         gameInfo.variant != VariantBerolina || toX < fromX)
8321                       board[EP_STATUS] = toX | berolina;
8322                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8323                         gameInfo.variant != VariantBerolina || toX > fromX) 
8324                       board[EP_STATUS] = toX;
8325            }
8326        }
8327
8328        for(i=0; i<nrCastlingRights; i++) {
8329            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8330               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8331              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8332        }
8333
8334      if (fromX == toX && fromY == toY) return;
8335
8336      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8337      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8338      if(gameInfo.variant == VariantKnightmate)
8339          king += (int) WhiteUnicorn - (int) WhiteKing;
8340
8341     /* Code added by Tord: */
8342     /* FRC castling assumed when king captures friendly rook. */
8343     if (board[fromY][fromX] == WhiteKing &&
8344              board[toY][toX] == WhiteRook) {
8345       board[fromY][fromX] = EmptySquare;
8346       board[toY][toX] = EmptySquare;
8347       if(toX > fromX) {
8348         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8349       } else {
8350         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8351       }
8352     } else if (board[fromY][fromX] == BlackKing &&
8353                board[toY][toX] == BlackRook) {
8354       board[fromY][fromX] = EmptySquare;
8355       board[toY][toX] = EmptySquare;
8356       if(toX > fromX) {
8357         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8358       } else {
8359         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8360       }
8361     /* End of code added by Tord */
8362
8363     } else if (board[fromY][fromX] == king
8364         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8365         && toY == fromY && toX > fromX+1) {
8366         board[fromY][fromX] = EmptySquare;
8367         board[toY][toX] = king;
8368         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8369         board[fromY][BOARD_RGHT-1] = EmptySquare;
8370     } else if (board[fromY][fromX] == king
8371         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8372                && toY == fromY && toX < fromX-1) {
8373         board[fromY][fromX] = EmptySquare;
8374         board[toY][toX] = king;
8375         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8376         board[fromY][BOARD_LEFT] = EmptySquare;
8377     } else if (board[fromY][fromX] == WhitePawn
8378                && toY >= BOARD_HEIGHT-promoRank
8379                && gameInfo.variant != VariantXiangqi
8380                ) {
8381         /* white pawn promotion */
8382         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8383         if (board[toY][toX] == EmptySquare) {
8384             board[toY][toX] = WhiteQueen;
8385         }
8386         if(gameInfo.variant==VariantBughouse ||
8387            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8388             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8389         board[fromY][fromX] = EmptySquare;
8390     } else if ((fromY == BOARD_HEIGHT-4)
8391                && (toX != fromX)
8392                && gameInfo.variant != VariantXiangqi
8393                && gameInfo.variant != VariantBerolina
8394                && (board[fromY][fromX] == WhitePawn)
8395                && (board[toY][toX] == EmptySquare)) {
8396         board[fromY][fromX] = EmptySquare;
8397         board[toY][toX] = WhitePawn;
8398         captured = board[toY - 1][toX];
8399         board[toY - 1][toX] = EmptySquare;
8400     } else if ((fromY == BOARD_HEIGHT-4)
8401                && (toX == fromX)
8402                && gameInfo.variant == VariantBerolina
8403                && (board[fromY][fromX] == WhitePawn)
8404                && (board[toY][toX] == EmptySquare)) {
8405         board[fromY][fromX] = EmptySquare;
8406         board[toY][toX] = WhitePawn;
8407         if(oldEP & EP_BEROLIN_A) {
8408                 captured = board[fromY][fromX-1];
8409                 board[fromY][fromX-1] = EmptySquare;
8410         }else{  captured = board[fromY][fromX+1];
8411                 board[fromY][fromX+1] = EmptySquare;
8412         }
8413     } else if (board[fromY][fromX] == king
8414         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8415                && toY == fromY && toX > fromX+1) {
8416         board[fromY][fromX] = EmptySquare;
8417         board[toY][toX] = king;
8418         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8419         board[fromY][BOARD_RGHT-1] = EmptySquare;
8420     } else if (board[fromY][fromX] == king
8421         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8422                && toY == fromY && toX < fromX-1) {
8423         board[fromY][fromX] = EmptySquare;
8424         board[toY][toX] = king;
8425         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8426         board[fromY][BOARD_LEFT] = EmptySquare;
8427     } else if (fromY == 7 && fromX == 3
8428                && board[fromY][fromX] == BlackKing
8429                && toY == 7 && toX == 5) {
8430         board[fromY][fromX] = EmptySquare;
8431         board[toY][toX] = BlackKing;
8432         board[fromY][7] = EmptySquare;
8433         board[toY][4] = BlackRook;
8434     } else if (fromY == 7 && fromX == 3
8435                && board[fromY][fromX] == BlackKing
8436                && toY == 7 && toX == 1) {
8437         board[fromY][fromX] = EmptySquare;
8438         board[toY][toX] = BlackKing;
8439         board[fromY][0] = EmptySquare;
8440         board[toY][2] = BlackRook;
8441     } else if (board[fromY][fromX] == BlackPawn
8442                && toY < promoRank
8443                && gameInfo.variant != VariantXiangqi
8444                ) {
8445         /* black pawn promotion */
8446         board[toY][toX] = CharToPiece(ToLower(promoChar));
8447         if (board[toY][toX] == EmptySquare) {
8448             board[toY][toX] = BlackQueen;
8449         }
8450         if(gameInfo.variant==VariantBughouse ||
8451            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8452             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8453         board[fromY][fromX] = EmptySquare;
8454     } else if ((fromY == 3)
8455                && (toX != fromX)
8456                && gameInfo.variant != VariantXiangqi
8457                && gameInfo.variant != VariantBerolina
8458                && (board[fromY][fromX] == BlackPawn)
8459                && (board[toY][toX] == EmptySquare)) {
8460         board[fromY][fromX] = EmptySquare;
8461         board[toY][toX] = BlackPawn;
8462         captured = board[toY + 1][toX];
8463         board[toY + 1][toX] = EmptySquare;
8464     } else if ((fromY == 3)
8465                && (toX == fromX)
8466                && gameInfo.variant == VariantBerolina
8467                && (board[fromY][fromX] == BlackPawn)
8468                && (board[toY][toX] == EmptySquare)) {
8469         board[fromY][fromX] = EmptySquare;
8470         board[toY][toX] = BlackPawn;
8471         if(oldEP & EP_BEROLIN_A) {
8472                 captured = board[fromY][fromX-1];
8473                 board[fromY][fromX-1] = EmptySquare;
8474         }else{  captured = board[fromY][fromX+1];
8475                 board[fromY][fromX+1] = EmptySquare;
8476         }
8477     } else {
8478         board[toY][toX] = board[fromY][fromX];
8479         board[fromY][fromX] = EmptySquare;
8480     }
8481
8482     /* [HGM] now we promote for Shogi, if needed */
8483     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8484         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8485   }
8486
8487     if (gameInfo.holdingsWidth != 0) {
8488
8489       /* !!A lot more code needs to be written to support holdings  */
8490       /* [HGM] OK, so I have written it. Holdings are stored in the */
8491       /* penultimate board files, so they are automaticlly stored   */
8492       /* in the game history.                                       */
8493       if (fromY == DROP_RANK) {
8494         /* Delete from holdings, by decreasing count */
8495         /* and erasing image if necessary            */
8496         p = (int) fromX;
8497         if(p < (int) BlackPawn) { /* white drop */
8498              p -= (int)WhitePawn;
8499                  p = PieceToNumber((ChessSquare)p);
8500              if(p >= gameInfo.holdingsSize) p = 0;
8501              if(--board[p][BOARD_WIDTH-2] <= 0)
8502                   board[p][BOARD_WIDTH-1] = EmptySquare;
8503              if((int)board[p][BOARD_WIDTH-2] < 0)
8504                         board[p][BOARD_WIDTH-2] = 0;
8505         } else {                  /* black drop */
8506              p -= (int)BlackPawn;
8507                  p = PieceToNumber((ChessSquare)p);
8508              if(p >= gameInfo.holdingsSize) p = 0;
8509              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8510                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8511              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8512                         board[BOARD_HEIGHT-1-p][1] = 0;
8513         }
8514       }
8515       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8516           && gameInfo.variant != VariantBughouse        ) {
8517         /* [HGM] holdings: Add to holdings, if holdings exist */
8518         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8519                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8520                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8521         }
8522         p = (int) captured;
8523         if (p >= (int) BlackPawn) {
8524           p -= (int)BlackPawn;
8525           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8526                   /* in Shogi restore piece to its original  first */
8527                   captured = (ChessSquare) (DEMOTED captured);
8528                   p = DEMOTED p;
8529           }
8530           p = PieceToNumber((ChessSquare)p);
8531           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8532           board[p][BOARD_WIDTH-2]++;
8533           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8534         } else {
8535           p -= (int)WhitePawn;
8536           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8537                   captured = (ChessSquare) (DEMOTED captured);
8538                   p = DEMOTED p;
8539           }
8540           p = PieceToNumber((ChessSquare)p);
8541           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8542           board[BOARD_HEIGHT-1-p][1]++;
8543           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8544         }
8545       }
8546     } else if (gameInfo.variant == VariantAtomic) {
8547       if (captured != EmptySquare) {
8548         int y, x;
8549         for (y = toY-1; y <= toY+1; y++) {
8550           for (x = toX-1; x <= toX+1; x++) {
8551             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8552                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8553               board[y][x] = EmptySquare;
8554             }
8555           }
8556         }
8557         board[toY][toX] = EmptySquare;
8558       }
8559     }
8560     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8561         /* [HGM] Shogi promotions */
8562         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8563     }
8564
8565     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8566                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8567         // [HGM] superchess: take promotion piece out of holdings
8568         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8569         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8570             if(!--board[k][BOARD_WIDTH-2])
8571                 board[k][BOARD_WIDTH-1] = EmptySquare;
8572         } else {
8573             if(!--board[BOARD_HEIGHT-1-k][1])
8574                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8575         }
8576     }
8577
8578 }
8579
8580 /* Updates forwardMostMove */
8581 void
8582 MakeMove(fromX, fromY, toX, toY, promoChar)
8583      int fromX, fromY, toX, toY;
8584      int promoChar;
8585 {
8586 //    forwardMostMove++; // [HGM] bare: moved downstream
8587
8588     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8589         int timeLeft; static int lastLoadFlag=0; int king, piece;
8590         piece = boards[forwardMostMove][fromY][fromX];
8591         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8592         if(gameInfo.variant == VariantKnightmate)
8593             king += (int) WhiteUnicorn - (int) WhiteKing;
8594         if(forwardMostMove == 0) {
8595             if(blackPlaysFirst) 
8596                 fprintf(serverMoves, "%s;", second.tidy);
8597             fprintf(serverMoves, "%s;", first.tidy);
8598             if(!blackPlaysFirst) 
8599                 fprintf(serverMoves, "%s;", second.tidy);
8600         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8601         lastLoadFlag = loadFlag;
8602         // print base move
8603         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8604         // print castling suffix
8605         if( toY == fromY && piece == king ) {
8606             if(toX-fromX > 1)
8607                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8608             if(fromX-toX >1)
8609                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8610         }
8611         // e.p. suffix
8612         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8613              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8614              boards[forwardMostMove][toY][toX] == EmptySquare
8615              && fromX != toX && fromY != toY)
8616                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8617         // promotion suffix
8618         if(promoChar != NULLCHAR)
8619                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8620         if(!loadFlag) {
8621             fprintf(serverMoves, "/%d/%d",
8622                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8623             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8624             else                      timeLeft = blackTimeRemaining/1000;
8625             fprintf(serverMoves, "/%d", timeLeft);
8626         }
8627         fflush(serverMoves);
8628     }
8629
8630     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8631       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8632                         0, 1);
8633       return;
8634     }
8635     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8636     if (commentList[forwardMostMove+1] != NULL) {
8637         free(commentList[forwardMostMove+1]);
8638         commentList[forwardMostMove+1] = NULL;
8639     }
8640     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8641     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8642     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8643     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8644     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8645     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8646     gameInfo.result = GameUnfinished;
8647     if (gameInfo.resultDetails != NULL) {
8648         free(gameInfo.resultDetails);
8649         gameInfo.resultDetails = NULL;
8650     }
8651     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8652                               moveList[forwardMostMove - 1]);
8653     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8654                              PosFlags(forwardMostMove - 1),
8655                              fromY, fromX, toY, toX, promoChar,
8656                              parseList[forwardMostMove - 1]);
8657     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8658       case MT_NONE:
8659       case MT_STALEMATE:
8660       default:
8661         break;
8662       case MT_CHECK:
8663         if(gameInfo.variant != VariantShogi)
8664             strcat(parseList[forwardMostMove - 1], "+");
8665         break;
8666       case MT_CHECKMATE:
8667       case MT_STAINMATE:
8668         strcat(parseList[forwardMostMove - 1], "#");
8669         break;
8670     }
8671     if (appData.debugMode) {
8672         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8673     }
8674
8675 }
8676
8677 /* Updates currentMove if not pausing */
8678 void
8679 ShowMove(fromX, fromY, toX, toY)
8680 {
8681     int instant = (gameMode == PlayFromGameFile) ?
8682         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8683     if(appData.noGUI) return;
8684     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8685         if (!instant) {
8686             if (forwardMostMove == currentMove + 1) {
8687                 AnimateMove(boards[forwardMostMove - 1],
8688                             fromX, fromY, toX, toY);
8689             }
8690             if (appData.highlightLastMove) {
8691                 SetHighlights(fromX, fromY, toX, toY);
8692             }
8693         }
8694         currentMove = forwardMostMove;
8695     }
8696
8697     if (instant) return;
8698
8699     DisplayMove(currentMove - 1);
8700     DrawPosition(FALSE, boards[currentMove]);
8701     DisplayBothClocks();
8702     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8703 }
8704
8705 void SendEgtPath(ChessProgramState *cps)
8706 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8707         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8708
8709         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8710
8711         while(*p) {
8712             char c, *q = name+1, *r, *s;
8713
8714             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8715             while(*p && *p != ',') *q++ = *p++;
8716             *q++ = ':'; *q = 0;
8717             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8718                 strcmp(name, ",nalimov:") == 0 ) {
8719                 // take nalimov path from the menu-changeable option first, if it is defined
8720                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8721                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8722             } else
8723             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8724                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8725                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8726                 s = r = StrStr(s, ":") + 1; // beginning of path info
8727                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8728                 c = *r; *r = 0;             // temporarily null-terminate path info
8729                     *--q = 0;               // strip of trailig ':' from name
8730                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8731                 *r = c;
8732                 SendToProgram(buf,cps);     // send egtbpath command for this format
8733             }
8734             if(*p == ',') p++; // read away comma to position for next format name
8735         }
8736 }
8737
8738 void
8739 InitChessProgram(cps, setup)
8740      ChessProgramState *cps;
8741      int setup; /* [HGM] needed to setup FRC opening position */
8742 {
8743     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8744     if (appData.noChessProgram) return;
8745     hintRequested = FALSE;
8746     bookRequested = FALSE;
8747
8748     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8749     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8750     if(cps->memSize) { /* [HGM] memory */
8751         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8752         SendToProgram(buf, cps);
8753     }
8754     SendEgtPath(cps); /* [HGM] EGT */
8755     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8756         sprintf(buf, "cores %d\n", appData.smpCores);
8757         SendToProgram(buf, cps);
8758     }
8759
8760     SendToProgram(cps->initString, cps);
8761     if (gameInfo.variant != VariantNormal &&
8762         gameInfo.variant != VariantLoadable
8763         /* [HGM] also send variant if board size non-standard */
8764         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8765                                             ) {
8766       char *v = VariantName(gameInfo.variant);
8767       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8768         /* [HGM] in protocol 1 we have to assume all variants valid */
8769         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8770         DisplayFatalError(buf, 0, 1);
8771         return;
8772       }
8773
8774       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8775       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8776       if( gameInfo.variant == VariantXiangqi )
8777            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8778       if( gameInfo.variant == VariantShogi )
8779            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8780       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8781            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8782       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8783                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8784            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8785       if( gameInfo.variant == VariantCourier )
8786            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8787       if( gameInfo.variant == VariantSuper )
8788            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8789       if( gameInfo.variant == VariantGreat )
8790            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8791
8792       if(overruled) {
8793            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8794                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8795            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8796            if(StrStr(cps->variants, b) == NULL) { 
8797                // specific sized variant not known, check if general sizing allowed
8798                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8799                    if(StrStr(cps->variants, "boardsize") == NULL) {
8800                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8801                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8802                        DisplayFatalError(buf, 0, 1);
8803                        return;
8804                    }
8805                    /* [HGM] here we really should compare with the maximum supported board size */
8806                }
8807            }
8808       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8809       sprintf(buf, "variant %s\n", b);
8810       SendToProgram(buf, cps);
8811     }
8812     currentlyInitializedVariant = gameInfo.variant;
8813
8814     /* [HGM] send opening position in FRC to first engine */
8815     if(setup) {
8816           SendToProgram("force\n", cps);
8817           SendBoard(cps, 0);
8818           /* engine is now in force mode! Set flag to wake it up after first move. */
8819           setboardSpoiledMachineBlack = 1;
8820     }
8821
8822     if (cps->sendICS) {
8823       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8824       SendToProgram(buf, cps);
8825     }
8826     cps->maybeThinking = FALSE;
8827     cps->offeredDraw = 0;
8828     if (!appData.icsActive) {
8829         SendTimeControl(cps, movesPerSession, timeControl,
8830                         timeIncrement, appData.searchDepth,
8831                         searchTime);
8832     }
8833     if (appData.showThinking 
8834         // [HGM] thinking: four options require thinking output to be sent
8835         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8836                                 ) {
8837         SendToProgram("post\n", cps);
8838     }
8839     SendToProgram("hard\n", cps);
8840     if (!appData.ponderNextMove) {
8841         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8842            it without being sure what state we are in first.  "hard"
8843            is not a toggle, so that one is OK.
8844          */
8845         SendToProgram("easy\n", cps);
8846     }
8847     if (cps->usePing) {
8848       sprintf(buf, "ping %d\n", ++cps->lastPing);
8849       SendToProgram(buf, cps);
8850     }
8851     cps->initDone = TRUE;
8852 }   
8853
8854
8855 void
8856 StartChessProgram(cps)
8857      ChessProgramState *cps;
8858 {
8859     char buf[MSG_SIZ];
8860     int err;
8861
8862     if (appData.noChessProgram) return;
8863     cps->initDone = FALSE;
8864
8865     if (strcmp(cps->host, "localhost") == 0) {
8866         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8867     } else if (*appData.remoteShell == NULLCHAR) {
8868         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8869     } else {
8870         if (*appData.remoteUser == NULLCHAR) {
8871           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8872                     cps->program);
8873         } else {
8874           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8875                     cps->host, appData.remoteUser, cps->program);
8876         }
8877         err = StartChildProcess(buf, "", &cps->pr);
8878     }
8879     
8880     if (err != 0) {
8881         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8882         DisplayFatalError(buf, err, 1);
8883         cps->pr = NoProc;
8884         cps->isr = NULL;
8885         return;
8886     }
8887     
8888     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8889     if (cps->protocolVersion > 1) {
8890       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8891       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8892       cps->comboCnt = 0;  //                and values of combo boxes
8893       SendToProgram(buf, cps);
8894     } else {
8895       SendToProgram("xboard\n", cps);
8896     }
8897 }
8898
8899
8900 void
8901 TwoMachinesEventIfReady P((void))
8902 {
8903   if (first.lastPing != first.lastPong) {
8904     DisplayMessage("", _("Waiting for first chess program"));
8905     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8906     return;
8907   }
8908   if (second.lastPing != second.lastPong) {
8909     DisplayMessage("", _("Waiting for second chess program"));
8910     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8911     return;
8912   }
8913   ThawUI();
8914   TwoMachinesEvent();
8915 }
8916
8917 void
8918 NextMatchGame P((void))
8919 {
8920     int index; /* [HGM] autoinc: step load index during match */
8921     Reset(FALSE, TRUE);
8922     if (*appData.loadGameFile != NULLCHAR) {
8923         index = appData.loadGameIndex;
8924         if(index < 0) { // [HGM] autoinc
8925             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8926             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8927         } 
8928         LoadGameFromFile(appData.loadGameFile,
8929                          index,
8930                          appData.loadGameFile, FALSE);
8931     } else if (*appData.loadPositionFile != NULLCHAR) {
8932         index = appData.loadPositionIndex;
8933         if(index < 0) { // [HGM] autoinc
8934             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8935             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8936         } 
8937         LoadPositionFromFile(appData.loadPositionFile,
8938                              index,
8939                              appData.loadPositionFile);
8940     }
8941     TwoMachinesEventIfReady();
8942 }
8943
8944 void UserAdjudicationEvent( int result )
8945 {
8946     ChessMove gameResult = GameIsDrawn;
8947
8948     if( result > 0 ) {
8949         gameResult = WhiteWins;
8950     }
8951     else if( result < 0 ) {
8952         gameResult = BlackWins;
8953     }
8954
8955     if( gameMode == TwoMachinesPlay ) {
8956         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8957     }
8958 }
8959
8960
8961 // [HGM] save: calculate checksum of game to make games easily identifiable
8962 int StringCheckSum(char *s)
8963 {
8964         int i = 0;
8965         if(s==NULL) return 0;
8966         while(*s) i = i*259 + *s++;
8967         return i;
8968 }
8969
8970 int GameCheckSum()
8971 {
8972         int i, sum=0;
8973         for(i=backwardMostMove; i<forwardMostMove; i++) {
8974                 sum += pvInfoList[i].depth;
8975                 sum += StringCheckSum(parseList[i]);
8976                 sum += StringCheckSum(commentList[i]);
8977                 sum *= 261;
8978         }
8979         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8980         return sum + StringCheckSum(commentList[i]);
8981 } // end of save patch
8982
8983 void
8984 GameEnds(result, resultDetails, whosays)
8985      ChessMove result;
8986      char *resultDetails;
8987      int whosays;
8988 {
8989     GameMode nextGameMode;
8990     int isIcsGame;
8991     char buf[MSG_SIZ], popupRequested = 0;
8992
8993     if(endingGame) return; /* [HGM] crash: forbid recursion */
8994     endingGame = 1;
8995     if(twoBoards) { // [HGM] dual: switch back to one board
8996         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8997         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8998     }
8999     if (appData.debugMode) {
9000       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9001               result, resultDetails ? resultDetails : "(null)", whosays);
9002     }
9003
9004     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9005
9006     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9007         /* If we are playing on ICS, the server decides when the
9008            game is over, but the engine can offer to draw, claim 
9009            a draw, or resign. 
9010          */
9011 #if ZIPPY
9012         if (appData.zippyPlay && first.initDone) {
9013             if (result == GameIsDrawn) {
9014                 /* In case draw still needs to be claimed */
9015                 SendToICS(ics_prefix);
9016                 SendToICS("draw\n");
9017             } else if (StrCaseStr(resultDetails, "resign")) {
9018                 SendToICS(ics_prefix);
9019                 SendToICS("resign\n");
9020             }
9021         }
9022 #endif
9023         endingGame = 0; /* [HGM] crash */
9024         return;
9025     }
9026
9027     /* If we're loading the game from a file, stop */
9028     if (whosays == GE_FILE) {
9029       (void) StopLoadGameTimer();
9030       gameFileFP = NULL;
9031     }
9032
9033     /* Cancel draw offers */
9034     first.offeredDraw = second.offeredDraw = 0;
9035
9036     /* If this is an ICS game, only ICS can really say it's done;
9037        if not, anyone can. */
9038     isIcsGame = (gameMode == IcsPlayingWhite || 
9039                  gameMode == IcsPlayingBlack || 
9040                  gameMode == IcsObserving    || 
9041                  gameMode == IcsExamining);
9042
9043     if (!isIcsGame || whosays == GE_ICS) {
9044         /* OK -- not an ICS game, or ICS said it was done */
9045         StopClocks();
9046         if (!isIcsGame && !appData.noChessProgram) 
9047           SetUserThinkingEnables();
9048     
9049         /* [HGM] if a machine claims the game end we verify this claim */
9050         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9051             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9052                 char claimer;
9053                 ChessMove trueResult = (ChessMove) -1;
9054
9055                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9056                                             first.twoMachinesColor[0] :
9057                                             second.twoMachinesColor[0] ;
9058
9059                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9060                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9061                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9062                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9063                 } else
9064                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9065                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9066                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9067                 } else
9068                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9069                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9070                 }
9071
9072                 // now verify win claims, but not in drop games, as we don't understand those yet
9073                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9074                                                  || gameInfo.variant == VariantGreat) &&
9075                     (result == WhiteWins && claimer == 'w' ||
9076                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9077                       if (appData.debugMode) {
9078                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9079                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9080                       }
9081                       if(result != trueResult) {
9082                               sprintf(buf, "False win claim: '%s'", resultDetails);
9083                               result = claimer == 'w' ? BlackWins : WhiteWins;
9084                               resultDetails = buf;
9085                       }
9086                 } else
9087                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9088                     && (forwardMostMove <= backwardMostMove ||
9089                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9090                         (claimer=='b')==(forwardMostMove&1))
9091                                                                                   ) {
9092                       /* [HGM] verify: draws that were not flagged are false claims */
9093                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9094                       result = claimer == 'w' ? BlackWins : WhiteWins;
9095                       resultDetails = buf;
9096                 }
9097                 /* (Claiming a loss is accepted no questions asked!) */
9098             }
9099             /* [HGM] bare: don't allow bare King to win */
9100             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9101                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9102                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9103                && result != GameIsDrawn)
9104             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9105                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9106                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9107                         if(p >= 0 && p <= (int)WhiteKing) k++;
9108                 }
9109                 if (appData.debugMode) {
9110                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9111                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9112                 }
9113                 if(k <= 1) {
9114                         result = GameIsDrawn;
9115                         sprintf(buf, "%s but bare king", resultDetails);
9116                         resultDetails = buf;
9117                 }
9118             }
9119         }
9120
9121
9122         if(serverMoves != NULL && !loadFlag) { char c = '=';
9123             if(result==WhiteWins) c = '+';
9124             if(result==BlackWins) c = '-';
9125             if(resultDetails != NULL)
9126                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9127         }
9128         if (resultDetails != NULL) {
9129             gameInfo.result = result;
9130             gameInfo.resultDetails = StrSave(resultDetails);
9131
9132             /* display last move only if game was not loaded from file */
9133             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9134                 DisplayMove(currentMove - 1);
9135     
9136             if (forwardMostMove != 0) {
9137                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9138                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9139                                                                 ) {
9140                     if (*appData.saveGameFile != NULLCHAR) {
9141                         SaveGameToFile(appData.saveGameFile, TRUE);
9142                     } else if (appData.autoSaveGames) {
9143                         AutoSaveGame();
9144                     }
9145                     if (*appData.savePositionFile != NULLCHAR) {
9146                         SavePositionToFile(appData.savePositionFile);
9147                     }
9148                 }
9149             }
9150
9151             /* Tell program how game ended in case it is learning */
9152             /* [HGM] Moved this to after saving the PGN, just in case */
9153             /* engine died and we got here through time loss. In that */
9154             /* case we will get a fatal error writing the pipe, which */
9155             /* would otherwise lose us the PGN.                       */
9156             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9157             /* output during GameEnds should never be fatal anymore   */
9158             if (gameMode == MachinePlaysWhite ||
9159                 gameMode == MachinePlaysBlack ||
9160                 gameMode == TwoMachinesPlay ||
9161                 gameMode == IcsPlayingWhite ||
9162                 gameMode == IcsPlayingBlack ||
9163                 gameMode == BeginningOfGame) {
9164                 char buf[MSG_SIZ];
9165                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9166                         resultDetails);
9167                 if (first.pr != NoProc) {
9168                     SendToProgram(buf, &first);
9169                 }
9170                 if (second.pr != NoProc &&
9171                     gameMode == TwoMachinesPlay) {
9172                     SendToProgram(buf, &second);
9173                 }
9174             }
9175         }
9176
9177         if (appData.icsActive) {
9178             if (appData.quietPlay &&
9179                 (gameMode == IcsPlayingWhite ||
9180                  gameMode == IcsPlayingBlack)) {
9181                 SendToICS(ics_prefix);
9182                 SendToICS("set shout 1\n");
9183             }
9184             nextGameMode = IcsIdle;
9185             ics_user_moved = FALSE;
9186             /* clean up premove.  It's ugly when the game has ended and the
9187              * premove highlights are still on the board.
9188              */
9189             if (gotPremove) {
9190               gotPremove = FALSE;
9191               ClearPremoveHighlights();
9192               DrawPosition(FALSE, boards[currentMove]);
9193             }
9194             if (whosays == GE_ICS) {
9195                 switch (result) {
9196                 case WhiteWins:
9197                     if (gameMode == IcsPlayingWhite)
9198                         PlayIcsWinSound();
9199                     else if(gameMode == IcsPlayingBlack)
9200                         PlayIcsLossSound();
9201                     break;
9202                 case BlackWins:
9203                     if (gameMode == IcsPlayingBlack)
9204                         PlayIcsWinSound();
9205                     else if(gameMode == IcsPlayingWhite)
9206                         PlayIcsLossSound();
9207                     break;
9208                 case GameIsDrawn:
9209                     PlayIcsDrawSound();
9210                     break;
9211                 default:
9212                     PlayIcsUnfinishedSound();
9213                 }
9214             }
9215         } else if (gameMode == EditGame ||
9216                    gameMode == PlayFromGameFile || 
9217                    gameMode == AnalyzeMode || 
9218                    gameMode == AnalyzeFile) {
9219             nextGameMode = gameMode;
9220         } else {
9221             nextGameMode = EndOfGame;
9222         }
9223         pausing = FALSE;
9224         ModeHighlight();
9225     } else {
9226         nextGameMode = gameMode;
9227     }
9228
9229     if (appData.noChessProgram) {
9230         gameMode = nextGameMode;
9231         ModeHighlight();
9232         endingGame = 0; /* [HGM] crash */
9233         return;
9234     }
9235
9236     if (first.reuse) {
9237         /* Put first chess program into idle state */
9238         if (first.pr != NoProc &&
9239             (gameMode == MachinePlaysWhite ||
9240              gameMode == MachinePlaysBlack ||
9241              gameMode == TwoMachinesPlay ||
9242              gameMode == IcsPlayingWhite ||
9243              gameMode == IcsPlayingBlack ||
9244              gameMode == BeginningOfGame)) {
9245             SendToProgram("force\n", &first);
9246             if (first.usePing) {
9247               char buf[MSG_SIZ];
9248               sprintf(buf, "ping %d\n", ++first.lastPing);
9249               SendToProgram(buf, &first);
9250             }
9251         }
9252     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9253         /* Kill off first chess program */
9254         if (first.isr != NULL)
9255           RemoveInputSource(first.isr);
9256         first.isr = NULL;
9257     
9258         if (first.pr != NoProc) {
9259             ExitAnalyzeMode();
9260             DoSleep( appData.delayBeforeQuit );
9261             SendToProgram("quit\n", &first);
9262             DoSleep( appData.delayAfterQuit );
9263             DestroyChildProcess(first.pr, first.useSigterm);
9264         }
9265         first.pr = NoProc;
9266     }
9267     if (second.reuse) {
9268         /* Put second chess program into idle state */
9269         if (second.pr != NoProc &&
9270             gameMode == TwoMachinesPlay) {
9271             SendToProgram("force\n", &second);
9272             if (second.usePing) {
9273               char buf[MSG_SIZ];
9274               sprintf(buf, "ping %d\n", ++second.lastPing);
9275               SendToProgram(buf, &second);
9276             }
9277         }
9278     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9279         /* Kill off second chess program */
9280         if (second.isr != NULL)
9281           RemoveInputSource(second.isr);
9282         second.isr = NULL;
9283     
9284         if (second.pr != NoProc) {
9285             DoSleep( appData.delayBeforeQuit );
9286             SendToProgram("quit\n", &second);
9287             DoSleep( appData.delayAfterQuit );
9288             DestroyChildProcess(second.pr, second.useSigterm);
9289         }
9290         second.pr = NoProc;
9291     }
9292
9293     if (matchMode && gameMode == TwoMachinesPlay) {
9294         switch (result) {
9295         case WhiteWins:
9296           if (first.twoMachinesColor[0] == 'w') {
9297             first.matchWins++;
9298           } else {
9299             second.matchWins++;
9300           }
9301           break;
9302         case BlackWins:
9303           if (first.twoMachinesColor[0] == 'b') {
9304             first.matchWins++;
9305           } else {
9306             second.matchWins++;
9307           }
9308           break;
9309         default:
9310           break;
9311         }
9312         if (matchGame < appData.matchGames) {
9313             char *tmp;
9314             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9315                 tmp = first.twoMachinesColor;
9316                 first.twoMachinesColor = second.twoMachinesColor;
9317                 second.twoMachinesColor = tmp;
9318             }
9319             gameMode = nextGameMode;
9320             matchGame++;
9321             if(appData.matchPause>10000 || appData.matchPause<10)
9322                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9323             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9324             endingGame = 0; /* [HGM] crash */
9325             return;
9326         } else {
9327             gameMode = nextGameMode;
9328             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9329                     first.tidy, second.tidy,
9330                     first.matchWins, second.matchWins,
9331                     appData.matchGames - (first.matchWins + second.matchWins));
9332             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9333         }
9334     }
9335     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9336         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9337       ExitAnalyzeMode();
9338     gameMode = nextGameMode;
9339     ModeHighlight();
9340     endingGame = 0;  /* [HGM] crash */
9341     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9342       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9343         matchMode = FALSE; appData.matchGames = matchGame = 0;
9344         DisplayNote(buf);
9345       }
9346     }
9347 }
9348
9349 /* Assumes program was just initialized (initString sent).
9350    Leaves program in force mode. */
9351 void
9352 FeedMovesToProgram(cps, upto) 
9353      ChessProgramState *cps;
9354      int upto;
9355 {
9356     int i;
9357     
9358     if (appData.debugMode)
9359       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9360               startedFromSetupPosition ? "position and " : "",
9361               backwardMostMove, upto, cps->which);
9362     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9363         // [HGM] variantswitch: make engine aware of new variant
9364         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9365                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9366         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9367         SendToProgram(buf, cps);
9368         currentlyInitializedVariant = gameInfo.variant;
9369     }
9370     SendToProgram("force\n", cps);
9371     if (startedFromSetupPosition) {
9372         SendBoard(cps, backwardMostMove);
9373     if (appData.debugMode) {
9374         fprintf(debugFP, "feedMoves\n");
9375     }
9376     }
9377     for (i = backwardMostMove; i < upto; i++) {
9378         SendMoveToProgram(i, cps);
9379     }
9380 }
9381
9382
9383 void
9384 ResurrectChessProgram()
9385 {
9386      /* The chess program may have exited.
9387         If so, restart it and feed it all the moves made so far. */
9388
9389     if (appData.noChessProgram || first.pr != NoProc) return;
9390     
9391     StartChessProgram(&first);
9392     InitChessProgram(&first, FALSE);
9393     FeedMovesToProgram(&first, currentMove);
9394
9395     if (!first.sendTime) {
9396         /* can't tell gnuchess what its clock should read,
9397            so we bow to its notion. */
9398         ResetClocks();
9399         timeRemaining[0][currentMove] = whiteTimeRemaining;
9400         timeRemaining[1][currentMove] = blackTimeRemaining;
9401     }
9402
9403     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9404                 appData.icsEngineAnalyze) && first.analysisSupport) {
9405       SendToProgram("analyze\n", &first);
9406       first.analyzing = TRUE;
9407     }
9408 }
9409
9410 /*
9411  * Button procedures
9412  */
9413 void
9414 Reset(redraw, init)
9415      int redraw, init;
9416 {
9417     int i;
9418
9419     if (appData.debugMode) {
9420         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9421                 redraw, init, gameMode);
9422     }
9423     CleanupTail(); // [HGM] vari: delete any stored variations
9424     pausing = pauseExamInvalid = FALSE;
9425     startedFromSetupPosition = blackPlaysFirst = FALSE;
9426     firstMove = TRUE;
9427     whiteFlag = blackFlag = FALSE;
9428     userOfferedDraw = FALSE;
9429     hintRequested = bookRequested = FALSE;
9430     first.maybeThinking = FALSE;
9431     second.maybeThinking = FALSE;
9432     first.bookSuspend = FALSE; // [HGM] book
9433     second.bookSuspend = FALSE;
9434     thinkOutput[0] = NULLCHAR;
9435     lastHint[0] = NULLCHAR;
9436     ClearGameInfo(&gameInfo);
9437     gameInfo.variant = StringToVariant(appData.variant);
9438     ics_user_moved = ics_clock_paused = FALSE;
9439     ics_getting_history = H_FALSE;
9440     ics_gamenum = -1;
9441     white_holding[0] = black_holding[0] = NULLCHAR;
9442     ClearProgramStats();
9443     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9444     
9445     ResetFrontEnd();
9446     ClearHighlights();
9447     flipView = appData.flipView;
9448     ClearPremoveHighlights();
9449     gotPremove = FALSE;
9450     alarmSounded = FALSE;
9451
9452     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9453     if(appData.serverMovesName != NULL) {
9454         /* [HGM] prepare to make moves file for broadcasting */
9455         clock_t t = clock();
9456         if(serverMoves != NULL) fclose(serverMoves);
9457         serverMoves = fopen(appData.serverMovesName, "r");
9458         if(serverMoves != NULL) {
9459             fclose(serverMoves);
9460             /* delay 15 sec before overwriting, so all clients can see end */
9461             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9462         }
9463         serverMoves = fopen(appData.serverMovesName, "w");
9464     }
9465
9466     ExitAnalyzeMode();
9467     gameMode = BeginningOfGame;
9468     ModeHighlight();
9469     if(appData.icsActive) gameInfo.variant = VariantNormal;
9470     currentMove = forwardMostMove = backwardMostMove = 0;
9471     InitPosition(redraw);
9472     for (i = 0; i < MAX_MOVES; i++) {
9473         if (commentList[i] != NULL) {
9474             free(commentList[i]);
9475             commentList[i] = NULL;
9476         }
9477     }
9478     ResetClocks();
9479     timeRemaining[0][0] = whiteTimeRemaining;
9480     timeRemaining[1][0] = blackTimeRemaining;
9481     if (first.pr == NULL) {
9482         StartChessProgram(&first);
9483     }
9484     if (init) {
9485             InitChessProgram(&first, startedFromSetupPosition);
9486     }
9487     DisplayTitle("");
9488     DisplayMessage("", "");
9489     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9490     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9491 }
9492
9493 void
9494 AutoPlayGameLoop()
9495 {
9496     for (;;) {
9497         if (!AutoPlayOneMove())
9498           return;
9499         if (matchMode || appData.timeDelay == 0)
9500           continue;
9501         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9502           return;
9503         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9504         break;
9505     }
9506 }
9507
9508
9509 int
9510 AutoPlayOneMove()
9511 {
9512     int fromX, fromY, toX, toY;
9513
9514     if (appData.debugMode) {
9515       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9516     }
9517
9518     if (gameMode != PlayFromGameFile)
9519       return FALSE;
9520
9521     if (currentMove >= forwardMostMove) {
9522       gameMode = EditGame;
9523       ModeHighlight();
9524
9525       /* [AS] Clear current move marker at the end of a game */
9526       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9527
9528       return FALSE;
9529     }
9530     
9531     toX = moveList[currentMove][2] - AAA;
9532     toY = moveList[currentMove][3] - ONE;
9533
9534     if (moveList[currentMove][1] == '@') {
9535         if (appData.highlightLastMove) {
9536             SetHighlights(-1, -1, toX, toY);
9537         }
9538     } else {
9539         fromX = moveList[currentMove][0] - AAA;
9540         fromY = moveList[currentMove][1] - ONE;
9541
9542         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9543
9544         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9545
9546         if (appData.highlightLastMove) {
9547             SetHighlights(fromX, fromY, toX, toY);
9548         }
9549     }
9550     DisplayMove(currentMove);
9551     SendMoveToProgram(currentMove++, &first);
9552     DisplayBothClocks();
9553     DrawPosition(FALSE, boards[currentMove]);
9554     // [HGM] PV info: always display, routine tests if empty
9555     DisplayComment(currentMove - 1, commentList[currentMove]);
9556     return TRUE;
9557 }
9558
9559
9560 int
9561 LoadGameOneMove(readAhead)
9562      ChessMove readAhead;
9563 {
9564     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9565     char promoChar = NULLCHAR;
9566     ChessMove moveType;
9567     char move[MSG_SIZ];
9568     char *p, *q;
9569     
9570     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9571         gameMode != AnalyzeMode && gameMode != Training) {
9572         gameFileFP = NULL;
9573         return FALSE;
9574     }
9575     
9576     yyboardindex = forwardMostMove;
9577     if (readAhead != (ChessMove)0) {
9578       moveType = readAhead;
9579     } else {
9580       if (gameFileFP == NULL)
9581           return FALSE;
9582       moveType = (ChessMove) yylex();
9583     }
9584     
9585     done = FALSE;
9586     switch (moveType) {
9587       case Comment:
9588         if (appData.debugMode) 
9589           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9590         p = yy_text;
9591
9592         /* append the comment but don't display it */
9593         AppendComment(currentMove, p, FALSE);
9594         return TRUE;
9595
9596       case WhiteCapturesEnPassant:
9597       case BlackCapturesEnPassant:
9598       case WhitePromotionChancellor:
9599       case BlackPromotionChancellor:
9600       case WhitePromotionArchbishop:
9601       case BlackPromotionArchbishop:
9602       case WhitePromotionCentaur:
9603       case BlackPromotionCentaur:
9604       case WhitePromotionQueen:
9605       case BlackPromotionQueen:
9606       case WhitePromotionRook:
9607       case BlackPromotionRook:
9608       case WhitePromotionBishop:
9609       case BlackPromotionBishop:
9610       case WhitePromotionKnight:
9611       case BlackPromotionKnight:
9612       case WhitePromotionKing:
9613       case BlackPromotionKing:
9614       case WhiteNonPromotion:
9615       case BlackNonPromotion:
9616       case NormalMove:
9617       case WhiteKingSideCastle:
9618       case WhiteQueenSideCastle:
9619       case BlackKingSideCastle:
9620       case BlackQueenSideCastle:
9621       case WhiteKingSideCastleWild:
9622       case WhiteQueenSideCastleWild:
9623       case BlackKingSideCastleWild:
9624       case BlackQueenSideCastleWild:
9625       /* PUSH Fabien */
9626       case WhiteHSideCastleFR:
9627       case WhiteASideCastleFR:
9628       case BlackHSideCastleFR:
9629       case BlackASideCastleFR:
9630       /* POP Fabien */
9631         if (appData.debugMode)
9632           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9633         fromX = currentMoveString[0] - AAA;
9634         fromY = currentMoveString[1] - ONE;
9635         toX = currentMoveString[2] - AAA;
9636         toY = currentMoveString[3] - ONE;
9637         promoChar = currentMoveString[4];
9638         break;
9639
9640       case WhiteDrop:
9641       case BlackDrop:
9642         if (appData.debugMode)
9643           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9644         fromX = moveType == WhiteDrop ?
9645           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9646         (int) CharToPiece(ToLower(currentMoveString[0]));
9647         fromY = DROP_RANK;
9648         toX = currentMoveString[2] - AAA;
9649         toY = currentMoveString[3] - ONE;
9650         break;
9651
9652       case WhiteWins:
9653       case BlackWins:
9654       case GameIsDrawn:
9655       case GameUnfinished:
9656         if (appData.debugMode)
9657           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9658         p = strchr(yy_text, '{');
9659         if (p == NULL) p = strchr(yy_text, '(');
9660         if (p == NULL) {
9661             p = yy_text;
9662             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9663         } else {
9664             q = strchr(p, *p == '{' ? '}' : ')');
9665             if (q != NULL) *q = NULLCHAR;
9666             p++;
9667         }
9668         GameEnds(moveType, p, GE_FILE);
9669         done = TRUE;
9670         if (cmailMsgLoaded) {
9671             ClearHighlights();
9672             flipView = WhiteOnMove(currentMove);
9673             if (moveType == GameUnfinished) flipView = !flipView;
9674             if (appData.debugMode)
9675               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9676         }
9677         break;
9678
9679       case (ChessMove) 0:       /* end of file */
9680         if (appData.debugMode)
9681           fprintf(debugFP, "Parser hit end of file\n");
9682         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9683           case MT_NONE:
9684           case MT_CHECK:
9685             break;
9686           case MT_CHECKMATE:
9687           case MT_STAINMATE:
9688             if (WhiteOnMove(currentMove)) {
9689                 GameEnds(BlackWins, "Black mates", GE_FILE);
9690             } else {
9691                 GameEnds(WhiteWins, "White mates", GE_FILE);
9692             }
9693             break;
9694           case MT_STALEMATE:
9695             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9696             break;
9697         }
9698         done = TRUE;
9699         break;
9700
9701       case MoveNumberOne:
9702         if (lastLoadGameStart == GNUChessGame) {
9703             /* GNUChessGames have numbers, but they aren't move numbers */
9704             if (appData.debugMode)
9705               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9706                       yy_text, (int) moveType);
9707             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9708         }
9709         /* else fall thru */
9710
9711       case XBoardGame:
9712       case GNUChessGame:
9713       case PGNTag:
9714         /* Reached start of next game in file */
9715         if (appData.debugMode)
9716           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9717         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9718           case MT_NONE:
9719           case MT_CHECK:
9720             break;
9721           case MT_CHECKMATE:
9722           case MT_STAINMATE:
9723             if (WhiteOnMove(currentMove)) {
9724                 GameEnds(BlackWins, "Black mates", GE_FILE);
9725             } else {
9726                 GameEnds(WhiteWins, "White mates", GE_FILE);
9727             }
9728             break;
9729           case MT_STALEMATE:
9730             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9731             break;
9732         }
9733         done = TRUE;
9734         break;
9735
9736       case PositionDiagram:     /* should not happen; ignore */
9737       case ElapsedTime:         /* ignore */
9738       case NAG:                 /* ignore */
9739         if (appData.debugMode)
9740           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9741                   yy_text, (int) moveType);
9742         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9743
9744       case IllegalMove:
9745         if (appData.testLegality) {
9746             if (appData.debugMode)
9747               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9748             sprintf(move, _("Illegal move: %d.%s%s"),
9749                     (forwardMostMove / 2) + 1,
9750                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9751             DisplayError(move, 0);
9752             done = TRUE;
9753         } else {
9754             if (appData.debugMode)
9755               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9756                       yy_text, currentMoveString);
9757             fromX = currentMoveString[0] - AAA;
9758             fromY = currentMoveString[1] - ONE;
9759             toX = currentMoveString[2] - AAA;
9760             toY = currentMoveString[3] - ONE;
9761             promoChar = currentMoveString[4];
9762         }
9763         break;
9764
9765       case AmbiguousMove:
9766         if (appData.debugMode)
9767           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9768         sprintf(move, _("Ambiguous move: %d.%s%s"),
9769                 (forwardMostMove / 2) + 1,
9770                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9771         DisplayError(move, 0);
9772         done = TRUE;
9773         break;
9774
9775       default:
9776       case ImpossibleMove:
9777         if (appData.debugMode)
9778           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9779         sprintf(move, _("Illegal move: %d.%s%s"),
9780                 (forwardMostMove / 2) + 1,
9781                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9782         DisplayError(move, 0);
9783         done = TRUE;
9784         break;
9785     }
9786
9787     if (done) {
9788         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9789             DrawPosition(FALSE, boards[currentMove]);
9790             DisplayBothClocks();
9791             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9792               DisplayComment(currentMove - 1, commentList[currentMove]);
9793         }
9794         (void) StopLoadGameTimer();
9795         gameFileFP = NULL;
9796         cmailOldMove = forwardMostMove;
9797         return FALSE;
9798     } else {
9799         /* currentMoveString is set as a side-effect of yylex */
9800         strcat(currentMoveString, "\n");
9801         strcpy(moveList[forwardMostMove], currentMoveString);
9802         
9803         thinkOutput[0] = NULLCHAR;
9804         MakeMove(fromX, fromY, toX, toY, promoChar);
9805         currentMove = forwardMostMove;
9806         return TRUE;
9807     }
9808 }
9809
9810 /* Load the nth game from the given file */
9811 int
9812 LoadGameFromFile(filename, n, title, useList)
9813      char *filename;
9814      int n;
9815      char *title;
9816      /*Boolean*/ int useList;
9817 {
9818     FILE *f;
9819     char buf[MSG_SIZ];
9820
9821     if (strcmp(filename, "-") == 0) {
9822         f = stdin;
9823         title = "stdin";
9824     } else {
9825         f = fopen(filename, "rb");
9826         if (f == NULL) {
9827           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9828             DisplayError(buf, errno);
9829             return FALSE;
9830         }
9831     }
9832     if (fseek(f, 0, 0) == -1) {
9833         /* f is not seekable; probably a pipe */
9834         useList = FALSE;
9835     }
9836     if (useList && n == 0) {
9837         int error = GameListBuild(f);
9838         if (error) {
9839             DisplayError(_("Cannot build game list"), error);
9840         } else if (!ListEmpty(&gameList) &&
9841                    ((ListGame *) gameList.tailPred)->number > 1) {
9842             GameListPopUp(f, title);
9843             return TRUE;
9844         }
9845         GameListDestroy();
9846         n = 1;
9847     }
9848     if (n == 0) n = 1;
9849     return LoadGame(f, n, title, FALSE);
9850 }
9851
9852
9853 void
9854 MakeRegisteredMove()
9855 {
9856     int fromX, fromY, toX, toY;
9857     char promoChar;
9858     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9859         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9860           case CMAIL_MOVE:
9861           case CMAIL_DRAW:
9862             if (appData.debugMode)
9863               fprintf(debugFP, "Restoring %s for game %d\n",
9864                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9865     
9866             thinkOutput[0] = NULLCHAR;
9867             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9868             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9869             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9870             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9871             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9872             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9873             MakeMove(fromX, fromY, toX, toY, promoChar);
9874             ShowMove(fromX, fromY, toX, toY);
9875               
9876             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9877               case MT_NONE:
9878               case MT_CHECK:
9879                 break;
9880                 
9881               case MT_CHECKMATE:
9882               case MT_STAINMATE:
9883                 if (WhiteOnMove(currentMove)) {
9884                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9885                 } else {
9886                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9887                 }
9888                 break;
9889                 
9890               case MT_STALEMATE:
9891                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9892                 break;
9893             }
9894
9895             break;
9896             
9897           case CMAIL_RESIGN:
9898             if (WhiteOnMove(currentMove)) {
9899                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9900             } else {
9901                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9902             }
9903             break;
9904             
9905           case CMAIL_ACCEPT:
9906             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9907             break;
9908               
9909           default:
9910             break;
9911         }
9912     }
9913
9914     return;
9915 }
9916
9917 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9918 int
9919 CmailLoadGame(f, gameNumber, title, useList)
9920      FILE *f;
9921      int gameNumber;
9922      char *title;
9923      int useList;
9924 {
9925     int retVal;
9926
9927     if (gameNumber > nCmailGames) {
9928         DisplayError(_("No more games in this message"), 0);
9929         return FALSE;
9930     }
9931     if (f == lastLoadGameFP) {
9932         int offset = gameNumber - lastLoadGameNumber;
9933         if (offset == 0) {
9934             cmailMsg[0] = NULLCHAR;
9935             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9936                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9937                 nCmailMovesRegistered--;
9938             }
9939             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9940             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9941                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9942             }
9943         } else {
9944             if (! RegisterMove()) return FALSE;
9945         }
9946     }
9947
9948     retVal = LoadGame(f, gameNumber, title, useList);
9949
9950     /* Make move registered during previous look at this game, if any */
9951     MakeRegisteredMove();
9952
9953     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9954         commentList[currentMove]
9955           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9956         DisplayComment(currentMove - 1, commentList[currentMove]);
9957     }
9958
9959     return retVal;
9960 }
9961
9962 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9963 int
9964 ReloadGame(offset)
9965      int offset;
9966 {
9967     int gameNumber = lastLoadGameNumber + offset;
9968     if (lastLoadGameFP == NULL) {
9969         DisplayError(_("No game has been loaded yet"), 0);
9970         return FALSE;
9971     }
9972     if (gameNumber <= 0) {
9973         DisplayError(_("Can't back up any further"), 0);
9974         return FALSE;
9975     }
9976     if (cmailMsgLoaded) {
9977         return CmailLoadGame(lastLoadGameFP, gameNumber,
9978                              lastLoadGameTitle, lastLoadGameUseList);
9979     } else {
9980         return LoadGame(lastLoadGameFP, gameNumber,
9981                         lastLoadGameTitle, lastLoadGameUseList);
9982     }
9983 }
9984
9985
9986
9987 /* Load the nth game from open file f */
9988 int
9989 LoadGame(f, gameNumber, title, useList)
9990      FILE *f;
9991      int gameNumber;
9992      char *title;
9993      int useList;
9994 {
9995     ChessMove cm;
9996     char buf[MSG_SIZ];
9997     int gn = gameNumber;
9998     ListGame *lg = NULL;
9999     int numPGNTags = 0;
10000     int err;
10001     GameMode oldGameMode;
10002     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10003
10004     if (appData.debugMode) 
10005         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10006
10007     if (gameMode == Training )
10008         SetTrainingModeOff();
10009
10010     oldGameMode = gameMode;
10011     if (gameMode != BeginningOfGame) {
10012       Reset(FALSE, TRUE);
10013     }
10014
10015     gameFileFP = f;
10016     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10017         fclose(lastLoadGameFP);
10018     }
10019
10020     if (useList) {
10021         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10022         
10023         if (lg) {
10024             fseek(f, lg->offset, 0);
10025             GameListHighlight(gameNumber);
10026             gn = 1;
10027         }
10028         else {
10029             DisplayError(_("Game number out of range"), 0);
10030             return FALSE;
10031         }
10032     } else {
10033         GameListDestroy();
10034         if (fseek(f, 0, 0) == -1) {
10035             if (f == lastLoadGameFP ?
10036                 gameNumber == lastLoadGameNumber + 1 :
10037                 gameNumber == 1) {
10038                 gn = 1;
10039             } else {
10040                 DisplayError(_("Can't seek on game file"), 0);
10041                 return FALSE;
10042             }
10043         }
10044     }
10045     lastLoadGameFP = f;
10046     lastLoadGameNumber = gameNumber;
10047     strcpy(lastLoadGameTitle, title);
10048     lastLoadGameUseList = useList;
10049
10050     yynewfile(f);
10051
10052     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10053       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10054                 lg->gameInfo.black);
10055             DisplayTitle(buf);
10056     } else if (*title != NULLCHAR) {
10057         if (gameNumber > 1) {
10058             sprintf(buf, "%s %d", title, gameNumber);
10059             DisplayTitle(buf);
10060         } else {
10061             DisplayTitle(title);
10062         }
10063     }
10064
10065     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10066         gameMode = PlayFromGameFile;
10067         ModeHighlight();
10068     }
10069
10070     currentMove = forwardMostMove = backwardMostMove = 0;
10071     CopyBoard(boards[0], initialPosition);
10072     StopClocks();
10073
10074     /*
10075      * Skip the first gn-1 games in the file.
10076      * Also skip over anything that precedes an identifiable 
10077      * start of game marker, to avoid being confused by 
10078      * garbage at the start of the file.  Currently 
10079      * recognized start of game markers are the move number "1",
10080      * the pattern "gnuchess .* game", the pattern
10081      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10082      * A game that starts with one of the latter two patterns
10083      * will also have a move number 1, possibly
10084      * following a position diagram.
10085      * 5-4-02: Let's try being more lenient and allowing a game to
10086      * start with an unnumbered move.  Does that break anything?
10087      */
10088     cm = lastLoadGameStart = (ChessMove) 0;
10089     while (gn > 0) {
10090         yyboardindex = forwardMostMove;
10091         cm = (ChessMove) yylex();
10092         switch (cm) {
10093           case (ChessMove) 0:
10094             if (cmailMsgLoaded) {
10095                 nCmailGames = CMAIL_MAX_GAMES - gn;
10096             } else {
10097                 Reset(TRUE, TRUE);
10098                 DisplayError(_("Game not found in file"), 0);
10099             }
10100             return FALSE;
10101
10102           case GNUChessGame:
10103           case XBoardGame:
10104             gn--;
10105             lastLoadGameStart = cm;
10106             break;
10107             
10108           case MoveNumberOne:
10109             switch (lastLoadGameStart) {
10110               case GNUChessGame:
10111               case XBoardGame:
10112               case PGNTag:
10113                 break;
10114               case MoveNumberOne:
10115               case (ChessMove) 0:
10116                 gn--;           /* count this game */
10117                 lastLoadGameStart = cm;
10118                 break;
10119               default:
10120                 /* impossible */
10121                 break;
10122             }
10123             break;
10124
10125           case PGNTag:
10126             switch (lastLoadGameStart) {
10127               case GNUChessGame:
10128               case PGNTag:
10129               case MoveNumberOne:
10130               case (ChessMove) 0:
10131                 gn--;           /* count this game */
10132                 lastLoadGameStart = cm;
10133                 break;
10134               case XBoardGame:
10135                 lastLoadGameStart = cm; /* game counted already */
10136                 break;
10137               default:
10138                 /* impossible */
10139                 break;
10140             }
10141             if (gn > 0) {
10142                 do {
10143                     yyboardindex = forwardMostMove;
10144                     cm = (ChessMove) yylex();
10145                 } while (cm == PGNTag || cm == Comment);
10146             }
10147             break;
10148
10149           case WhiteWins:
10150           case BlackWins:
10151           case GameIsDrawn:
10152             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10153                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10154                     != CMAIL_OLD_RESULT) {
10155                     nCmailResults ++ ;
10156                     cmailResult[  CMAIL_MAX_GAMES
10157                                 - gn - 1] = CMAIL_OLD_RESULT;
10158                 }
10159             }
10160             break;
10161
10162           case NormalMove:
10163             /* Only a NormalMove can be at the start of a game
10164              * without a position diagram. */
10165             if (lastLoadGameStart == (ChessMove) 0) {
10166               gn--;
10167               lastLoadGameStart = MoveNumberOne;
10168             }
10169             break;
10170
10171           default:
10172             break;
10173         }
10174     }
10175     
10176     if (appData.debugMode)
10177       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10178
10179     if (cm == XBoardGame) {
10180         /* Skip any header junk before position diagram and/or move 1 */
10181         for (;;) {
10182             yyboardindex = forwardMostMove;
10183             cm = (ChessMove) yylex();
10184
10185             if (cm == (ChessMove) 0 ||
10186                 cm == GNUChessGame || cm == XBoardGame) {
10187                 /* Empty game; pretend end-of-file and handle later */
10188                 cm = (ChessMove) 0;
10189                 break;
10190             }
10191
10192             if (cm == MoveNumberOne || cm == PositionDiagram ||
10193                 cm == PGNTag || cm == Comment)
10194               break;
10195         }
10196     } else if (cm == GNUChessGame) {
10197         if (gameInfo.event != NULL) {
10198             free(gameInfo.event);
10199         }
10200         gameInfo.event = StrSave(yy_text);
10201     }   
10202
10203     startedFromSetupPosition = FALSE;
10204     while (cm == PGNTag) {
10205         if (appData.debugMode) 
10206           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10207         err = ParsePGNTag(yy_text, &gameInfo);
10208         if (!err) numPGNTags++;
10209
10210         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10211         if(gameInfo.variant != oldVariant) {
10212             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10213             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10214             InitPosition(TRUE);
10215             oldVariant = gameInfo.variant;
10216             if (appData.debugMode) 
10217               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10218         }
10219
10220
10221         if (gameInfo.fen != NULL) {
10222           Board initial_position;
10223           startedFromSetupPosition = TRUE;
10224           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10225             Reset(TRUE, TRUE);
10226             DisplayError(_("Bad FEN position in file"), 0);
10227             return FALSE;
10228           }
10229           CopyBoard(boards[0], initial_position);
10230           if (blackPlaysFirst) {
10231             currentMove = forwardMostMove = backwardMostMove = 1;
10232             CopyBoard(boards[1], initial_position);
10233             strcpy(moveList[0], "");
10234             strcpy(parseList[0], "");
10235             timeRemaining[0][1] = whiteTimeRemaining;
10236             timeRemaining[1][1] = blackTimeRemaining;
10237             if (commentList[0] != NULL) {
10238               commentList[1] = commentList[0];
10239               commentList[0] = NULL;
10240             }
10241           } else {
10242             currentMove = forwardMostMove = backwardMostMove = 0;
10243           }
10244           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10245           {   int i;
10246               initialRulePlies = FENrulePlies;
10247               for( i=0; i< nrCastlingRights; i++ )
10248                   initialRights[i] = initial_position[CASTLING][i];
10249           }
10250           yyboardindex = forwardMostMove;
10251           free(gameInfo.fen);
10252           gameInfo.fen = NULL;
10253         }
10254
10255         yyboardindex = forwardMostMove;
10256         cm = (ChessMove) yylex();
10257
10258         /* Handle comments interspersed among the tags */
10259         while (cm == Comment) {
10260             char *p;
10261             if (appData.debugMode) 
10262               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10263             p = yy_text;
10264             AppendComment(currentMove, p, FALSE);
10265             yyboardindex = forwardMostMove;
10266             cm = (ChessMove) yylex();
10267         }
10268     }
10269
10270     /* don't rely on existence of Event tag since if game was
10271      * pasted from clipboard the Event tag may not exist
10272      */
10273     if (numPGNTags > 0){
10274         char *tags;
10275         if (gameInfo.variant == VariantNormal) {
10276           VariantClass v = StringToVariant(gameInfo.event);
10277           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10278           if(v < VariantShogi) gameInfo.variant = v;
10279         }
10280         if (!matchMode) {
10281           if( appData.autoDisplayTags ) {
10282             tags = PGNTags(&gameInfo);
10283             TagsPopUp(tags, CmailMsg());
10284             free(tags);
10285           }
10286         }
10287     } else {
10288         /* Make something up, but don't display it now */
10289         SetGameInfo();
10290         TagsPopDown();
10291     }
10292
10293     if (cm == PositionDiagram) {
10294         int i, j;
10295         char *p;
10296         Board initial_position;
10297
10298         if (appData.debugMode)
10299           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10300
10301         if (!startedFromSetupPosition) {
10302             p = yy_text;
10303             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10304               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10305                 switch (*p) {
10306                   case '[':
10307                   case '-':
10308                   case ' ':
10309                   case '\t':
10310                   case '\n':
10311                   case '\r':
10312                     break;
10313                   default:
10314                     initial_position[i][j++] = CharToPiece(*p);
10315                     break;
10316                 }
10317             while (*p == ' ' || *p == '\t' ||
10318                    *p == '\n' || *p == '\r') p++;
10319         
10320             if (strncmp(p, "black", strlen("black"))==0)
10321               blackPlaysFirst = TRUE;
10322             else
10323               blackPlaysFirst = FALSE;
10324             startedFromSetupPosition = TRUE;
10325         
10326             CopyBoard(boards[0], initial_position);
10327             if (blackPlaysFirst) {
10328                 currentMove = forwardMostMove = backwardMostMove = 1;
10329                 CopyBoard(boards[1], initial_position);
10330                 strcpy(moveList[0], "");
10331                 strcpy(parseList[0], "");
10332                 timeRemaining[0][1] = whiteTimeRemaining;
10333                 timeRemaining[1][1] = blackTimeRemaining;
10334                 if (commentList[0] != NULL) {
10335                     commentList[1] = commentList[0];
10336                     commentList[0] = NULL;
10337                 }
10338             } else {
10339                 currentMove = forwardMostMove = backwardMostMove = 0;
10340             }
10341         }
10342         yyboardindex = forwardMostMove;
10343         cm = (ChessMove) yylex();
10344     }
10345
10346     if (first.pr == NoProc) {
10347         StartChessProgram(&first);
10348     }
10349     InitChessProgram(&first, FALSE);
10350     SendToProgram("force\n", &first);
10351     if (startedFromSetupPosition) {
10352         SendBoard(&first, forwardMostMove);
10353     if (appData.debugMode) {
10354         fprintf(debugFP, "Load Game\n");
10355     }
10356         DisplayBothClocks();
10357     }      
10358
10359     /* [HGM] server: flag to write setup moves in broadcast file as one */
10360     loadFlag = appData.suppressLoadMoves;
10361
10362     while (cm == Comment) {
10363         char *p;
10364         if (appData.debugMode) 
10365           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10366         p = yy_text;
10367         AppendComment(currentMove, p, FALSE);
10368         yyboardindex = forwardMostMove;
10369         cm = (ChessMove) yylex();
10370     }
10371
10372     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10373         cm == WhiteWins || cm == BlackWins ||
10374         cm == GameIsDrawn || cm == GameUnfinished) {
10375         DisplayMessage("", _("No moves in game"));
10376         if (cmailMsgLoaded) {
10377             if (appData.debugMode)
10378               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10379             ClearHighlights();
10380             flipView = FALSE;
10381         }
10382         DrawPosition(FALSE, boards[currentMove]);
10383         DisplayBothClocks();
10384         gameMode = EditGame;
10385         ModeHighlight();
10386         gameFileFP = NULL;
10387         cmailOldMove = 0;
10388         return TRUE;
10389     }
10390
10391     // [HGM] PV info: routine tests if comment empty
10392     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10393         DisplayComment(currentMove - 1, commentList[currentMove]);
10394     }
10395     if (!matchMode && appData.timeDelay != 0) 
10396       DrawPosition(FALSE, boards[currentMove]);
10397
10398     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10399       programStats.ok_to_send = 1;
10400     }
10401
10402     /* if the first token after the PGN tags is a move
10403      * and not move number 1, retrieve it from the parser 
10404      */
10405     if (cm != MoveNumberOne)
10406         LoadGameOneMove(cm);
10407
10408     /* load the remaining moves from the file */
10409     while (LoadGameOneMove((ChessMove)0)) {
10410       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10411       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10412     }
10413
10414     /* rewind to the start of the game */
10415     currentMove = backwardMostMove;
10416
10417     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10418
10419     if (oldGameMode == AnalyzeFile ||
10420         oldGameMode == AnalyzeMode) {
10421       AnalyzeFileEvent();
10422     }
10423
10424     if (matchMode || appData.timeDelay == 0) {
10425       ToEndEvent();
10426       gameMode = EditGame;
10427       ModeHighlight();
10428     } else if (appData.timeDelay > 0) {
10429       AutoPlayGameLoop();
10430     }
10431
10432     if (appData.debugMode) 
10433         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10434
10435     loadFlag = 0; /* [HGM] true game starts */
10436     return TRUE;
10437 }
10438
10439 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10440 int
10441 ReloadPosition(offset)
10442      int offset;
10443 {
10444     int positionNumber = lastLoadPositionNumber + offset;
10445     if (lastLoadPositionFP == NULL) {
10446         DisplayError(_("No position has been loaded yet"), 0);
10447         return FALSE;
10448     }
10449     if (positionNumber <= 0) {
10450         DisplayError(_("Can't back up any further"), 0);
10451         return FALSE;
10452     }
10453     return LoadPosition(lastLoadPositionFP, positionNumber,
10454                         lastLoadPositionTitle);
10455 }
10456
10457 /* Load the nth position from the given file */
10458 int
10459 LoadPositionFromFile(filename, n, title)
10460      char *filename;
10461      int n;
10462      char *title;
10463 {
10464     FILE *f;
10465     char buf[MSG_SIZ];
10466
10467     if (strcmp(filename, "-") == 0) {
10468         return LoadPosition(stdin, n, "stdin");
10469     } else {
10470         f = fopen(filename, "rb");
10471         if (f == NULL) {
10472             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10473             DisplayError(buf, errno);
10474             return FALSE;
10475         } else {
10476             return LoadPosition(f, n, title);
10477         }
10478     }
10479 }
10480
10481 /* Load the nth position from the given open file, and close it */
10482 int
10483 LoadPosition(f, positionNumber, title)
10484      FILE *f;
10485      int positionNumber;
10486      char *title;
10487 {
10488     char *p, line[MSG_SIZ];
10489     Board initial_position;
10490     int i, j, fenMode, pn;
10491     
10492     if (gameMode == Training )
10493         SetTrainingModeOff();
10494
10495     if (gameMode != BeginningOfGame) {
10496         Reset(FALSE, TRUE);
10497     }
10498     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10499         fclose(lastLoadPositionFP);
10500     }
10501     if (positionNumber == 0) positionNumber = 1;
10502     lastLoadPositionFP = f;
10503     lastLoadPositionNumber = positionNumber;
10504     strcpy(lastLoadPositionTitle, title);
10505     if (first.pr == NoProc) {
10506       StartChessProgram(&first);
10507       InitChessProgram(&first, FALSE);
10508     }    
10509     pn = positionNumber;
10510     if (positionNumber < 0) {
10511         /* Negative position number means to seek to that byte offset */
10512         if (fseek(f, -positionNumber, 0) == -1) {
10513             DisplayError(_("Can't seek on position file"), 0);
10514             return FALSE;
10515         };
10516         pn = 1;
10517     } else {
10518         if (fseek(f, 0, 0) == -1) {
10519             if (f == lastLoadPositionFP ?
10520                 positionNumber == lastLoadPositionNumber + 1 :
10521                 positionNumber == 1) {
10522                 pn = 1;
10523             } else {
10524                 DisplayError(_("Can't seek on position file"), 0);
10525                 return FALSE;
10526             }
10527         }
10528     }
10529     /* See if this file is FEN or old-style xboard */
10530     if (fgets(line, MSG_SIZ, f) == NULL) {
10531         DisplayError(_("Position not found in file"), 0);
10532         return FALSE;
10533     }
10534     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10535     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10536
10537     if (pn >= 2) {
10538         if (fenMode || line[0] == '#') pn--;
10539         while (pn > 0) {
10540             /* skip positions before number pn */
10541             if (fgets(line, MSG_SIZ, f) == NULL) {
10542                 Reset(TRUE, TRUE);
10543                 DisplayError(_("Position not found in file"), 0);
10544                 return FALSE;
10545             }
10546             if (fenMode || line[0] == '#') pn--;
10547         }
10548     }
10549
10550     if (fenMode) {
10551         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10552             DisplayError(_("Bad FEN position in file"), 0);
10553             return FALSE;
10554         }
10555     } else {
10556         (void) fgets(line, MSG_SIZ, f);
10557         (void) fgets(line, MSG_SIZ, f);
10558     
10559         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10560             (void) fgets(line, MSG_SIZ, f);
10561             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10562                 if (*p == ' ')
10563                   continue;
10564                 initial_position[i][j++] = CharToPiece(*p);
10565             }
10566         }
10567     
10568         blackPlaysFirst = FALSE;
10569         if (!feof(f)) {
10570             (void) fgets(line, MSG_SIZ, f);
10571             if (strncmp(line, "black", strlen("black"))==0)
10572               blackPlaysFirst = TRUE;
10573         }
10574     }
10575     startedFromSetupPosition = TRUE;
10576     
10577     SendToProgram("force\n", &first);
10578     CopyBoard(boards[0], initial_position);
10579     if (blackPlaysFirst) {
10580         currentMove = forwardMostMove = backwardMostMove = 1;
10581         strcpy(moveList[0], "");
10582         strcpy(parseList[0], "");
10583         CopyBoard(boards[1], initial_position);
10584         DisplayMessage("", _("Black to play"));
10585     } else {
10586         currentMove = forwardMostMove = backwardMostMove = 0;
10587         DisplayMessage("", _("White to play"));
10588     }
10589     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10590     SendBoard(&first, forwardMostMove);
10591     if (appData.debugMode) {
10592 int i, j;
10593   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10594   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10595         fprintf(debugFP, "Load Position\n");
10596     }
10597
10598     if (positionNumber > 1) {
10599         sprintf(line, "%s %d", title, positionNumber);
10600         DisplayTitle(line);
10601     } else {
10602         DisplayTitle(title);
10603     }
10604     gameMode = EditGame;
10605     ModeHighlight();
10606     ResetClocks();
10607     timeRemaining[0][1] = whiteTimeRemaining;
10608     timeRemaining[1][1] = blackTimeRemaining;
10609     DrawPosition(FALSE, boards[currentMove]);
10610    
10611     return TRUE;
10612 }
10613
10614
10615 void
10616 CopyPlayerNameIntoFileName(dest, src)
10617      char **dest, *src;
10618 {
10619     while (*src != NULLCHAR && *src != ',') {
10620         if (*src == ' ') {
10621             *(*dest)++ = '_';
10622             src++;
10623         } else {
10624             *(*dest)++ = *src++;
10625         }
10626     }
10627 }
10628
10629 char *DefaultFileName(ext)
10630      char *ext;
10631 {
10632     static char def[MSG_SIZ];
10633     char *p;
10634
10635     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10636         p = def;
10637         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10638         *p++ = '-';
10639         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10640         *p++ = '.';
10641         strcpy(p, ext);
10642     } else {
10643         def[0] = NULLCHAR;
10644     }
10645     return def;
10646 }
10647
10648 /* Save the current game to the given file */
10649 int
10650 SaveGameToFile(filename, append)
10651      char *filename;
10652      int append;
10653 {
10654     FILE *f;
10655     char buf[MSG_SIZ];
10656
10657     if (strcmp(filename, "-") == 0) {
10658         return SaveGame(stdout, 0, NULL);
10659     } else {
10660         f = fopen(filename, append ? "a" : "w");
10661         if (f == NULL) {
10662             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10663             DisplayError(buf, errno);
10664             return FALSE;
10665         } else {
10666             return SaveGame(f, 0, NULL);
10667         }
10668     }
10669 }
10670
10671 char *
10672 SavePart(str)
10673      char *str;
10674 {
10675     static char buf[MSG_SIZ];
10676     char *p;
10677     
10678     p = strchr(str, ' ');
10679     if (p == NULL) return str;
10680     strncpy(buf, str, p - str);
10681     buf[p - str] = NULLCHAR;
10682     return buf;
10683 }
10684
10685 #define PGN_MAX_LINE 75
10686
10687 #define PGN_SIDE_WHITE  0
10688 #define PGN_SIDE_BLACK  1
10689
10690 /* [AS] */
10691 static int FindFirstMoveOutOfBook( int side )
10692 {
10693     int result = -1;
10694
10695     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10696         int index = backwardMostMove;
10697         int has_book_hit = 0;
10698
10699         if( (index % 2) != side ) {
10700             index++;
10701         }
10702
10703         while( index < forwardMostMove ) {
10704             /* Check to see if engine is in book */
10705             int depth = pvInfoList[index].depth;
10706             int score = pvInfoList[index].score;
10707             int in_book = 0;
10708
10709             if( depth <= 2 ) {
10710                 in_book = 1;
10711             }
10712             else if( score == 0 && depth == 63 ) {
10713                 in_book = 1; /* Zappa */
10714             }
10715             else if( score == 2 && depth == 99 ) {
10716                 in_book = 1; /* Abrok */
10717             }
10718
10719             has_book_hit += in_book;
10720
10721             if( ! in_book ) {
10722                 result = index;
10723
10724                 break;
10725             }
10726
10727             index += 2;
10728         }
10729     }
10730
10731     return result;
10732 }
10733
10734 /* [AS] */
10735 void GetOutOfBookInfo( char * buf )
10736 {
10737     int oob[2];
10738     int i;
10739     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10740
10741     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10742     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10743
10744     *buf = '\0';
10745
10746     if( oob[0] >= 0 || oob[1] >= 0 ) {
10747         for( i=0; i<2; i++ ) {
10748             int idx = oob[i];
10749
10750             if( idx >= 0 ) {
10751                 if( i > 0 && oob[0] >= 0 ) {
10752                     strcat( buf, "   " );
10753                 }
10754
10755                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10756                 sprintf( buf+strlen(buf), "%s%.2f", 
10757                     pvInfoList[idx].score >= 0 ? "+" : "",
10758                     pvInfoList[idx].score / 100.0 );
10759             }
10760         }
10761     }
10762 }
10763
10764 /* Save game in PGN style and close the file */
10765 int
10766 SaveGamePGN(f)
10767      FILE *f;
10768 {
10769     int i, offset, linelen, newblock;
10770     time_t tm;
10771 //    char *movetext;
10772     char numtext[32];
10773     int movelen, numlen, blank;
10774     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10775
10776     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10777     
10778     tm = time((time_t *) NULL);
10779     
10780     PrintPGNTags(f, &gameInfo);
10781     
10782     if (backwardMostMove > 0 || startedFromSetupPosition) {
10783         char *fen = PositionToFEN(backwardMostMove, NULL);
10784         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10785         fprintf(f, "\n{--------------\n");
10786         PrintPosition(f, backwardMostMove);
10787         fprintf(f, "--------------}\n");
10788         free(fen);
10789     }
10790     else {
10791         /* [AS] Out of book annotation */
10792         if( appData.saveOutOfBookInfo ) {
10793             char buf[64];
10794
10795             GetOutOfBookInfo( buf );
10796
10797             if( buf[0] != '\0' ) {
10798                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10799             }
10800         }
10801
10802         fprintf(f, "\n");
10803     }
10804
10805     i = backwardMostMove;
10806     linelen = 0;
10807     newblock = TRUE;
10808
10809     while (i < forwardMostMove) {
10810         /* Print comments preceding this move */
10811         if (commentList[i] != NULL) {
10812             if (linelen > 0) fprintf(f, "\n");
10813             fprintf(f, "%s", commentList[i]);
10814             linelen = 0;
10815             newblock = TRUE;
10816         }
10817
10818         /* Format move number */
10819         if ((i % 2) == 0) {
10820             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10821         } else {
10822             if (newblock) {
10823                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10824             } else {
10825                 numtext[0] = NULLCHAR;
10826             }
10827         }
10828         numlen = strlen(numtext);
10829         newblock = FALSE;
10830
10831         /* Print move number */
10832         blank = linelen > 0 && numlen > 0;
10833         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10834             fprintf(f, "\n");
10835             linelen = 0;
10836             blank = 0;
10837         }
10838         if (blank) {
10839             fprintf(f, " ");
10840             linelen++;
10841         }
10842         fprintf(f, "%s", numtext);
10843         linelen += numlen;
10844
10845         /* Get move */
10846         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10847         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10848
10849         /* Print move */
10850         blank = linelen > 0 && movelen > 0;
10851         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10852             fprintf(f, "\n");
10853             linelen = 0;
10854             blank = 0;
10855         }
10856         if (blank) {
10857             fprintf(f, " ");
10858             linelen++;
10859         }
10860         fprintf(f, "%s", move_buffer);
10861         linelen += movelen;
10862
10863         /* [AS] Add PV info if present */
10864         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10865             /* [HGM] add time */
10866             char buf[MSG_SIZ]; int seconds;
10867
10868             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10869
10870             if( seconds <= 0) buf[0] = 0; else
10871             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10872                 seconds = (seconds + 4)/10; // round to full seconds
10873                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10874                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10875             }
10876
10877             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10878                 pvInfoList[i].score >= 0 ? "+" : "",
10879                 pvInfoList[i].score / 100.0,
10880                 pvInfoList[i].depth,
10881                 buf );
10882
10883             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10884
10885             /* Print score/depth */
10886             blank = linelen > 0 && movelen > 0;
10887             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10888                 fprintf(f, "\n");
10889                 linelen = 0;
10890                 blank = 0;
10891             }
10892             if (blank) {
10893                 fprintf(f, " ");
10894                 linelen++;
10895             }
10896             fprintf(f, "%s", move_buffer);
10897             linelen += movelen;
10898         }
10899
10900         i++;
10901     }
10902     
10903     /* Start a new line */
10904     if (linelen > 0) fprintf(f, "\n");
10905
10906     /* Print comments after last move */
10907     if (commentList[i] != NULL) {
10908         fprintf(f, "%s\n", commentList[i]);
10909     }
10910
10911     /* Print result */
10912     if (gameInfo.resultDetails != NULL &&
10913         gameInfo.resultDetails[0] != NULLCHAR) {
10914         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10915                 PGNResult(gameInfo.result));
10916     } else {
10917         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10918     }
10919
10920     fclose(f);
10921     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10922     return TRUE;
10923 }
10924
10925 /* Save game in old style and close the file */
10926 int
10927 SaveGameOldStyle(f)
10928      FILE *f;
10929 {
10930     int i, offset;
10931     time_t tm;
10932     
10933     tm = time((time_t *) NULL);
10934     
10935     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10936     PrintOpponents(f);
10937     
10938     if (backwardMostMove > 0 || startedFromSetupPosition) {
10939         fprintf(f, "\n[--------------\n");
10940         PrintPosition(f, backwardMostMove);
10941         fprintf(f, "--------------]\n");
10942     } else {
10943         fprintf(f, "\n");
10944     }
10945
10946     i = backwardMostMove;
10947     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10948
10949     while (i < forwardMostMove) {
10950         if (commentList[i] != NULL) {
10951             fprintf(f, "[%s]\n", commentList[i]);
10952         }
10953
10954         if ((i % 2) == 1) {
10955             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10956             i++;
10957         } else {
10958             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10959             i++;
10960             if (commentList[i] != NULL) {
10961                 fprintf(f, "\n");
10962                 continue;
10963             }
10964             if (i >= forwardMostMove) {
10965                 fprintf(f, "\n");
10966                 break;
10967             }
10968             fprintf(f, "%s\n", parseList[i]);
10969             i++;
10970         }
10971     }
10972     
10973     if (commentList[i] != NULL) {
10974         fprintf(f, "[%s]\n", commentList[i]);
10975     }
10976
10977     /* This isn't really the old style, but it's close enough */
10978     if (gameInfo.resultDetails != NULL &&
10979         gameInfo.resultDetails[0] != NULLCHAR) {
10980         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10981                 gameInfo.resultDetails);
10982     } else {
10983         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10984     }
10985
10986     fclose(f);
10987     return TRUE;
10988 }
10989
10990 /* Save the current game to open file f and close the file */
10991 int
10992 SaveGame(f, dummy, dummy2)
10993      FILE *f;
10994      int dummy;
10995      char *dummy2;
10996 {
10997     if (gameMode == EditPosition) EditPositionDone(TRUE);
10998     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10999     if (appData.oldSaveStyle)
11000       return SaveGameOldStyle(f);
11001     else
11002       return SaveGamePGN(f);
11003 }
11004
11005 /* Save the current position to the given file */
11006 int
11007 SavePositionToFile(filename)
11008      char *filename;
11009 {
11010     FILE *f;
11011     char buf[MSG_SIZ];
11012
11013     if (strcmp(filename, "-") == 0) {
11014         return SavePosition(stdout, 0, NULL);
11015     } else {
11016         f = fopen(filename, "a");
11017         if (f == NULL) {
11018             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11019             DisplayError(buf, errno);
11020             return FALSE;
11021         } else {
11022             SavePosition(f, 0, NULL);
11023             return TRUE;
11024         }
11025     }
11026 }
11027
11028 /* Save the current position to the given open file and close the file */
11029 int
11030 SavePosition(f, dummy, dummy2)
11031      FILE *f;
11032      int dummy;
11033      char *dummy2;
11034 {
11035     time_t tm;
11036     char *fen;
11037     
11038     if (gameMode == EditPosition) EditPositionDone(TRUE);
11039     if (appData.oldSaveStyle) {
11040         tm = time((time_t *) NULL);
11041     
11042         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11043         PrintOpponents(f);
11044         fprintf(f, "[--------------\n");
11045         PrintPosition(f, currentMove);
11046         fprintf(f, "--------------]\n");
11047     } else {
11048         fen = PositionToFEN(currentMove, NULL);
11049         fprintf(f, "%s\n", fen);
11050         free(fen);
11051     }
11052     fclose(f);
11053     return TRUE;
11054 }
11055
11056 void
11057 ReloadCmailMsgEvent(unregister)
11058      int unregister;
11059 {
11060 #if !WIN32
11061     static char *inFilename = NULL;
11062     static char *outFilename;
11063     int i;
11064     struct stat inbuf, outbuf;
11065     int status;
11066     
11067     /* Any registered moves are unregistered if unregister is set, */
11068     /* i.e. invoked by the signal handler */
11069     if (unregister) {
11070         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11071             cmailMoveRegistered[i] = FALSE;
11072             if (cmailCommentList[i] != NULL) {
11073                 free(cmailCommentList[i]);
11074                 cmailCommentList[i] = NULL;
11075             }
11076         }
11077         nCmailMovesRegistered = 0;
11078     }
11079
11080     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11081         cmailResult[i] = CMAIL_NOT_RESULT;
11082     }
11083     nCmailResults = 0;
11084
11085     if (inFilename == NULL) {
11086         /* Because the filenames are static they only get malloced once  */
11087         /* and they never get freed                                      */
11088         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11089         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11090
11091         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11092         sprintf(outFilename, "%s.out", appData.cmailGameName);
11093     }
11094     
11095     status = stat(outFilename, &outbuf);
11096     if (status < 0) {
11097         cmailMailedMove = FALSE;
11098     } else {
11099         status = stat(inFilename, &inbuf);
11100         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11101     }
11102     
11103     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11104        counts the games, notes how each one terminated, etc.
11105        
11106        It would be nice to remove this kludge and instead gather all
11107        the information while building the game list.  (And to keep it
11108        in the game list nodes instead of having a bunch of fixed-size
11109        parallel arrays.)  Note this will require getting each game's
11110        termination from the PGN tags, as the game list builder does
11111        not process the game moves.  --mann
11112        */
11113     cmailMsgLoaded = TRUE;
11114     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11115     
11116     /* Load first game in the file or popup game menu */
11117     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11118
11119 #endif /* !WIN32 */
11120     return;
11121 }
11122
11123 int
11124 RegisterMove()
11125 {
11126     FILE *f;
11127     char string[MSG_SIZ];
11128
11129     if (   cmailMailedMove
11130         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11131         return TRUE;            /* Allow free viewing  */
11132     }
11133
11134     /* Unregister move to ensure that we don't leave RegisterMove        */
11135     /* with the move registered when the conditions for registering no   */
11136     /* longer hold                                                       */
11137     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11138         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11139         nCmailMovesRegistered --;
11140
11141         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11142           {
11143               free(cmailCommentList[lastLoadGameNumber - 1]);
11144               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11145           }
11146     }
11147
11148     if (cmailOldMove == -1) {
11149         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11150         return FALSE;
11151     }
11152
11153     if (currentMove > cmailOldMove + 1) {
11154         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11155         return FALSE;
11156     }
11157
11158     if (currentMove < cmailOldMove) {
11159         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11160         return FALSE;
11161     }
11162
11163     if (forwardMostMove > currentMove) {
11164         /* Silently truncate extra moves */
11165         TruncateGame();
11166     }
11167
11168     if (   (currentMove == cmailOldMove + 1)
11169         || (   (currentMove == cmailOldMove)
11170             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11171                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11172         if (gameInfo.result != GameUnfinished) {
11173             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11174         }
11175
11176         if (commentList[currentMove] != NULL) {
11177             cmailCommentList[lastLoadGameNumber - 1]
11178               = StrSave(commentList[currentMove]);
11179         }
11180         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11181
11182         if (appData.debugMode)
11183           fprintf(debugFP, "Saving %s for game %d\n",
11184                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11185
11186         sprintf(string,
11187                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11188         
11189         f = fopen(string, "w");
11190         if (appData.oldSaveStyle) {
11191             SaveGameOldStyle(f); /* also closes the file */
11192             
11193             sprintf(string, "%s.pos.out", appData.cmailGameName);
11194             f = fopen(string, "w");
11195             SavePosition(f, 0, NULL); /* also closes the file */
11196         } else {
11197             fprintf(f, "{--------------\n");
11198             PrintPosition(f, currentMove);
11199             fprintf(f, "--------------}\n\n");
11200             
11201             SaveGame(f, 0, NULL); /* also closes the file*/
11202         }
11203         
11204         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11205         nCmailMovesRegistered ++;
11206     } else if (nCmailGames == 1) {
11207         DisplayError(_("You have not made a move yet"), 0);
11208         return FALSE;
11209     }
11210
11211     return TRUE;
11212 }
11213
11214 void
11215 MailMoveEvent()
11216 {
11217 #if !WIN32
11218     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11219     FILE *commandOutput;
11220     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11221     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11222     int nBuffers;
11223     int i;
11224     int archived;
11225     char *arcDir;
11226
11227     if (! cmailMsgLoaded) {
11228         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11229         return;
11230     }
11231
11232     if (nCmailGames == nCmailResults) {
11233         DisplayError(_("No unfinished games"), 0);
11234         return;
11235     }
11236
11237 #if CMAIL_PROHIBIT_REMAIL
11238     if (cmailMailedMove) {
11239         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);
11240         DisplayError(msg, 0);
11241         return;
11242     }
11243 #endif
11244
11245     if (! (cmailMailedMove || RegisterMove())) return;
11246     
11247     if (   cmailMailedMove
11248         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11249         sprintf(string, partCommandString,
11250                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11251         commandOutput = popen(string, "r");
11252
11253         if (commandOutput == NULL) {
11254             DisplayError(_("Failed to invoke cmail"), 0);
11255         } else {
11256             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11257                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11258             }
11259             if (nBuffers > 1) {
11260                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11261                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11262                 nBytes = MSG_SIZ - 1;
11263             } else {
11264                 (void) memcpy(msg, buffer, nBytes);
11265             }
11266             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11267
11268             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11269                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11270
11271                 archived = TRUE;
11272                 for (i = 0; i < nCmailGames; i ++) {
11273                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11274                         archived = FALSE;
11275                     }
11276                 }
11277                 if (   archived
11278                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11279                         != NULL)) {
11280                     sprintf(buffer, "%s/%s.%s.archive",
11281                             arcDir,
11282                             appData.cmailGameName,
11283                             gameInfo.date);
11284                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11285                     cmailMsgLoaded = FALSE;
11286                 }
11287             }
11288
11289             DisplayInformation(msg);
11290             pclose(commandOutput);
11291         }
11292     } else {
11293         if ((*cmailMsg) != '\0') {
11294             DisplayInformation(cmailMsg);
11295         }
11296     }
11297
11298     return;
11299 #endif /* !WIN32 */
11300 }
11301
11302 char *
11303 CmailMsg()
11304 {
11305 #if WIN32
11306     return NULL;
11307 #else
11308     int  prependComma = 0;
11309     char number[5];
11310     char string[MSG_SIZ];       /* Space for game-list */
11311     int  i;
11312     
11313     if (!cmailMsgLoaded) return "";
11314
11315     if (cmailMailedMove) {
11316         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11317     } else {
11318         /* Create a list of games left */
11319         sprintf(string, "[");
11320         for (i = 0; i < nCmailGames; i ++) {
11321             if (! (   cmailMoveRegistered[i]
11322                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11323                 if (prependComma) {
11324                     sprintf(number, ",%d", i + 1);
11325                 } else {
11326                     sprintf(number, "%d", i + 1);
11327                     prependComma = 1;
11328                 }
11329                 
11330                 strcat(string, number);
11331             }
11332         }
11333         strcat(string, "]");
11334
11335         if (nCmailMovesRegistered + nCmailResults == 0) {
11336             switch (nCmailGames) {
11337               case 1:
11338                 sprintf(cmailMsg,
11339                         _("Still need to make move for game\n"));
11340                 break;
11341                 
11342               case 2:
11343                 sprintf(cmailMsg,
11344                         _("Still need to make moves for both games\n"));
11345                 break;
11346                 
11347               default:
11348                 sprintf(cmailMsg,
11349                         _("Still need to make moves for all %d games\n"),
11350                         nCmailGames);
11351                 break;
11352             }
11353         } else {
11354             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11355               case 1:
11356                 sprintf(cmailMsg,
11357                         _("Still need to make a move for game %s\n"),
11358                         string);
11359                 break;
11360                 
11361               case 0:
11362                 if (nCmailResults == nCmailGames) {
11363                     sprintf(cmailMsg, _("No unfinished games\n"));
11364                 } else {
11365                     sprintf(cmailMsg, _("Ready to send mail\n"));
11366                 }
11367                 break;
11368                 
11369               default:
11370                 sprintf(cmailMsg,
11371                         _("Still need to make moves for games %s\n"),
11372                         string);
11373             }
11374         }
11375     }
11376     return cmailMsg;
11377 #endif /* WIN32 */
11378 }
11379
11380 void
11381 ResetGameEvent()
11382 {
11383     if (gameMode == Training)
11384       SetTrainingModeOff();
11385
11386     Reset(TRUE, TRUE);
11387     cmailMsgLoaded = FALSE;
11388     if (appData.icsActive) {
11389       SendToICS(ics_prefix);
11390       SendToICS("refresh\n");
11391     }
11392 }
11393
11394 void
11395 ExitEvent(status)
11396      int status;
11397 {
11398     exiting++;
11399     if (exiting > 2) {
11400       /* Give up on clean exit */
11401       exit(status);
11402     }
11403     if (exiting > 1) {
11404       /* Keep trying for clean exit */
11405       return;
11406     }
11407
11408     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11409
11410     if (telnetISR != NULL) {
11411       RemoveInputSource(telnetISR);
11412     }
11413     if (icsPR != NoProc) {
11414       DestroyChildProcess(icsPR, TRUE);
11415     }
11416
11417     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11418     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11419
11420     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11421     /* make sure this other one finishes before killing it!                  */
11422     if(endingGame) { int count = 0;
11423         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11424         while(endingGame && count++ < 10) DoSleep(1);
11425         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11426     }
11427
11428     /* Kill off chess programs */
11429     if (first.pr != NoProc) {
11430         ExitAnalyzeMode();
11431         
11432         DoSleep( appData.delayBeforeQuit );
11433         SendToProgram("quit\n", &first);
11434         DoSleep( appData.delayAfterQuit );
11435         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11436     }
11437     if (second.pr != NoProc) {
11438         DoSleep( appData.delayBeforeQuit );
11439         SendToProgram("quit\n", &second);
11440         DoSleep( appData.delayAfterQuit );
11441         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11442     }
11443     if (first.isr != NULL) {
11444         RemoveInputSource(first.isr);
11445     }
11446     if (second.isr != NULL) {
11447         RemoveInputSource(second.isr);
11448     }
11449
11450     ShutDownFrontEnd();
11451     exit(status);
11452 }
11453
11454 void
11455 PauseEvent()
11456 {
11457     if (appData.debugMode)
11458         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11459     if (pausing) {
11460         pausing = FALSE;
11461         ModeHighlight();
11462         if (gameMode == MachinePlaysWhite ||
11463             gameMode == MachinePlaysBlack) {
11464             StartClocks();
11465         } else {
11466             DisplayBothClocks();
11467         }
11468         if (gameMode == PlayFromGameFile) {
11469             if (appData.timeDelay >= 0) 
11470                 AutoPlayGameLoop();
11471         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11472             Reset(FALSE, TRUE);
11473             SendToICS(ics_prefix);
11474             SendToICS("refresh\n");
11475         } else if (currentMove < forwardMostMove) {
11476             ForwardInner(forwardMostMove);
11477         }
11478         pauseExamInvalid = FALSE;
11479     } else {
11480         switch (gameMode) {
11481           default:
11482             return;
11483           case IcsExamining:
11484             pauseExamForwardMostMove = forwardMostMove;
11485             pauseExamInvalid = FALSE;
11486             /* fall through */
11487           case IcsObserving:
11488           case IcsPlayingWhite:
11489           case IcsPlayingBlack:
11490             pausing = TRUE;
11491             ModeHighlight();
11492             return;
11493           case PlayFromGameFile:
11494             (void) StopLoadGameTimer();
11495             pausing = TRUE;
11496             ModeHighlight();
11497             break;
11498           case BeginningOfGame:
11499             if (appData.icsActive) return;
11500             /* else fall through */
11501           case MachinePlaysWhite:
11502           case MachinePlaysBlack:
11503           case TwoMachinesPlay:
11504             if (forwardMostMove == 0)
11505               return;           /* don't pause if no one has moved */
11506             if ((gameMode == MachinePlaysWhite &&
11507                  !WhiteOnMove(forwardMostMove)) ||
11508                 (gameMode == MachinePlaysBlack &&
11509                  WhiteOnMove(forwardMostMove))) {
11510                 StopClocks();
11511             }
11512             pausing = TRUE;
11513             ModeHighlight();
11514             break;
11515         }
11516     }
11517 }
11518
11519 void
11520 EditCommentEvent()
11521 {
11522     char title[MSG_SIZ];
11523
11524     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11525         strcpy(title, _("Edit comment"));
11526     } else {
11527         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11528                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11529                 parseList[currentMove - 1]);
11530     }
11531
11532     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11533 }
11534
11535
11536 void
11537 EditTagsEvent()
11538 {
11539     char *tags = PGNTags(&gameInfo);
11540     EditTagsPopUp(tags);
11541     free(tags);
11542 }
11543
11544 void
11545 AnalyzeModeEvent()
11546 {
11547     if (appData.noChessProgram || gameMode == AnalyzeMode)
11548       return;
11549
11550     if (gameMode != AnalyzeFile) {
11551         if (!appData.icsEngineAnalyze) {
11552                EditGameEvent();
11553                if (gameMode != EditGame) return;
11554         }
11555         ResurrectChessProgram();
11556         SendToProgram("analyze\n", &first);
11557         first.analyzing = TRUE;
11558         /*first.maybeThinking = TRUE;*/
11559         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11560         EngineOutputPopUp();
11561     }
11562     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11563     pausing = FALSE;
11564     ModeHighlight();
11565     SetGameInfo();
11566
11567     StartAnalysisClock();
11568     GetTimeMark(&lastNodeCountTime);
11569     lastNodeCount = 0;
11570 }
11571
11572 void
11573 AnalyzeFileEvent()
11574 {
11575     if (appData.noChessProgram || gameMode == AnalyzeFile)
11576       return;
11577
11578     if (gameMode != AnalyzeMode) {
11579         EditGameEvent();
11580         if (gameMode != EditGame) return;
11581         ResurrectChessProgram();
11582         SendToProgram("analyze\n", &first);
11583         first.analyzing = TRUE;
11584         /*first.maybeThinking = TRUE;*/
11585         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11586         EngineOutputPopUp();
11587     }
11588     gameMode = AnalyzeFile;
11589     pausing = FALSE;
11590     ModeHighlight();
11591     SetGameInfo();
11592
11593     StartAnalysisClock();
11594     GetTimeMark(&lastNodeCountTime);
11595     lastNodeCount = 0;
11596 }
11597
11598 void
11599 MachineWhiteEvent()
11600 {
11601     char buf[MSG_SIZ];
11602     char *bookHit = NULL;
11603
11604     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11605       return;
11606
11607
11608     if (gameMode == PlayFromGameFile || 
11609         gameMode == TwoMachinesPlay  || 
11610         gameMode == Training         || 
11611         gameMode == AnalyzeMode      || 
11612         gameMode == EndOfGame)
11613         EditGameEvent();
11614
11615     if (gameMode == EditPosition) 
11616         EditPositionDone(TRUE);
11617
11618     if (!WhiteOnMove(currentMove)) {
11619         DisplayError(_("It is not White's turn"), 0);
11620         return;
11621     }
11622   
11623     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11624       ExitAnalyzeMode();
11625
11626     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11627         gameMode == AnalyzeFile)
11628         TruncateGame();
11629
11630     ResurrectChessProgram();    /* in case it isn't running */
11631     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11632         gameMode = MachinePlaysWhite;
11633         ResetClocks();
11634     } else
11635     gameMode = MachinePlaysWhite;
11636     pausing = FALSE;
11637     ModeHighlight();
11638     SetGameInfo();
11639     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11640     DisplayTitle(buf);
11641     if (first.sendName) {
11642       sprintf(buf, "name %s\n", gameInfo.black);
11643       SendToProgram(buf, &first);
11644     }
11645     if (first.sendTime) {
11646       if (first.useColors) {
11647         SendToProgram("black\n", &first); /*gnu kludge*/
11648       }
11649       SendTimeRemaining(&first, TRUE);
11650     }
11651     if (first.useColors) {
11652       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11653     }
11654     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11655     SetMachineThinkingEnables();
11656     first.maybeThinking = TRUE;
11657     StartClocks();
11658     firstMove = FALSE;
11659
11660     if (appData.autoFlipView && !flipView) {
11661       flipView = !flipView;
11662       DrawPosition(FALSE, NULL);
11663       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11664     }
11665
11666     if(bookHit) { // [HGM] book: simulate book reply
11667         static char bookMove[MSG_SIZ]; // a bit generous?
11668
11669         programStats.nodes = programStats.depth = programStats.time = 
11670         programStats.score = programStats.got_only_move = 0;
11671         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11672
11673         strcpy(bookMove, "move ");
11674         strcat(bookMove, bookHit);
11675         HandleMachineMove(bookMove, &first);
11676     }
11677 }
11678
11679 void
11680 MachineBlackEvent()
11681 {
11682     char buf[MSG_SIZ];
11683    char *bookHit = NULL;
11684
11685     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11686         return;
11687
11688
11689     if (gameMode == PlayFromGameFile || 
11690         gameMode == TwoMachinesPlay  || 
11691         gameMode == Training         || 
11692         gameMode == AnalyzeMode      || 
11693         gameMode == EndOfGame)
11694         EditGameEvent();
11695
11696     if (gameMode == EditPosition) 
11697         EditPositionDone(TRUE);
11698
11699     if (WhiteOnMove(currentMove)) {
11700         DisplayError(_("It is not Black's turn"), 0);
11701         return;
11702     }
11703     
11704     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11705       ExitAnalyzeMode();
11706
11707     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11708         gameMode == AnalyzeFile)
11709         TruncateGame();
11710
11711     ResurrectChessProgram();    /* in case it isn't running */
11712     gameMode = MachinePlaysBlack;
11713     pausing = FALSE;
11714     ModeHighlight();
11715     SetGameInfo();
11716     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11717     DisplayTitle(buf);
11718     if (first.sendName) {
11719       sprintf(buf, "name %s\n", gameInfo.white);
11720       SendToProgram(buf, &first);
11721     }
11722     if (first.sendTime) {
11723       if (first.useColors) {
11724         SendToProgram("white\n", &first); /*gnu kludge*/
11725       }
11726       SendTimeRemaining(&first, FALSE);
11727     }
11728     if (first.useColors) {
11729       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11730     }
11731     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11732     SetMachineThinkingEnables();
11733     first.maybeThinking = TRUE;
11734     StartClocks();
11735
11736     if (appData.autoFlipView && flipView) {
11737       flipView = !flipView;
11738       DrawPosition(FALSE, NULL);
11739       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11740     }
11741     if(bookHit) { // [HGM] book: simulate book reply
11742         static char bookMove[MSG_SIZ]; // a bit generous?
11743
11744         programStats.nodes = programStats.depth = programStats.time = 
11745         programStats.score = programStats.got_only_move = 0;
11746         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11747
11748         strcpy(bookMove, "move ");
11749         strcat(bookMove, bookHit);
11750         HandleMachineMove(bookMove, &first);
11751     }
11752 }
11753
11754
11755 void
11756 DisplayTwoMachinesTitle()
11757 {
11758     char buf[MSG_SIZ];
11759     if (appData.matchGames > 0) {
11760         if (first.twoMachinesColor[0] == 'w') {
11761             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11762                     gameInfo.white, gameInfo.black,
11763                     first.matchWins, second.matchWins,
11764                     matchGame - 1 - (first.matchWins + second.matchWins));
11765         } else {
11766             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11767                     gameInfo.white, gameInfo.black,
11768                     second.matchWins, first.matchWins,
11769                     matchGame - 1 - (first.matchWins + second.matchWins));
11770         }
11771     } else {
11772         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11773     }
11774     DisplayTitle(buf);
11775 }
11776
11777 void
11778 TwoMachinesEvent P((void))
11779 {
11780     int i;
11781     char buf[MSG_SIZ];
11782     ChessProgramState *onmove;
11783     char *bookHit = NULL;
11784     
11785     if (appData.noChessProgram) return;
11786
11787     switch (gameMode) {
11788       case TwoMachinesPlay:
11789         return;
11790       case MachinePlaysWhite:
11791       case MachinePlaysBlack:
11792         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11793             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11794             return;
11795         }
11796         /* fall through */
11797       case BeginningOfGame:
11798       case PlayFromGameFile:
11799       case EndOfGame:
11800         EditGameEvent();
11801         if (gameMode != EditGame) return;
11802         break;
11803       case EditPosition:
11804         EditPositionDone(TRUE);
11805         break;
11806       case AnalyzeMode:
11807       case AnalyzeFile:
11808         ExitAnalyzeMode();
11809         break;
11810       case EditGame:
11811       default:
11812         break;
11813     }
11814
11815 //    forwardMostMove = currentMove;
11816     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11817     ResurrectChessProgram();    /* in case first program isn't running */
11818
11819     if (second.pr == NULL) {
11820         StartChessProgram(&second);
11821         if (second.protocolVersion == 1) {
11822           TwoMachinesEventIfReady();
11823         } else {
11824           /* kludge: allow timeout for initial "feature" command */
11825           FreezeUI();
11826           DisplayMessage("", _("Starting second chess program"));
11827           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11828         }
11829         return;
11830     }
11831     DisplayMessage("", "");
11832     InitChessProgram(&second, FALSE);
11833     SendToProgram("force\n", &second);
11834     if (startedFromSetupPosition) {
11835         SendBoard(&second, backwardMostMove);
11836     if (appData.debugMode) {
11837         fprintf(debugFP, "Two Machines\n");
11838     }
11839     }
11840     for (i = backwardMostMove; i < forwardMostMove; i++) {
11841         SendMoveToProgram(i, &second);
11842     }
11843
11844     gameMode = TwoMachinesPlay;
11845     pausing = FALSE;
11846     ModeHighlight();
11847     SetGameInfo();
11848     DisplayTwoMachinesTitle();
11849     firstMove = TRUE;
11850     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11851         onmove = &first;
11852     } else {
11853         onmove = &second;
11854     }
11855
11856     SendToProgram(first.computerString, &first);
11857     if (first.sendName) {
11858       sprintf(buf, "name %s\n", second.tidy);
11859       SendToProgram(buf, &first);
11860     }
11861     SendToProgram(second.computerString, &second);
11862     if (second.sendName) {
11863       sprintf(buf, "name %s\n", first.tidy);
11864       SendToProgram(buf, &second);
11865     }
11866
11867     ResetClocks();
11868     if (!first.sendTime || !second.sendTime) {
11869         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11870         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11871     }
11872     if (onmove->sendTime) {
11873       if (onmove->useColors) {
11874         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11875       }
11876       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11877     }
11878     if (onmove->useColors) {
11879       SendToProgram(onmove->twoMachinesColor, onmove);
11880     }
11881     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11882 //    SendToProgram("go\n", onmove);
11883     onmove->maybeThinking = TRUE;
11884     SetMachineThinkingEnables();
11885
11886     StartClocks();
11887
11888     if(bookHit) { // [HGM] book: simulate book reply
11889         static char bookMove[MSG_SIZ]; // a bit generous?
11890
11891         programStats.nodes = programStats.depth = programStats.time = 
11892         programStats.score = programStats.got_only_move = 0;
11893         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11894
11895         strcpy(bookMove, "move ");
11896         strcat(bookMove, bookHit);
11897         savedMessage = bookMove; // args for deferred call
11898         savedState = onmove;
11899         ScheduleDelayedEvent(DeferredBookMove, 1);
11900     }
11901 }
11902
11903 void
11904 TrainingEvent()
11905 {
11906     if (gameMode == Training) {
11907       SetTrainingModeOff();
11908       gameMode = PlayFromGameFile;
11909       DisplayMessage("", _("Training mode off"));
11910     } else {
11911       gameMode = Training;
11912       animateTraining = appData.animate;
11913
11914       /* make sure we are not already at the end of the game */
11915       if (currentMove < forwardMostMove) {
11916         SetTrainingModeOn();
11917         DisplayMessage("", _("Training mode on"));
11918       } else {
11919         gameMode = PlayFromGameFile;
11920         DisplayError(_("Already at end of game"), 0);
11921       }
11922     }
11923     ModeHighlight();
11924 }
11925
11926 void
11927 IcsClientEvent()
11928 {
11929     if (!appData.icsActive) return;
11930     switch (gameMode) {
11931       case IcsPlayingWhite:
11932       case IcsPlayingBlack:
11933       case IcsObserving:
11934       case IcsIdle:
11935       case BeginningOfGame:
11936       case IcsExamining:
11937         return;
11938
11939       case EditGame:
11940         break;
11941
11942       case EditPosition:
11943         EditPositionDone(TRUE);
11944         break;
11945
11946       case AnalyzeMode:
11947       case AnalyzeFile:
11948         ExitAnalyzeMode();
11949         break;
11950         
11951       default:
11952         EditGameEvent();
11953         break;
11954     }
11955
11956     gameMode = IcsIdle;
11957     ModeHighlight();
11958     return;
11959 }
11960
11961
11962 void
11963 EditGameEvent()
11964 {
11965     int i;
11966
11967     switch (gameMode) {
11968       case Training:
11969         SetTrainingModeOff();
11970         break;
11971       case MachinePlaysWhite:
11972       case MachinePlaysBlack:
11973       case BeginningOfGame:
11974         SendToProgram("force\n", &first);
11975         SetUserThinkingEnables();
11976         break;
11977       case PlayFromGameFile:
11978         (void) StopLoadGameTimer();
11979         if (gameFileFP != NULL) {
11980             gameFileFP = NULL;
11981         }
11982         break;
11983       case EditPosition:
11984         EditPositionDone(TRUE);
11985         break;
11986       case AnalyzeMode:
11987       case AnalyzeFile:
11988         ExitAnalyzeMode();
11989         SendToProgram("force\n", &first);
11990         break;
11991       case TwoMachinesPlay:
11992         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11993         ResurrectChessProgram();
11994         SetUserThinkingEnables();
11995         break;
11996       case EndOfGame:
11997         ResurrectChessProgram();
11998         break;
11999       case IcsPlayingBlack:
12000       case IcsPlayingWhite:
12001         DisplayError(_("Warning: You are still playing a game"), 0);
12002         break;
12003       case IcsObserving:
12004         DisplayError(_("Warning: You are still observing a game"), 0);
12005         break;
12006       case IcsExamining:
12007         DisplayError(_("Warning: You are still examining a game"), 0);
12008         break;
12009       case IcsIdle:
12010         break;
12011       case EditGame:
12012       default:
12013         return;
12014     }
12015     
12016     pausing = FALSE;
12017     StopClocks();
12018     first.offeredDraw = second.offeredDraw = 0;
12019
12020     if (gameMode == PlayFromGameFile) {
12021         whiteTimeRemaining = timeRemaining[0][currentMove];
12022         blackTimeRemaining = timeRemaining[1][currentMove];
12023         DisplayTitle("");
12024     }
12025
12026     if (gameMode == MachinePlaysWhite ||
12027         gameMode == MachinePlaysBlack ||
12028         gameMode == TwoMachinesPlay ||
12029         gameMode == EndOfGame) {
12030         i = forwardMostMove;
12031         while (i > currentMove) {
12032             SendToProgram("undo\n", &first);
12033             i--;
12034         }
12035         whiteTimeRemaining = timeRemaining[0][currentMove];
12036         blackTimeRemaining = timeRemaining[1][currentMove];
12037         DisplayBothClocks();
12038         if (whiteFlag || blackFlag) {
12039             whiteFlag = blackFlag = 0;
12040         }
12041         DisplayTitle("");
12042     }           
12043     
12044     gameMode = EditGame;
12045     ModeHighlight();
12046     SetGameInfo();
12047 }
12048
12049
12050 void
12051 EditPositionEvent()
12052 {
12053     if (gameMode == EditPosition) {
12054         EditGameEvent();
12055         return;
12056     }
12057     
12058     EditGameEvent();
12059     if (gameMode != EditGame) return;
12060     
12061     gameMode = EditPosition;
12062     ModeHighlight();
12063     SetGameInfo();
12064     if (currentMove > 0)
12065       CopyBoard(boards[0], boards[currentMove]);
12066     
12067     blackPlaysFirst = !WhiteOnMove(currentMove);
12068     ResetClocks();
12069     currentMove = forwardMostMove = backwardMostMove = 0;
12070     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12071     DisplayMove(-1);
12072 }
12073
12074 void
12075 ExitAnalyzeMode()
12076 {
12077     /* [DM] icsEngineAnalyze - possible call from other functions */
12078     if (appData.icsEngineAnalyze) {
12079         appData.icsEngineAnalyze = FALSE;
12080
12081         DisplayMessage("",_("Close ICS engine analyze..."));
12082     }
12083     if (first.analysisSupport && first.analyzing) {
12084       SendToProgram("exit\n", &first);
12085       first.analyzing = FALSE;
12086     }
12087     thinkOutput[0] = NULLCHAR;
12088 }
12089
12090 void
12091 EditPositionDone(Boolean fakeRights)
12092 {
12093     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12094
12095     startedFromSetupPosition = TRUE;
12096     InitChessProgram(&first, FALSE);
12097     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12098       boards[0][EP_STATUS] = EP_NONE;
12099       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12100     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12101         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12102         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12103       } else boards[0][CASTLING][2] = NoRights;
12104     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12105         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12106         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12107       } else boards[0][CASTLING][5] = NoRights;
12108     }
12109     SendToProgram("force\n", &first);
12110     if (blackPlaysFirst) {
12111         strcpy(moveList[0], "");
12112         strcpy(parseList[0], "");
12113         currentMove = forwardMostMove = backwardMostMove = 1;
12114         CopyBoard(boards[1], boards[0]);
12115     } else {
12116         currentMove = forwardMostMove = backwardMostMove = 0;
12117     }
12118     SendBoard(&first, forwardMostMove);
12119     if (appData.debugMode) {
12120         fprintf(debugFP, "EditPosDone\n");
12121     }
12122     DisplayTitle("");
12123     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12124     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12125     gameMode = EditGame;
12126     ModeHighlight();
12127     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12128     ClearHighlights(); /* [AS] */
12129 }
12130
12131 /* Pause for `ms' milliseconds */
12132 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12133 void
12134 TimeDelay(ms)
12135      long ms;
12136 {
12137     TimeMark m1, m2;
12138
12139     GetTimeMark(&m1);
12140     do {
12141         GetTimeMark(&m2);
12142     } while (SubtractTimeMarks(&m2, &m1) < ms);
12143 }
12144
12145 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12146 void
12147 SendMultiLineToICS(buf)
12148      char *buf;
12149 {
12150     char temp[MSG_SIZ+1], *p;
12151     int len;
12152
12153     len = strlen(buf);
12154     if (len > MSG_SIZ)
12155       len = MSG_SIZ;
12156   
12157     strncpy(temp, buf, len);
12158     temp[len] = 0;
12159
12160     p = temp;
12161     while (*p) {
12162         if (*p == '\n' || *p == '\r')
12163           *p = ' ';
12164         ++p;
12165     }
12166
12167     strcat(temp, "\n");
12168     SendToICS(temp);
12169     SendToPlayer(temp, strlen(temp));
12170 }
12171
12172 void
12173 SetWhiteToPlayEvent()
12174 {
12175     if (gameMode == EditPosition) {
12176         blackPlaysFirst = FALSE;
12177         DisplayBothClocks();    /* works because currentMove is 0 */
12178     } else if (gameMode == IcsExamining) {
12179         SendToICS(ics_prefix);
12180         SendToICS("tomove white\n");
12181     }
12182 }
12183
12184 void
12185 SetBlackToPlayEvent()
12186 {
12187     if (gameMode == EditPosition) {
12188         blackPlaysFirst = TRUE;
12189         currentMove = 1;        /* kludge */
12190         DisplayBothClocks();
12191         currentMove = 0;
12192     } else if (gameMode == IcsExamining) {
12193         SendToICS(ics_prefix);
12194         SendToICS("tomove black\n");
12195     }
12196 }
12197
12198 void
12199 EditPositionMenuEvent(selection, x, y)
12200      ChessSquare selection;
12201      int x, y;
12202 {
12203     char buf[MSG_SIZ];
12204     ChessSquare piece = boards[0][y][x];
12205
12206     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12207
12208     switch (selection) {
12209       case ClearBoard:
12210         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12211             SendToICS(ics_prefix);
12212             SendToICS("bsetup clear\n");
12213         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12214             SendToICS(ics_prefix);
12215             SendToICS("clearboard\n");
12216         } else {
12217             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12218                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12219                 for (y = 0; y < BOARD_HEIGHT; y++) {
12220                     if (gameMode == IcsExamining) {
12221                         if (boards[currentMove][y][x] != EmptySquare) {
12222                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12223                                     AAA + x, ONE + y);
12224                             SendToICS(buf);
12225                         }
12226                     } else {
12227                         boards[0][y][x] = p;
12228                     }
12229                 }
12230             }
12231         }
12232         if (gameMode == EditPosition) {
12233             DrawPosition(FALSE, boards[0]);
12234         }
12235         break;
12236
12237       case WhitePlay:
12238         SetWhiteToPlayEvent();
12239         break;
12240
12241       case BlackPlay:
12242         SetBlackToPlayEvent();
12243         break;
12244
12245       case EmptySquare:
12246         if (gameMode == IcsExamining) {
12247             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12248             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12249             SendToICS(buf);
12250         } else {
12251             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12252                 if(x == BOARD_LEFT-2) {
12253                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12254                     boards[0][y][1] = 0;
12255                 } else
12256                 if(x == BOARD_RGHT+1) {
12257                     if(y >= gameInfo.holdingsSize) break;
12258                     boards[0][y][BOARD_WIDTH-2] = 0;
12259                 } else break;
12260             }
12261             boards[0][y][x] = EmptySquare;
12262             DrawPosition(FALSE, boards[0]);
12263         }
12264         break;
12265
12266       case PromotePiece:
12267         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12268            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12269             selection = (ChessSquare) (PROMOTED piece);
12270         } else if(piece == EmptySquare) selection = WhiteSilver;
12271         else selection = (ChessSquare)((int)piece - 1);
12272         goto defaultlabel;
12273
12274       case DemotePiece:
12275         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12276            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12277             selection = (ChessSquare) (DEMOTED piece);
12278         } else if(piece == EmptySquare) selection = BlackSilver;
12279         else selection = (ChessSquare)((int)piece + 1);       
12280         goto defaultlabel;
12281
12282       case WhiteQueen:
12283       case BlackQueen:
12284         if(gameInfo.variant == VariantShatranj ||
12285            gameInfo.variant == VariantXiangqi  ||
12286            gameInfo.variant == VariantCourier  ||
12287            gameInfo.variant == VariantMakruk     )
12288             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12289         goto defaultlabel;
12290
12291       case WhiteKing:
12292       case BlackKing:
12293         if(gameInfo.variant == VariantXiangqi)
12294             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12295         if(gameInfo.variant == VariantKnightmate)
12296             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12297       default:
12298         defaultlabel:
12299         if (gameMode == IcsExamining) {
12300             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12301             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12302                     PieceToChar(selection), AAA + x, ONE + y);
12303             SendToICS(buf);
12304         } else {
12305             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12306                 int n;
12307                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12308                     n = PieceToNumber(selection - BlackPawn);
12309                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12310                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12311                     boards[0][BOARD_HEIGHT-1-n][1]++;
12312                 } else
12313                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12314                     n = PieceToNumber(selection);
12315                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12316                     boards[0][n][BOARD_WIDTH-1] = selection;
12317                     boards[0][n][BOARD_WIDTH-2]++;
12318                 }
12319             } else
12320             boards[0][y][x] = selection;
12321             DrawPosition(TRUE, boards[0]);
12322         }
12323         break;
12324     }
12325 }
12326
12327
12328 void
12329 DropMenuEvent(selection, x, y)
12330      ChessSquare selection;
12331      int x, y;
12332 {
12333     ChessMove moveType;
12334
12335     switch (gameMode) {
12336       case IcsPlayingWhite:
12337       case MachinePlaysBlack:
12338         if (!WhiteOnMove(currentMove)) {
12339             DisplayMoveError(_("It is Black's turn"));
12340             return;
12341         }
12342         moveType = WhiteDrop;
12343         break;
12344       case IcsPlayingBlack:
12345       case MachinePlaysWhite:
12346         if (WhiteOnMove(currentMove)) {
12347             DisplayMoveError(_("It is White's turn"));
12348             return;
12349         }
12350         moveType = BlackDrop;
12351         break;
12352       case EditGame:
12353         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12354         break;
12355       default:
12356         return;
12357     }
12358
12359     if (moveType == BlackDrop && selection < BlackPawn) {
12360       selection = (ChessSquare) ((int) selection
12361                                  + (int) BlackPawn - (int) WhitePawn);
12362     }
12363     if (boards[currentMove][y][x] != EmptySquare) {
12364         DisplayMoveError(_("That square is occupied"));
12365         return;
12366     }
12367
12368     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12369 }
12370
12371 void
12372 AcceptEvent()
12373 {
12374     /* Accept a pending offer of any kind from opponent */
12375     
12376     if (appData.icsActive) {
12377         SendToICS(ics_prefix);
12378         SendToICS("accept\n");
12379     } else if (cmailMsgLoaded) {
12380         if (currentMove == cmailOldMove &&
12381             commentList[cmailOldMove] != NULL &&
12382             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12383                    "Black offers a draw" : "White offers a draw")) {
12384             TruncateGame();
12385             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12386             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12387         } else {
12388             DisplayError(_("There is no pending offer on this move"), 0);
12389             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12390         }
12391     } else {
12392         /* Not used for offers from chess program */
12393     }
12394 }
12395
12396 void
12397 DeclineEvent()
12398 {
12399     /* Decline a pending offer of any kind from opponent */
12400     
12401     if (appData.icsActive) {
12402         SendToICS(ics_prefix);
12403         SendToICS("decline\n");
12404     } else if (cmailMsgLoaded) {
12405         if (currentMove == cmailOldMove &&
12406             commentList[cmailOldMove] != NULL &&
12407             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12408                    "Black offers a draw" : "White offers a draw")) {
12409 #ifdef NOTDEF
12410             AppendComment(cmailOldMove, "Draw declined", TRUE);
12411             DisplayComment(cmailOldMove - 1, "Draw declined");
12412 #endif /*NOTDEF*/
12413         } else {
12414             DisplayError(_("There is no pending offer on this move"), 0);
12415         }
12416     } else {
12417         /* Not used for offers from chess program */
12418     }
12419 }
12420
12421 void
12422 RematchEvent()
12423 {
12424     /* Issue ICS rematch command */
12425     if (appData.icsActive) {
12426         SendToICS(ics_prefix);
12427         SendToICS("rematch\n");
12428     }
12429 }
12430
12431 void
12432 CallFlagEvent()
12433 {
12434     /* Call your opponent's flag (claim a win on time) */
12435     if (appData.icsActive) {
12436         SendToICS(ics_prefix);
12437         SendToICS("flag\n");
12438     } else {
12439         switch (gameMode) {
12440           default:
12441             return;
12442           case MachinePlaysWhite:
12443             if (whiteFlag) {
12444                 if (blackFlag)
12445                   GameEnds(GameIsDrawn, "Both players ran out of time",
12446                            GE_PLAYER);
12447                 else
12448                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12449             } else {
12450                 DisplayError(_("Your opponent is not out of time"), 0);
12451             }
12452             break;
12453           case MachinePlaysBlack:
12454             if (blackFlag) {
12455                 if (whiteFlag)
12456                   GameEnds(GameIsDrawn, "Both players ran out of time",
12457                            GE_PLAYER);
12458                 else
12459                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12460             } else {
12461                 DisplayError(_("Your opponent is not out of time"), 0);
12462             }
12463             break;
12464         }
12465     }
12466 }
12467
12468 void
12469 DrawEvent()
12470 {
12471     /* Offer draw or accept pending draw offer from opponent */
12472     
12473     if (appData.icsActive) {
12474         /* Note: tournament rules require draw offers to be
12475            made after you make your move but before you punch
12476            your clock.  Currently ICS doesn't let you do that;
12477            instead, you immediately punch your clock after making
12478            a move, but you can offer a draw at any time. */
12479         
12480         SendToICS(ics_prefix);
12481         SendToICS("draw\n");
12482         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12483     } else if (cmailMsgLoaded) {
12484         if (currentMove == cmailOldMove &&
12485             commentList[cmailOldMove] != NULL &&
12486             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12487                    "Black offers a draw" : "White offers a draw")) {
12488             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12489             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12490         } else if (currentMove == cmailOldMove + 1) {
12491             char *offer = WhiteOnMove(cmailOldMove) ?
12492               "White offers a draw" : "Black offers a draw";
12493             AppendComment(currentMove, offer, TRUE);
12494             DisplayComment(currentMove - 1, offer);
12495             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12496         } else {
12497             DisplayError(_("You must make your move before offering a draw"), 0);
12498             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12499         }
12500     } else if (first.offeredDraw) {
12501         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12502     } else {
12503         if (first.sendDrawOffers) {
12504             SendToProgram("draw\n", &first);
12505             userOfferedDraw = TRUE;
12506         }
12507     }
12508 }
12509
12510 void
12511 AdjournEvent()
12512 {
12513     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12514     
12515     if (appData.icsActive) {
12516         SendToICS(ics_prefix);
12517         SendToICS("adjourn\n");
12518     } else {
12519         /* Currently GNU Chess doesn't offer or accept Adjourns */
12520     }
12521 }
12522
12523
12524 void
12525 AbortEvent()
12526 {
12527     /* Offer Abort or accept pending Abort offer from opponent */
12528     
12529     if (appData.icsActive) {
12530         SendToICS(ics_prefix);
12531         SendToICS("abort\n");
12532     } else {
12533         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12534     }
12535 }
12536
12537 void
12538 ResignEvent()
12539 {
12540     /* Resign.  You can do this even if it's not your turn. */
12541     
12542     if (appData.icsActive) {
12543         SendToICS(ics_prefix);
12544         SendToICS("resign\n");
12545     } else {
12546         switch (gameMode) {
12547           case MachinePlaysWhite:
12548             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12549             break;
12550           case MachinePlaysBlack:
12551             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12552             break;
12553           case EditGame:
12554             if (cmailMsgLoaded) {
12555                 TruncateGame();
12556                 if (WhiteOnMove(cmailOldMove)) {
12557                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12558                 } else {
12559                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12560                 }
12561                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12562             }
12563             break;
12564           default:
12565             break;
12566         }
12567     }
12568 }
12569
12570
12571 void
12572 StopObservingEvent()
12573 {
12574     /* Stop observing current games */
12575     SendToICS(ics_prefix);
12576     SendToICS("unobserve\n");
12577 }
12578
12579 void
12580 StopExaminingEvent()
12581 {
12582     /* Stop observing current game */
12583     SendToICS(ics_prefix);
12584     SendToICS("unexamine\n");
12585 }
12586
12587 void
12588 ForwardInner(target)
12589      int target;
12590 {
12591     int limit;
12592
12593     if (appData.debugMode)
12594         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12595                 target, currentMove, forwardMostMove);
12596
12597     if (gameMode == EditPosition)
12598       return;
12599
12600     if (gameMode == PlayFromGameFile && !pausing)
12601       PauseEvent();
12602     
12603     if (gameMode == IcsExamining && pausing)
12604       limit = pauseExamForwardMostMove;
12605     else
12606       limit = forwardMostMove;
12607     
12608     if (target > limit) target = limit;
12609
12610     if (target > 0 && moveList[target - 1][0]) {
12611         int fromX, fromY, toX, toY;
12612         toX = moveList[target - 1][2] - AAA;
12613         toY = moveList[target - 1][3] - ONE;
12614         if (moveList[target - 1][1] == '@') {
12615             if (appData.highlightLastMove) {
12616                 SetHighlights(-1, -1, toX, toY);
12617             }
12618         } else {
12619             fromX = moveList[target - 1][0] - AAA;
12620             fromY = moveList[target - 1][1] - ONE;
12621             if (target == currentMove + 1) {
12622                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12623             }
12624             if (appData.highlightLastMove) {
12625                 SetHighlights(fromX, fromY, toX, toY);
12626             }
12627         }
12628     }
12629     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12630         gameMode == Training || gameMode == PlayFromGameFile || 
12631         gameMode == AnalyzeFile) {
12632         while (currentMove < target) {
12633             SendMoveToProgram(currentMove++, &first);
12634         }
12635     } else {
12636         currentMove = target;
12637     }
12638     
12639     if (gameMode == EditGame || gameMode == EndOfGame) {
12640         whiteTimeRemaining = timeRemaining[0][currentMove];
12641         blackTimeRemaining = timeRemaining[1][currentMove];
12642     }
12643     DisplayBothClocks();
12644     DisplayMove(currentMove - 1);
12645     DrawPosition(FALSE, boards[currentMove]);
12646     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12647     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12648         DisplayComment(currentMove - 1, commentList[currentMove]);
12649     }
12650 }
12651
12652
12653 void
12654 ForwardEvent()
12655 {
12656     if (gameMode == IcsExamining && !pausing) {
12657         SendToICS(ics_prefix);
12658         SendToICS("forward\n");
12659     } else {
12660         ForwardInner(currentMove + 1);
12661     }
12662 }
12663
12664 void
12665 ToEndEvent()
12666 {
12667     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12668         /* to optimze, we temporarily turn off analysis mode while we feed
12669          * the remaining moves to the engine. Otherwise we get analysis output
12670          * after each move.
12671          */ 
12672         if (first.analysisSupport) {
12673           SendToProgram("exit\nforce\n", &first);
12674           first.analyzing = FALSE;
12675         }
12676     }
12677         
12678     if (gameMode == IcsExamining && !pausing) {
12679         SendToICS(ics_prefix);
12680         SendToICS("forward 999999\n");
12681     } else {
12682         ForwardInner(forwardMostMove);
12683     }
12684
12685     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12686         /* we have fed all the moves, so reactivate analysis mode */
12687         SendToProgram("analyze\n", &first);
12688         first.analyzing = TRUE;
12689         /*first.maybeThinking = TRUE;*/
12690         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12691     }
12692 }
12693
12694 void
12695 BackwardInner(target)
12696      int target;
12697 {
12698     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12699
12700     if (appData.debugMode)
12701         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12702                 target, currentMove, forwardMostMove);
12703
12704     if (gameMode == EditPosition) return;
12705     if (currentMove <= backwardMostMove) {
12706         ClearHighlights();
12707         DrawPosition(full_redraw, boards[currentMove]);
12708         return;
12709     }
12710     if (gameMode == PlayFromGameFile && !pausing)
12711       PauseEvent();
12712     
12713     if (moveList[target][0]) {
12714         int fromX, fromY, toX, toY;
12715         toX = moveList[target][2] - AAA;
12716         toY = moveList[target][3] - ONE;
12717         if (moveList[target][1] == '@') {
12718             if (appData.highlightLastMove) {
12719                 SetHighlights(-1, -1, toX, toY);
12720             }
12721         } else {
12722             fromX = moveList[target][0] - AAA;
12723             fromY = moveList[target][1] - ONE;
12724             if (target == currentMove - 1) {
12725                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12726             }
12727             if (appData.highlightLastMove) {
12728                 SetHighlights(fromX, fromY, toX, toY);
12729             }
12730         }
12731     }
12732     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12733         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12734         while (currentMove > target) {
12735             SendToProgram("undo\n", &first);
12736             currentMove--;
12737         }
12738     } else {
12739         currentMove = target;
12740     }
12741     
12742     if (gameMode == EditGame || gameMode == EndOfGame) {
12743         whiteTimeRemaining = timeRemaining[0][currentMove];
12744         blackTimeRemaining = timeRemaining[1][currentMove];
12745     }
12746     DisplayBothClocks();
12747     DisplayMove(currentMove - 1);
12748     DrawPosition(full_redraw, boards[currentMove]);
12749     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12750     // [HGM] PV info: routine tests if comment empty
12751     DisplayComment(currentMove - 1, commentList[currentMove]);
12752 }
12753
12754 void
12755 BackwardEvent()
12756 {
12757     if (gameMode == IcsExamining && !pausing) {
12758         SendToICS(ics_prefix);
12759         SendToICS("backward\n");
12760     } else {
12761         BackwardInner(currentMove - 1);
12762     }
12763 }
12764
12765 void
12766 ToStartEvent()
12767 {
12768     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12769         /* to optimize, we temporarily turn off analysis mode while we undo
12770          * all the moves. Otherwise we get analysis output after each undo.
12771          */ 
12772         if (first.analysisSupport) {
12773           SendToProgram("exit\nforce\n", &first);
12774           first.analyzing = FALSE;
12775         }
12776     }
12777
12778     if (gameMode == IcsExamining && !pausing) {
12779         SendToICS(ics_prefix);
12780         SendToICS("backward 999999\n");
12781     } else {
12782         BackwardInner(backwardMostMove);
12783     }
12784
12785     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12786         /* we have fed all the moves, so reactivate analysis mode */
12787         SendToProgram("analyze\n", &first);
12788         first.analyzing = TRUE;
12789         /*first.maybeThinking = TRUE;*/
12790         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12791     }
12792 }
12793
12794 void
12795 ToNrEvent(int to)
12796 {
12797   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12798   if (to >= forwardMostMove) to = forwardMostMove;
12799   if (to <= backwardMostMove) to = backwardMostMove;
12800   if (to < currentMove) {
12801     BackwardInner(to);
12802   } else {
12803     ForwardInner(to);
12804   }
12805 }
12806
12807 void
12808 RevertEvent(Boolean annotate)
12809 {
12810     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12811         return;
12812     }
12813     if (gameMode != IcsExamining) {
12814         DisplayError(_("You are not examining a game"), 0);
12815         return;
12816     }
12817     if (pausing) {
12818         DisplayError(_("You can't revert while pausing"), 0);
12819         return;
12820     }
12821     SendToICS(ics_prefix);
12822     SendToICS("revert\n");
12823 }
12824
12825 void
12826 RetractMoveEvent()
12827 {
12828     switch (gameMode) {
12829       case MachinePlaysWhite:
12830       case MachinePlaysBlack:
12831         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12832             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12833             return;
12834         }
12835         if (forwardMostMove < 2) return;
12836         currentMove = forwardMostMove = forwardMostMove - 2;
12837         whiteTimeRemaining = timeRemaining[0][currentMove];
12838         blackTimeRemaining = timeRemaining[1][currentMove];
12839         DisplayBothClocks();
12840         DisplayMove(currentMove - 1);
12841         ClearHighlights();/*!! could figure this out*/
12842         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12843         SendToProgram("remove\n", &first);
12844         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12845         break;
12846
12847       case BeginningOfGame:
12848       default:
12849         break;
12850
12851       case IcsPlayingWhite:
12852       case IcsPlayingBlack:
12853         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12854             SendToICS(ics_prefix);
12855             SendToICS("takeback 2\n");
12856         } else {
12857             SendToICS(ics_prefix);
12858             SendToICS("takeback 1\n");
12859         }
12860         break;
12861     }
12862 }
12863
12864 void
12865 MoveNowEvent()
12866 {
12867     ChessProgramState *cps;
12868
12869     switch (gameMode) {
12870       case MachinePlaysWhite:
12871         if (!WhiteOnMove(forwardMostMove)) {
12872             DisplayError(_("It is your turn"), 0);
12873             return;
12874         }
12875         cps = &first;
12876         break;
12877       case MachinePlaysBlack:
12878         if (WhiteOnMove(forwardMostMove)) {
12879             DisplayError(_("It is your turn"), 0);
12880             return;
12881         }
12882         cps = &first;
12883         break;
12884       case TwoMachinesPlay:
12885         if (WhiteOnMove(forwardMostMove) ==
12886             (first.twoMachinesColor[0] == 'w')) {
12887             cps = &first;
12888         } else {
12889             cps = &second;
12890         }
12891         break;
12892       case BeginningOfGame:
12893       default:
12894         return;
12895     }
12896     SendToProgram("?\n", cps);
12897 }
12898
12899 void
12900 TruncateGameEvent()
12901 {
12902     EditGameEvent();
12903     if (gameMode != EditGame) return;
12904     TruncateGame();
12905 }
12906
12907 void
12908 TruncateGame()
12909 {
12910     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12911     if (forwardMostMove > currentMove) {
12912         if (gameInfo.resultDetails != NULL) {
12913             free(gameInfo.resultDetails);
12914             gameInfo.resultDetails = NULL;
12915             gameInfo.result = GameUnfinished;
12916         }
12917         forwardMostMove = currentMove;
12918         HistorySet(parseList, backwardMostMove, forwardMostMove,
12919                    currentMove-1);
12920     }
12921 }
12922
12923 void
12924 HintEvent()
12925 {
12926     if (appData.noChessProgram) return;
12927     switch (gameMode) {
12928       case MachinePlaysWhite:
12929         if (WhiteOnMove(forwardMostMove)) {
12930             DisplayError(_("Wait until your turn"), 0);
12931             return;
12932         }
12933         break;
12934       case BeginningOfGame:
12935       case MachinePlaysBlack:
12936         if (!WhiteOnMove(forwardMostMove)) {
12937             DisplayError(_("Wait until your turn"), 0);
12938             return;
12939         }
12940         break;
12941       default:
12942         DisplayError(_("No hint available"), 0);
12943         return;
12944     }
12945     SendToProgram("hint\n", &first);
12946     hintRequested = TRUE;
12947 }
12948
12949 void
12950 BookEvent()
12951 {
12952     if (appData.noChessProgram) return;
12953     switch (gameMode) {
12954       case MachinePlaysWhite:
12955         if (WhiteOnMove(forwardMostMove)) {
12956             DisplayError(_("Wait until your turn"), 0);
12957             return;
12958         }
12959         break;
12960       case BeginningOfGame:
12961       case MachinePlaysBlack:
12962         if (!WhiteOnMove(forwardMostMove)) {
12963             DisplayError(_("Wait until your turn"), 0);
12964             return;
12965         }
12966         break;
12967       case EditPosition:
12968         EditPositionDone(TRUE);
12969         break;
12970       case TwoMachinesPlay:
12971         return;
12972       default:
12973         break;
12974     }
12975     SendToProgram("bk\n", &first);
12976     bookOutput[0] = NULLCHAR;
12977     bookRequested = TRUE;
12978 }
12979
12980 void
12981 AboutGameEvent()
12982 {
12983     char *tags = PGNTags(&gameInfo);
12984     TagsPopUp(tags, CmailMsg());
12985     free(tags);
12986 }
12987
12988 /* end button procedures */
12989
12990 void
12991 PrintPosition(fp, move)
12992      FILE *fp;
12993      int move;
12994 {
12995     int i, j;
12996     
12997     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12998         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12999             char c = PieceToChar(boards[move][i][j]);
13000             fputc(c == 'x' ? '.' : c, fp);
13001             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13002         }
13003     }
13004     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13005       fprintf(fp, "white to play\n");
13006     else
13007       fprintf(fp, "black to play\n");
13008 }
13009
13010 void
13011 PrintOpponents(fp)
13012      FILE *fp;
13013 {
13014     if (gameInfo.white != NULL) {
13015         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13016     } else {
13017         fprintf(fp, "\n");
13018     }
13019 }
13020
13021 /* Find last component of program's own name, using some heuristics */
13022 void
13023 TidyProgramName(prog, host, buf)
13024      char *prog, *host, buf[MSG_SIZ];
13025 {
13026     char *p, *q;
13027     int local = (strcmp(host, "localhost") == 0);
13028     while (!local && (p = strchr(prog, ';')) != NULL) {
13029         p++;
13030         while (*p == ' ') p++;
13031         prog = p;
13032     }
13033     if (*prog == '"' || *prog == '\'') {
13034         q = strchr(prog + 1, *prog);
13035     } else {
13036         q = strchr(prog, ' ');
13037     }
13038     if (q == NULL) q = prog + strlen(prog);
13039     p = q;
13040     while (p >= prog && *p != '/' && *p != '\\') p--;
13041     p++;
13042     if(p == prog && *p == '"') p++;
13043     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13044     memcpy(buf, p, q - p);
13045     buf[q - p] = NULLCHAR;
13046     if (!local) {
13047         strcat(buf, "@");
13048         strcat(buf, host);
13049     }
13050 }
13051
13052 char *
13053 TimeControlTagValue()
13054 {
13055     char buf[MSG_SIZ];
13056     if (!appData.clockMode) {
13057         strcpy(buf, "-");
13058     } else if (movesPerSession > 0) {
13059         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13060     } else if (timeIncrement == 0) {
13061         sprintf(buf, "%ld", timeControl/1000);
13062     } else {
13063         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13064     }
13065     return StrSave(buf);
13066 }
13067
13068 void
13069 SetGameInfo()
13070 {
13071     /* This routine is used only for certain modes */
13072     VariantClass v = gameInfo.variant;
13073     ChessMove r = GameUnfinished;
13074     char *p = NULL;
13075
13076     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13077         r = gameInfo.result; 
13078         p = gameInfo.resultDetails; 
13079         gameInfo.resultDetails = NULL;
13080     }
13081     ClearGameInfo(&gameInfo);
13082     gameInfo.variant = v;
13083
13084     switch (gameMode) {
13085       case MachinePlaysWhite:
13086         gameInfo.event = StrSave( appData.pgnEventHeader );
13087         gameInfo.site = StrSave(HostName());
13088         gameInfo.date = PGNDate();
13089         gameInfo.round = StrSave("-");
13090         gameInfo.white = StrSave(first.tidy);
13091         gameInfo.black = StrSave(UserName());
13092         gameInfo.timeControl = TimeControlTagValue();
13093         break;
13094
13095       case MachinePlaysBlack:
13096         gameInfo.event = StrSave( appData.pgnEventHeader );
13097         gameInfo.site = StrSave(HostName());
13098         gameInfo.date = PGNDate();
13099         gameInfo.round = StrSave("-");
13100         gameInfo.white = StrSave(UserName());
13101         gameInfo.black = StrSave(first.tidy);
13102         gameInfo.timeControl = TimeControlTagValue();
13103         break;
13104
13105       case TwoMachinesPlay:
13106         gameInfo.event = StrSave( appData.pgnEventHeader );
13107         gameInfo.site = StrSave(HostName());
13108         gameInfo.date = PGNDate();
13109         if (matchGame > 0) {
13110             char buf[MSG_SIZ];
13111             sprintf(buf, "%d", matchGame);
13112             gameInfo.round = StrSave(buf);
13113         } else {
13114             gameInfo.round = StrSave("-");
13115         }
13116         if (first.twoMachinesColor[0] == 'w') {
13117             gameInfo.white = StrSave(first.tidy);
13118             gameInfo.black = StrSave(second.tidy);
13119         } else {
13120             gameInfo.white = StrSave(second.tidy);
13121             gameInfo.black = StrSave(first.tidy);
13122         }
13123         gameInfo.timeControl = TimeControlTagValue();
13124         break;
13125
13126       case EditGame:
13127         gameInfo.event = StrSave("Edited game");
13128         gameInfo.site = StrSave(HostName());
13129         gameInfo.date = PGNDate();
13130         gameInfo.round = StrSave("-");
13131         gameInfo.white = StrSave("-");
13132         gameInfo.black = StrSave("-");
13133         gameInfo.result = r;
13134         gameInfo.resultDetails = p;
13135         break;
13136
13137       case EditPosition:
13138         gameInfo.event = StrSave("Edited position");
13139         gameInfo.site = StrSave(HostName());
13140         gameInfo.date = PGNDate();
13141         gameInfo.round = StrSave("-");
13142         gameInfo.white = StrSave("-");
13143         gameInfo.black = StrSave("-");
13144         break;
13145
13146       case IcsPlayingWhite:
13147       case IcsPlayingBlack:
13148       case IcsObserving:
13149       case IcsExamining:
13150         break;
13151
13152       case PlayFromGameFile:
13153         gameInfo.event = StrSave("Game from non-PGN file");
13154         gameInfo.site = StrSave(HostName());
13155         gameInfo.date = PGNDate();
13156         gameInfo.round = StrSave("-");
13157         gameInfo.white = StrSave("?");
13158         gameInfo.black = StrSave("?");
13159         break;
13160
13161       default:
13162         break;
13163     }
13164 }
13165
13166 void
13167 ReplaceComment(index, text)
13168      int index;
13169      char *text;
13170 {
13171     int len;
13172
13173     while (*text == '\n') text++;
13174     len = strlen(text);
13175     while (len > 0 && text[len - 1] == '\n') len--;
13176
13177     if (commentList[index] != NULL)
13178       free(commentList[index]);
13179
13180     if (len == 0) {
13181         commentList[index] = NULL;
13182         return;
13183     }
13184   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13185       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13186       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13187     commentList[index] = (char *) malloc(len + 2);
13188     strncpy(commentList[index], text, len);
13189     commentList[index][len] = '\n';
13190     commentList[index][len + 1] = NULLCHAR;
13191   } else { 
13192     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13193     char *p;
13194     commentList[index] = (char *) malloc(len + 6);
13195     strcpy(commentList[index], "{\n");
13196     strncpy(commentList[index]+2, text, len);
13197     commentList[index][len+2] = NULLCHAR;
13198     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13199     strcat(commentList[index], "\n}\n");
13200   }
13201 }
13202
13203 void
13204 CrushCRs(text)
13205      char *text;
13206 {
13207   char *p = text;
13208   char *q = text;
13209   char ch;
13210
13211   do {
13212     ch = *p++;
13213     if (ch == '\r') continue;
13214     *q++ = ch;
13215   } while (ch != '\0');
13216 }
13217
13218 void
13219 AppendComment(index, text, addBraces)
13220      int index;
13221      char *text;
13222      Boolean addBraces; // [HGM] braces: tells if we should add {}
13223 {
13224     int oldlen, len;
13225     char *old;
13226
13227 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13228     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13229
13230     CrushCRs(text);
13231     while (*text == '\n') text++;
13232     len = strlen(text);
13233     while (len > 0 && text[len - 1] == '\n') len--;
13234
13235     if (len == 0) return;
13236
13237     if (commentList[index] != NULL) {
13238         old = commentList[index];
13239         oldlen = strlen(old);
13240         while(commentList[index][oldlen-1] ==  '\n')
13241           commentList[index][--oldlen] = NULLCHAR;
13242         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13243         strcpy(commentList[index], old);
13244         free(old);
13245         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13246         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13247           if(addBraces) addBraces = FALSE; else { text++; len--; }
13248           while (*text == '\n') { text++; len--; }
13249           commentList[index][--oldlen] = NULLCHAR;
13250       }
13251         if(addBraces) strcat(commentList[index], "\n{\n");
13252         else          strcat(commentList[index], "\n");
13253         strcat(commentList[index], text);
13254         if(addBraces) strcat(commentList[index], "\n}\n");
13255         else          strcat(commentList[index], "\n");
13256     } else {
13257         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13258         if(addBraces)
13259              strcpy(commentList[index], "{\n");
13260         else commentList[index][0] = NULLCHAR;
13261         strcat(commentList[index], text);
13262         strcat(commentList[index], "\n");
13263         if(addBraces) strcat(commentList[index], "}\n");
13264     }
13265 }
13266
13267 static char * FindStr( char * text, char * sub_text )
13268 {
13269     char * result = strstr( text, sub_text );
13270
13271     if( result != NULL ) {
13272         result += strlen( sub_text );
13273     }
13274
13275     return result;
13276 }
13277
13278 /* [AS] Try to extract PV info from PGN comment */
13279 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13280 char *GetInfoFromComment( int index, char * text )
13281 {
13282     char * sep = text;
13283
13284     if( text != NULL && index > 0 ) {
13285         int score = 0;
13286         int depth = 0;
13287         int time = -1, sec = 0, deci;
13288         char * s_eval = FindStr( text, "[%eval " );
13289         char * s_emt = FindStr( text, "[%emt " );
13290
13291         if( s_eval != NULL || s_emt != NULL ) {
13292             /* New style */
13293             char delim;
13294
13295             if( s_eval != NULL ) {
13296                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13297                     return text;
13298                 }
13299
13300                 if( delim != ']' ) {
13301                     return text;
13302                 }
13303             }
13304
13305             if( s_emt != NULL ) {
13306             }
13307                 return text;
13308         }
13309         else {
13310             /* We expect something like: [+|-]nnn.nn/dd */
13311             int score_lo = 0;
13312
13313             if(*text != '{') return text; // [HGM] braces: must be normal comment
13314
13315             sep = strchr( text, '/' );
13316             if( sep == NULL || sep < (text+4) ) {
13317                 return text;
13318             }
13319
13320             time = -1; sec = -1; deci = -1;
13321             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13322                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13323                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13324                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13325                 return text;
13326             }
13327
13328             if( score_lo < 0 || score_lo >= 100 ) {
13329                 return text;
13330             }
13331
13332             if(sec >= 0) time = 600*time + 10*sec; else
13333             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13334
13335             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13336
13337             /* [HGM] PV time: now locate end of PV info */
13338             while( *++sep >= '0' && *sep <= '9'); // strip depth
13339             if(time >= 0)
13340             while( *++sep >= '0' && *sep <= '9'); // strip time
13341             if(sec >= 0)
13342             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13343             if(deci >= 0)
13344             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13345             while(*sep == ' ') sep++;
13346         }
13347
13348         if( depth <= 0 ) {
13349             return text;
13350         }
13351
13352         if( time < 0 ) {
13353             time = -1;
13354         }
13355
13356         pvInfoList[index-1].depth = depth;
13357         pvInfoList[index-1].score = score;
13358         pvInfoList[index-1].time  = 10*time; // centi-sec
13359         if(*sep == '}') *sep = 0; else *--sep = '{';
13360     }
13361     return sep;
13362 }
13363
13364 void
13365 SendToProgram(message, cps)
13366      char *message;
13367      ChessProgramState *cps;
13368 {
13369     int count, outCount, error;
13370     char buf[MSG_SIZ];
13371
13372     if (cps->pr == NULL) return;
13373     Attention(cps);
13374     
13375     if (appData.debugMode) {
13376         TimeMark now;
13377         GetTimeMark(&now);
13378         fprintf(debugFP, "%ld >%-6s: %s", 
13379                 SubtractTimeMarks(&now, &programStartTime),
13380                 cps->which, message);
13381     }
13382     
13383     count = strlen(message);
13384     outCount = OutputToProcess(cps->pr, message, count, &error);
13385     if (outCount < count && !exiting 
13386                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13387         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13388         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13389             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13390                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13391                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13392             } else {
13393                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13394             }
13395             gameInfo.resultDetails = StrSave(buf);
13396         }
13397         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13398     }
13399 }
13400
13401 void
13402 ReceiveFromProgram(isr, closure, message, count, error)
13403      InputSourceRef isr;
13404      VOIDSTAR closure;
13405      char *message;
13406      int count;
13407      int error;
13408 {
13409     char *end_str;
13410     char buf[MSG_SIZ];
13411     ChessProgramState *cps = (ChessProgramState *)closure;
13412
13413     if (isr != cps->isr) return; /* Killed intentionally */
13414     if (count <= 0) {
13415         if (count == 0) {
13416             sprintf(buf,
13417                     _("Error: %s chess program (%s) exited unexpectedly"),
13418                     cps->which, cps->program);
13419         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13420                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13421                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13422                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13423                 } else {
13424                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13425                 }
13426                 gameInfo.resultDetails = StrSave(buf);
13427             }
13428             RemoveInputSource(cps->isr);
13429             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13430         } else {
13431             sprintf(buf,
13432                     _("Error reading from %s chess program (%s)"),
13433                     cps->which, cps->program);
13434             RemoveInputSource(cps->isr);
13435
13436             /* [AS] Program is misbehaving badly... kill it */
13437             if( count == -2 ) {
13438                 DestroyChildProcess( cps->pr, 9 );
13439                 cps->pr = NoProc;
13440             }
13441
13442             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13443         }
13444         return;
13445     }
13446     
13447     if ((end_str = strchr(message, '\r')) != NULL)
13448       *end_str = NULLCHAR;
13449     if ((end_str = strchr(message, '\n')) != NULL)
13450       *end_str = NULLCHAR;
13451     
13452     if (appData.debugMode) {
13453         TimeMark now; int print = 1;
13454         char *quote = ""; char c; int i;
13455
13456         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13457                 char start = message[0];
13458                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13459                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13460                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13461                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13462                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13463                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13464                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13465                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13466                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13467                     print = (appData.engineComments >= 2);
13468                 }
13469                 message[0] = start; // restore original message
13470         }
13471         if(print) {
13472                 GetTimeMark(&now);
13473                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13474                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13475                         quote,
13476                         message);
13477         }
13478     }
13479
13480     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13481     if (appData.icsEngineAnalyze) {
13482         if (strstr(message, "whisper") != NULL ||
13483              strstr(message, "kibitz") != NULL || 
13484             strstr(message, "tellics") != NULL) return;
13485     }
13486
13487     HandleMachineMove(message, cps);
13488 }
13489
13490
13491 void
13492 SendTimeControl(cps, mps, tc, inc, sd, st)
13493      ChessProgramState *cps;
13494      int mps, inc, sd, st;
13495      long tc;
13496 {
13497     char buf[MSG_SIZ];
13498     int seconds;
13499
13500     if( timeControl_2 > 0 ) {
13501         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13502             tc = timeControl_2;
13503         }
13504     }
13505     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13506     inc /= cps->timeOdds;
13507     st  /= cps->timeOdds;
13508
13509     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13510
13511     if (st > 0) {
13512       /* Set exact time per move, normally using st command */
13513       if (cps->stKludge) {
13514         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13515         seconds = st % 60;
13516         if (seconds == 0) {
13517           sprintf(buf, "level 1 %d\n", st/60);
13518         } else {
13519           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13520         }
13521       } else {
13522         sprintf(buf, "st %d\n", st);
13523       }
13524     } else {
13525       /* Set conventional or incremental time control, using level command */
13526       if (seconds == 0) {
13527         /* Note old gnuchess bug -- minutes:seconds used to not work.
13528            Fixed in later versions, but still avoid :seconds
13529            when seconds is 0. */
13530         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13531       } else {
13532         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13533                 seconds, inc/1000);
13534       }
13535     }
13536     SendToProgram(buf, cps);
13537
13538     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13539     /* Orthogonally, limit search to given depth */
13540     if (sd > 0) {
13541       if (cps->sdKludge) {
13542         sprintf(buf, "depth\n%d\n", sd);
13543       } else {
13544         sprintf(buf, "sd %d\n", sd);
13545       }
13546       SendToProgram(buf, cps);
13547     }
13548
13549     if(cps->nps > 0) { /* [HGM] nps */
13550         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13551         else {
13552                 sprintf(buf, "nps %d\n", cps->nps);
13553               SendToProgram(buf, cps);
13554         }
13555     }
13556 }
13557
13558 ChessProgramState *WhitePlayer()
13559 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13560 {
13561     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13562        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13563         return &second;
13564     return &first;
13565 }
13566
13567 void
13568 SendTimeRemaining(cps, machineWhite)
13569      ChessProgramState *cps;
13570      int /*boolean*/ machineWhite;
13571 {
13572     char message[MSG_SIZ];
13573     long time, otime;
13574
13575     /* Note: this routine must be called when the clocks are stopped
13576        or when they have *just* been set or switched; otherwise
13577        it will be off by the time since the current tick started.
13578     */
13579     if (machineWhite) {
13580         time = whiteTimeRemaining / 10;
13581         otime = blackTimeRemaining / 10;
13582     } else {
13583         time = blackTimeRemaining / 10;
13584         otime = whiteTimeRemaining / 10;
13585     }
13586     /* [HGM] translate opponent's time by time-odds factor */
13587     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13588     if (appData.debugMode) {
13589         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13590     }
13591
13592     if (time <= 0) time = 1;
13593     if (otime <= 0) otime = 1;
13594     
13595     sprintf(message, "time %ld\n", time);
13596     SendToProgram(message, cps);
13597
13598     sprintf(message, "otim %ld\n", otime);
13599     SendToProgram(message, cps);
13600 }
13601
13602 int
13603 BoolFeature(p, name, loc, cps)
13604      char **p;
13605      char *name;
13606      int *loc;
13607      ChessProgramState *cps;
13608 {
13609   char buf[MSG_SIZ];
13610   int len = strlen(name);
13611   int val;
13612   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13613     (*p) += len + 1;
13614     sscanf(*p, "%d", &val);
13615     *loc = (val != 0);
13616     while (**p && **p != ' ') (*p)++;
13617     sprintf(buf, "accepted %s\n", name);
13618     SendToProgram(buf, cps);
13619     return TRUE;
13620   }
13621   return FALSE;
13622 }
13623
13624 int
13625 IntFeature(p, name, loc, cps)
13626      char **p;
13627      char *name;
13628      int *loc;
13629      ChessProgramState *cps;
13630 {
13631   char buf[MSG_SIZ];
13632   int len = strlen(name);
13633   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13634     (*p) += len + 1;
13635     sscanf(*p, "%d", loc);
13636     while (**p && **p != ' ') (*p)++;
13637     sprintf(buf, "accepted %s\n", name);
13638     SendToProgram(buf, cps);
13639     return TRUE;
13640   }
13641   return FALSE;
13642 }
13643
13644 int
13645 StringFeature(p, name, loc, cps)
13646      char **p;
13647      char *name;
13648      char loc[];
13649      ChessProgramState *cps;
13650 {
13651   char buf[MSG_SIZ];
13652   int len = strlen(name);
13653   if (strncmp((*p), name, len) == 0
13654       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13655     (*p) += len + 2;
13656     sscanf(*p, "%[^\"]", loc);
13657     while (**p && **p != '\"') (*p)++;
13658     if (**p == '\"') (*p)++;
13659     sprintf(buf, "accepted %s\n", name);
13660     SendToProgram(buf, cps);
13661     return TRUE;
13662   }
13663   return FALSE;
13664 }
13665
13666 int 
13667 ParseOption(Option *opt, ChessProgramState *cps)
13668 // [HGM] options: process the string that defines an engine option, and determine
13669 // name, type, default value, and allowed value range
13670 {
13671         char *p, *q, buf[MSG_SIZ];
13672         int n, min = (-1)<<31, max = 1<<31, def;
13673
13674         if(p = strstr(opt->name, " -spin ")) {
13675             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13676             if(max < min) max = min; // enforce consistency
13677             if(def < min) def = min;
13678             if(def > max) def = max;
13679             opt->value = def;
13680             opt->min = min;
13681             opt->max = max;
13682             opt->type = Spin;
13683         } else if((p = strstr(opt->name, " -slider "))) {
13684             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13685             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13686             if(max < min) max = min; // enforce consistency
13687             if(def < min) def = min;
13688             if(def > max) def = max;
13689             opt->value = def;
13690             opt->min = min;
13691             opt->max = max;
13692             opt->type = Spin; // Slider;
13693         } else if((p = strstr(opt->name, " -string "))) {
13694             opt->textValue = p+9;
13695             opt->type = TextBox;
13696         } else if((p = strstr(opt->name, " -file "))) {
13697             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13698             opt->textValue = p+7;
13699             opt->type = TextBox; // FileName;
13700         } else if((p = strstr(opt->name, " -path "))) {
13701             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13702             opt->textValue = p+7;
13703             opt->type = TextBox; // PathName;
13704         } else if(p = strstr(opt->name, " -check ")) {
13705             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13706             opt->value = (def != 0);
13707             opt->type = CheckBox;
13708         } else if(p = strstr(opt->name, " -combo ")) {
13709             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13710             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13711             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13712             opt->value = n = 0;
13713             while(q = StrStr(q, " /// ")) {
13714                 n++; *q = 0;    // count choices, and null-terminate each of them
13715                 q += 5;
13716                 if(*q == '*') { // remember default, which is marked with * prefix
13717                     q++;
13718                     opt->value = n;
13719                 }
13720                 cps->comboList[cps->comboCnt++] = q;
13721             }
13722             cps->comboList[cps->comboCnt++] = NULL;
13723             opt->max = n + 1;
13724             opt->type = ComboBox;
13725         } else if(p = strstr(opt->name, " -button")) {
13726             opt->type = Button;
13727         } else if(p = strstr(opt->name, " -save")) {
13728             opt->type = SaveButton;
13729         } else return FALSE;
13730         *p = 0; // terminate option name
13731         // now look if the command-line options define a setting for this engine option.
13732         if(cps->optionSettings && cps->optionSettings[0])
13733             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13734         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13735                 sprintf(buf, "option %s", p);
13736                 if(p = strstr(buf, ",")) *p = 0;
13737                 strcat(buf, "\n");
13738                 SendToProgram(buf, cps);
13739         }
13740         return TRUE;
13741 }
13742
13743 void
13744 FeatureDone(cps, val)
13745      ChessProgramState* cps;
13746      int val;
13747 {
13748   DelayedEventCallback cb = GetDelayedEvent();
13749   if ((cb == InitBackEnd3 && cps == &first) ||
13750       (cb == TwoMachinesEventIfReady && cps == &second)) {
13751     CancelDelayedEvent();
13752     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13753   }
13754   cps->initDone = val;
13755 }
13756
13757 /* Parse feature command from engine */
13758 void
13759 ParseFeatures(args, cps)
13760      char* args;
13761      ChessProgramState *cps;  
13762 {
13763   char *p = args;
13764   char *q;
13765   int val;
13766   char buf[MSG_SIZ];
13767
13768   for (;;) {
13769     while (*p == ' ') p++;
13770     if (*p == NULLCHAR) return;
13771
13772     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13773     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13774     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13775     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13776     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13777     if (BoolFeature(&p, "reuse", &val, cps)) {
13778       /* Engine can disable reuse, but can't enable it if user said no */
13779       if (!val) cps->reuse = FALSE;
13780       continue;
13781     }
13782     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13783     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13784       if (gameMode == TwoMachinesPlay) {
13785         DisplayTwoMachinesTitle();
13786       } else {
13787         DisplayTitle("");
13788       }
13789       continue;
13790     }
13791     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13792     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13793     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13794     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13795     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13796     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13797     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13798     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13799     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13800     if (IntFeature(&p, "done", &val, cps)) {
13801       FeatureDone(cps, val);
13802       continue;
13803     }
13804     /* Added by Tord: */
13805     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13806     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13807     /* End of additions by Tord */
13808
13809     /* [HGM] added features: */
13810     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13811     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13812     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13813     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13814     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13815     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13816     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13817         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13818             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13819             SendToProgram(buf, cps);
13820             continue;
13821         }
13822         if(cps->nrOptions >= MAX_OPTIONS) {
13823             cps->nrOptions--;
13824             sprintf(buf, "%s engine has too many options\n", cps->which);
13825             DisplayError(buf, 0);
13826         }
13827         continue;
13828     }
13829     /* End of additions by HGM */
13830
13831     /* unknown feature: complain and skip */
13832     q = p;
13833     while (*q && *q != '=') q++;
13834     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13835     SendToProgram(buf, cps);
13836     p = q;
13837     if (*p == '=') {
13838       p++;
13839       if (*p == '\"') {
13840         p++;
13841         while (*p && *p != '\"') p++;
13842         if (*p == '\"') p++;
13843       } else {
13844         while (*p && *p != ' ') p++;
13845       }
13846     }
13847   }
13848
13849 }
13850
13851 void
13852 PeriodicUpdatesEvent(newState)
13853      int newState;
13854 {
13855     if (newState == appData.periodicUpdates)
13856       return;
13857
13858     appData.periodicUpdates=newState;
13859
13860     /* Display type changes, so update it now */
13861 //    DisplayAnalysis();
13862
13863     /* Get the ball rolling again... */
13864     if (newState) {
13865         AnalysisPeriodicEvent(1);
13866         StartAnalysisClock();
13867     }
13868 }
13869
13870 void
13871 PonderNextMoveEvent(newState)
13872      int newState;
13873 {
13874     if (newState == appData.ponderNextMove) return;
13875     if (gameMode == EditPosition) EditPositionDone(TRUE);
13876     if (newState) {
13877         SendToProgram("hard\n", &first);
13878         if (gameMode == TwoMachinesPlay) {
13879             SendToProgram("hard\n", &second);
13880         }
13881     } else {
13882         SendToProgram("easy\n", &first);
13883         thinkOutput[0] = NULLCHAR;
13884         if (gameMode == TwoMachinesPlay) {
13885             SendToProgram("easy\n", &second);
13886         }
13887     }
13888     appData.ponderNextMove = newState;
13889 }
13890
13891 void
13892 NewSettingEvent(option, feature, command, value)
13893      char *command;
13894      int option, value, *feature;
13895 {
13896     char buf[MSG_SIZ];
13897
13898     if (gameMode == EditPosition) EditPositionDone(TRUE);
13899     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13900     if(feature == NULL || *feature) SendToProgram(buf, &first);
13901     if (gameMode == TwoMachinesPlay) {
13902         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13903     }
13904 }
13905
13906 void
13907 ShowThinkingEvent()
13908 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13909 {
13910     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13911     int newState = appData.showThinking
13912         // [HGM] thinking: other features now need thinking output as well
13913         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13914     
13915     if (oldState == newState) return;
13916     oldState = newState;
13917     if (gameMode == EditPosition) EditPositionDone(TRUE);
13918     if (oldState) {
13919         SendToProgram("post\n", &first);
13920         if (gameMode == TwoMachinesPlay) {
13921             SendToProgram("post\n", &second);
13922         }
13923     } else {
13924         SendToProgram("nopost\n", &first);
13925         thinkOutput[0] = NULLCHAR;
13926         if (gameMode == TwoMachinesPlay) {
13927             SendToProgram("nopost\n", &second);
13928         }
13929     }
13930 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13931 }
13932
13933 void
13934 AskQuestionEvent(title, question, replyPrefix, which)
13935      char *title; char *question; char *replyPrefix; char *which;
13936 {
13937   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13938   if (pr == NoProc) return;
13939   AskQuestion(title, question, replyPrefix, pr);
13940 }
13941
13942 void
13943 DisplayMove(moveNumber)
13944      int moveNumber;
13945 {
13946     char message[MSG_SIZ];
13947     char res[MSG_SIZ];
13948     char cpThinkOutput[MSG_SIZ];
13949
13950     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13951     
13952     if (moveNumber == forwardMostMove - 1 || 
13953         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13954
13955         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13956
13957         if (strchr(cpThinkOutput, '\n')) {
13958             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13959         }
13960     } else {
13961         *cpThinkOutput = NULLCHAR;
13962     }
13963
13964     /* [AS] Hide thinking from human user */
13965     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13966         *cpThinkOutput = NULLCHAR;
13967         if( thinkOutput[0] != NULLCHAR ) {
13968             int i;
13969
13970             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13971                 cpThinkOutput[i] = '.';
13972             }
13973             cpThinkOutput[i] = NULLCHAR;
13974             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13975         }
13976     }
13977
13978     if (moveNumber == forwardMostMove - 1 &&
13979         gameInfo.resultDetails != NULL) {
13980         if (gameInfo.resultDetails[0] == NULLCHAR) {
13981             sprintf(res, " %s", PGNResult(gameInfo.result));
13982         } else {
13983             sprintf(res, " {%s} %s",
13984                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
13985         }
13986     } else {
13987         res[0] = NULLCHAR;
13988     }
13989
13990     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13991         DisplayMessage(res, cpThinkOutput);
13992     } else {
13993         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13994                 WhiteOnMove(moveNumber) ? " " : ".. ",
13995                 parseList[moveNumber], res);
13996         DisplayMessage(message, cpThinkOutput);
13997     }
13998 }
13999
14000 void
14001 DisplayComment(moveNumber, text)
14002      int moveNumber;
14003      char *text;
14004 {
14005     char title[MSG_SIZ];
14006     char buf[8000]; // comment can be long!
14007     int score, depth;
14008     
14009     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14010       strcpy(title, "Comment");
14011     } else {
14012       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
14013               WhiteOnMove(moveNumber) ? " " : ".. ",
14014               parseList[moveNumber]);
14015     }
14016     // [HGM] PV info: display PV info together with (or as) comment
14017     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14018       if(text == NULL) text = "";                                           
14019       score = pvInfoList[moveNumber].score;
14020       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14021               depth, (pvInfoList[moveNumber].time+50)/100, text);
14022       text = buf;
14023     }
14024     if (text != NULL && (appData.autoDisplayComment || commentUp))
14025         CommentPopUp(title, text);
14026 }
14027
14028 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14029  * might be busy thinking or pondering.  It can be omitted if your
14030  * gnuchess is configured to stop thinking immediately on any user
14031  * input.  However, that gnuchess feature depends on the FIONREAD
14032  * ioctl, which does not work properly on some flavors of Unix.
14033  */
14034 void
14035 Attention(cps)
14036      ChessProgramState *cps;
14037 {
14038 #if ATTENTION
14039     if (!cps->useSigint) return;
14040     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14041     switch (gameMode) {
14042       case MachinePlaysWhite:
14043       case MachinePlaysBlack:
14044       case TwoMachinesPlay:
14045       case IcsPlayingWhite:
14046       case IcsPlayingBlack:
14047       case AnalyzeMode:
14048       case AnalyzeFile:
14049         /* Skip if we know it isn't thinking */
14050         if (!cps->maybeThinking) return;
14051         if (appData.debugMode)
14052           fprintf(debugFP, "Interrupting %s\n", cps->which);
14053         InterruptChildProcess(cps->pr);
14054         cps->maybeThinking = FALSE;
14055         break;
14056       default:
14057         break;
14058     }
14059 #endif /*ATTENTION*/
14060 }
14061
14062 int
14063 CheckFlags()
14064 {
14065     if (whiteTimeRemaining <= 0) {
14066         if (!whiteFlag) {
14067             whiteFlag = TRUE;
14068             if (appData.icsActive) {
14069                 if (appData.autoCallFlag &&
14070                     gameMode == IcsPlayingBlack && !blackFlag) {
14071                   SendToICS(ics_prefix);
14072                   SendToICS("flag\n");
14073                 }
14074             } else {
14075                 if (blackFlag) {
14076                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14077                 } else {
14078                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14079                     if (appData.autoCallFlag) {
14080                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14081                         return TRUE;
14082                     }
14083                 }
14084             }
14085         }
14086     }
14087     if (blackTimeRemaining <= 0) {
14088         if (!blackFlag) {
14089             blackFlag = TRUE;
14090             if (appData.icsActive) {
14091                 if (appData.autoCallFlag &&
14092                     gameMode == IcsPlayingWhite && !whiteFlag) {
14093                   SendToICS(ics_prefix);
14094                   SendToICS("flag\n");
14095                 }
14096             } else {
14097                 if (whiteFlag) {
14098                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14099                 } else {
14100                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14101                     if (appData.autoCallFlag) {
14102                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14103                         return TRUE;
14104                     }
14105                 }
14106             }
14107         }
14108     }
14109     return FALSE;
14110 }
14111
14112 void
14113 CheckTimeControl()
14114 {
14115     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14116         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14117
14118     /*
14119      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14120      */
14121     if ( !WhiteOnMove(forwardMostMove) )
14122         /* White made time control */
14123         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14124         /* [HGM] time odds: correct new time quota for time odds! */
14125                                             / WhitePlayer()->timeOdds;
14126       else
14127         /* Black made time control */
14128         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14129                                             / WhitePlayer()->other->timeOdds;
14130 }
14131
14132 void
14133 DisplayBothClocks()
14134 {
14135     int wom = gameMode == EditPosition ?
14136       !blackPlaysFirst : WhiteOnMove(currentMove);
14137     DisplayWhiteClock(whiteTimeRemaining, wom);
14138     DisplayBlackClock(blackTimeRemaining, !wom);
14139 }
14140
14141
14142 /* Timekeeping seems to be a portability nightmare.  I think everyone
14143    has ftime(), but I'm really not sure, so I'm including some ifdefs
14144    to use other calls if you don't.  Clocks will be less accurate if
14145    you have neither ftime nor gettimeofday.
14146 */
14147
14148 /* VS 2008 requires the #include outside of the function */
14149 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14150 #include <sys/timeb.h>
14151 #endif
14152
14153 /* Get the current time as a TimeMark */
14154 void
14155 GetTimeMark(tm)
14156      TimeMark *tm;
14157 {
14158 #if HAVE_GETTIMEOFDAY
14159
14160     struct timeval timeVal;
14161     struct timezone timeZone;
14162
14163     gettimeofday(&timeVal, &timeZone);
14164     tm->sec = (long) timeVal.tv_sec; 
14165     tm->ms = (int) (timeVal.tv_usec / 1000L);
14166
14167 #else /*!HAVE_GETTIMEOFDAY*/
14168 #if HAVE_FTIME
14169
14170 // include <sys/timeb.h> / moved to just above start of function
14171     struct timeb timeB;
14172
14173     ftime(&timeB);
14174     tm->sec = (long) timeB.time;
14175     tm->ms = (int) timeB.millitm;
14176
14177 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14178     tm->sec = (long) time(NULL);
14179     tm->ms = 0;
14180 #endif
14181 #endif
14182 }
14183
14184 /* Return the difference in milliseconds between two
14185    time marks.  We assume the difference will fit in a long!
14186 */
14187 long
14188 SubtractTimeMarks(tm2, tm1)
14189      TimeMark *tm2, *tm1;
14190 {
14191     return 1000L*(tm2->sec - tm1->sec) +
14192            (long) (tm2->ms - tm1->ms);
14193 }
14194
14195
14196 /*
14197  * Code to manage the game clocks.
14198  *
14199  * In tournament play, black starts the clock and then white makes a move.
14200  * We give the human user a slight advantage if he is playing white---the
14201  * clocks don't run until he makes his first move, so it takes zero time.
14202  * Also, we don't account for network lag, so we could get out of sync
14203  * with GNU Chess's clock -- but then, referees are always right.  
14204  */
14205
14206 static TimeMark tickStartTM;
14207 static long intendedTickLength;
14208
14209 long
14210 NextTickLength(timeRemaining)
14211      long timeRemaining;
14212 {
14213     long nominalTickLength, nextTickLength;
14214
14215     if (timeRemaining > 0L && timeRemaining <= 10000L)
14216       nominalTickLength = 100L;
14217     else
14218       nominalTickLength = 1000L;
14219     nextTickLength = timeRemaining % nominalTickLength;
14220     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14221
14222     return nextTickLength;
14223 }
14224
14225 /* Adjust clock one minute up or down */
14226 void
14227 AdjustClock(Boolean which, int dir)
14228 {
14229     if(which) blackTimeRemaining += 60000*dir;
14230     else      whiteTimeRemaining += 60000*dir;
14231     DisplayBothClocks();
14232 }
14233
14234 /* Stop clocks and reset to a fresh time control */
14235 void
14236 ResetClocks() 
14237 {
14238     (void) StopClockTimer();
14239     if (appData.icsActive) {
14240         whiteTimeRemaining = blackTimeRemaining = 0;
14241     } else if (searchTime) {
14242         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14243         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14244     } else { /* [HGM] correct new time quote for time odds */
14245         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14246         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14247     }
14248     if (whiteFlag || blackFlag) {
14249         DisplayTitle("");
14250         whiteFlag = blackFlag = FALSE;
14251     }
14252     DisplayBothClocks();
14253 }
14254
14255 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14256
14257 /* Decrement running clock by amount of time that has passed */
14258 void
14259 DecrementClocks()
14260 {
14261     long timeRemaining;
14262     long lastTickLength, fudge;
14263     TimeMark now;
14264
14265     if (!appData.clockMode) return;
14266     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14267         
14268     GetTimeMark(&now);
14269
14270     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14271
14272     /* Fudge if we woke up a little too soon */
14273     fudge = intendedTickLength - lastTickLength;
14274     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14275
14276     if (WhiteOnMove(forwardMostMove)) {
14277         if(whiteNPS >= 0) lastTickLength = 0;
14278         timeRemaining = whiteTimeRemaining -= lastTickLength;
14279         DisplayWhiteClock(whiteTimeRemaining - fudge,
14280                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14281     } else {
14282         if(blackNPS >= 0) lastTickLength = 0;
14283         timeRemaining = blackTimeRemaining -= lastTickLength;
14284         DisplayBlackClock(blackTimeRemaining - fudge,
14285                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14286     }
14287
14288     if (CheckFlags()) return;
14289         
14290     tickStartTM = now;
14291     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14292     StartClockTimer(intendedTickLength);
14293
14294     /* if the time remaining has fallen below the alarm threshold, sound the
14295      * alarm. if the alarm has sounded and (due to a takeback or time control
14296      * with increment) the time remaining has increased to a level above the
14297      * threshold, reset the alarm so it can sound again. 
14298      */
14299     
14300     if (appData.icsActive && appData.icsAlarm) {
14301
14302         /* make sure we are dealing with the user's clock */
14303         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14304                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14305            )) return;
14306
14307         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14308             alarmSounded = FALSE;
14309         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14310             PlayAlarmSound();
14311             alarmSounded = TRUE;
14312         }
14313     }
14314 }
14315
14316
14317 /* A player has just moved, so stop the previously running
14318    clock and (if in clock mode) start the other one.
14319    We redisplay both clocks in case we're in ICS mode, because
14320    ICS gives us an update to both clocks after every move.
14321    Note that this routine is called *after* forwardMostMove
14322    is updated, so the last fractional tick must be subtracted
14323    from the color that is *not* on move now.
14324 */
14325 void
14326 SwitchClocks(int newMoveNr)
14327 {
14328     long lastTickLength;
14329     TimeMark now;
14330     int flagged = FALSE;
14331
14332     GetTimeMark(&now);
14333
14334     if (StopClockTimer() && appData.clockMode) {
14335         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14336         if (!WhiteOnMove(forwardMostMove)) {
14337             if(blackNPS >= 0) lastTickLength = 0;
14338             blackTimeRemaining -= lastTickLength;
14339            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14340 //         if(pvInfoList[forwardMostMove-1].time == -1)
14341                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14342                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14343         } else {
14344            if(whiteNPS >= 0) lastTickLength = 0;
14345            whiteTimeRemaining -= lastTickLength;
14346            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14347 //         if(pvInfoList[forwardMostMove-1].time == -1)
14348                  pvInfoList[forwardMostMove-1].time = 
14349                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14350         }
14351         flagged = CheckFlags();
14352     }
14353     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14354     CheckTimeControl();
14355
14356     if (flagged || !appData.clockMode) return;
14357
14358     switch (gameMode) {
14359       case MachinePlaysBlack:
14360       case MachinePlaysWhite:
14361       case BeginningOfGame:
14362         if (pausing) return;
14363         break;
14364
14365       case EditGame:
14366       case PlayFromGameFile:
14367       case IcsExamining:
14368         return;
14369
14370       default:
14371         break;
14372     }
14373
14374     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14375         if(WhiteOnMove(forwardMostMove))
14376              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14377         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14378     }
14379
14380     tickStartTM = now;
14381     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14382       whiteTimeRemaining : blackTimeRemaining);
14383     StartClockTimer(intendedTickLength);
14384 }
14385         
14386
14387 /* Stop both clocks */
14388 void
14389 StopClocks()
14390 {       
14391     long lastTickLength;
14392     TimeMark now;
14393
14394     if (!StopClockTimer()) return;
14395     if (!appData.clockMode) return;
14396
14397     GetTimeMark(&now);
14398
14399     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14400     if (WhiteOnMove(forwardMostMove)) {
14401         if(whiteNPS >= 0) lastTickLength = 0;
14402         whiteTimeRemaining -= lastTickLength;
14403         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14404     } else {
14405         if(blackNPS >= 0) lastTickLength = 0;
14406         blackTimeRemaining -= lastTickLength;
14407         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14408     }
14409     CheckFlags();
14410 }
14411         
14412 /* Start clock of player on move.  Time may have been reset, so
14413    if clock is already running, stop and restart it. */
14414 void
14415 StartClocks()
14416 {
14417     (void) StopClockTimer(); /* in case it was running already */
14418     DisplayBothClocks();
14419     if (CheckFlags()) return;
14420
14421     if (!appData.clockMode) return;
14422     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14423
14424     GetTimeMark(&tickStartTM);
14425     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14426       whiteTimeRemaining : blackTimeRemaining);
14427
14428    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14429     whiteNPS = blackNPS = -1; 
14430     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14431        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14432         whiteNPS = first.nps;
14433     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14434        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14435         blackNPS = first.nps;
14436     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14437         whiteNPS = second.nps;
14438     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14439         blackNPS = second.nps;
14440     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14441
14442     StartClockTimer(intendedTickLength);
14443 }
14444
14445 char *
14446 TimeString(ms)
14447      long ms;
14448 {
14449     long second, minute, hour, day;
14450     char *sign = "";
14451     static char buf[32];
14452     
14453     if (ms > 0 && ms <= 9900) {
14454       /* convert milliseconds to tenths, rounding up */
14455       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14456
14457       sprintf(buf, " %03.1f ", tenths/10.0);
14458       return buf;
14459     }
14460
14461     /* convert milliseconds to seconds, rounding up */
14462     /* use floating point to avoid strangeness of integer division
14463        with negative dividends on many machines */
14464     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14465
14466     if (second < 0) {
14467         sign = "-";
14468         second = -second;
14469     }
14470     
14471     day = second / (60 * 60 * 24);
14472     second = second % (60 * 60 * 24);
14473     hour = second / (60 * 60);
14474     second = second % (60 * 60);
14475     minute = second / 60;
14476     second = second % 60;
14477     
14478     if (day > 0)
14479       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14480               sign, day, hour, minute, second);
14481     else if (hour > 0)
14482       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14483     else
14484       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14485     
14486     return buf;
14487 }
14488
14489
14490 /*
14491  * This is necessary because some C libraries aren't ANSI C compliant yet.
14492  */
14493 char *
14494 StrStr(string, match)
14495      char *string, *match;
14496 {
14497     int i, length;
14498     
14499     length = strlen(match);
14500     
14501     for (i = strlen(string) - length; i >= 0; i--, string++)
14502       if (!strncmp(match, string, length))
14503         return string;
14504     
14505     return NULL;
14506 }
14507
14508 char *
14509 StrCaseStr(string, match)
14510      char *string, *match;
14511 {
14512     int i, j, length;
14513     
14514     length = strlen(match);
14515     
14516     for (i = strlen(string) - length; i >= 0; i--, string++) {
14517         for (j = 0; j < length; j++) {
14518             if (ToLower(match[j]) != ToLower(string[j]))
14519               break;
14520         }
14521         if (j == length) return string;
14522     }
14523
14524     return NULL;
14525 }
14526
14527 #ifndef _amigados
14528 int
14529 StrCaseCmp(s1, s2)
14530      char *s1, *s2;
14531 {
14532     char c1, c2;
14533     
14534     for (;;) {
14535         c1 = ToLower(*s1++);
14536         c2 = ToLower(*s2++);
14537         if (c1 > c2) return 1;
14538         if (c1 < c2) return -1;
14539         if (c1 == NULLCHAR) return 0;
14540     }
14541 }
14542
14543
14544 int
14545 ToLower(c)
14546      int c;
14547 {
14548     return isupper(c) ? tolower(c) : c;
14549 }
14550
14551
14552 int
14553 ToUpper(c)
14554      int c;
14555 {
14556     return islower(c) ? toupper(c) : c;
14557 }
14558 #endif /* !_amigados    */
14559
14560 char *
14561 StrSave(s)
14562      char *s;
14563 {
14564     char *ret;
14565
14566     if ((ret = (char *) malloc(strlen(s) + 1))) {
14567         strcpy(ret, s);
14568     }
14569     return ret;
14570 }
14571
14572 char *
14573 StrSavePtr(s, savePtr)
14574      char *s, **savePtr;
14575 {
14576     if (*savePtr) {
14577         free(*savePtr);
14578     }
14579     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14580         strcpy(*savePtr, s);
14581     }
14582     return(*savePtr);
14583 }
14584
14585 char *
14586 PGNDate()
14587 {
14588     time_t clock;
14589     struct tm *tm;
14590     char buf[MSG_SIZ];
14591
14592     clock = time((time_t *)NULL);
14593     tm = localtime(&clock);
14594     sprintf(buf, "%04d.%02d.%02d",
14595             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14596     return StrSave(buf);
14597 }
14598
14599
14600 char *
14601 PositionToFEN(move, overrideCastling)
14602      int move;
14603      char *overrideCastling;
14604 {
14605     int i, j, fromX, fromY, toX, toY;
14606     int whiteToPlay;
14607     char buf[128];
14608     char *p, *q;
14609     int emptycount;
14610     ChessSquare piece;
14611
14612     whiteToPlay = (gameMode == EditPosition) ?
14613       !blackPlaysFirst : (move % 2 == 0);
14614     p = buf;
14615
14616     /* Piece placement data */
14617     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14618         emptycount = 0;
14619         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14620             if (boards[move][i][j] == EmptySquare) {
14621                 emptycount++;
14622             } else { ChessSquare piece = boards[move][i][j];
14623                 if (emptycount > 0) {
14624                     if(emptycount<10) /* [HGM] can be >= 10 */
14625                         *p++ = '0' + emptycount;
14626                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14627                     emptycount = 0;
14628                 }
14629                 if(PieceToChar(piece) == '+') {
14630                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14631                     *p++ = '+';
14632                     piece = (ChessSquare)(DEMOTED piece);
14633                 } 
14634                 *p++ = PieceToChar(piece);
14635                 if(p[-1] == '~') {
14636                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14637                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14638                     *p++ = '~';
14639                 }
14640             }
14641         }
14642         if (emptycount > 0) {
14643             if(emptycount<10) /* [HGM] can be >= 10 */
14644                 *p++ = '0' + emptycount;
14645             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14646             emptycount = 0;
14647         }
14648         *p++ = '/';
14649     }
14650     *(p - 1) = ' ';
14651
14652     /* [HGM] print Crazyhouse or Shogi holdings */
14653     if( gameInfo.holdingsWidth ) {
14654         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14655         q = p;
14656         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14657             piece = boards[move][i][BOARD_WIDTH-1];
14658             if( piece != EmptySquare )
14659               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14660                   *p++ = PieceToChar(piece);
14661         }
14662         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14663             piece = boards[move][BOARD_HEIGHT-i-1][0];
14664             if( piece != EmptySquare )
14665               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14666                   *p++ = PieceToChar(piece);
14667         }
14668
14669         if( q == p ) *p++ = '-';
14670         *p++ = ']';
14671         *p++ = ' ';
14672     }
14673
14674     /* Active color */
14675     *p++ = whiteToPlay ? 'w' : 'b';
14676     *p++ = ' ';
14677
14678   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14679     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14680   } else {
14681   if(nrCastlingRights) {
14682      q = p;
14683      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14684        /* [HGM] write directly from rights */
14685            if(boards[move][CASTLING][2] != NoRights &&
14686               boards[move][CASTLING][0] != NoRights   )
14687                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14688            if(boards[move][CASTLING][2] != NoRights &&
14689               boards[move][CASTLING][1] != NoRights   )
14690                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14691            if(boards[move][CASTLING][5] != NoRights &&
14692               boards[move][CASTLING][3] != NoRights   )
14693                 *p++ = boards[move][CASTLING][3] + AAA;
14694            if(boards[move][CASTLING][5] != NoRights &&
14695               boards[move][CASTLING][4] != NoRights   )
14696                 *p++ = boards[move][CASTLING][4] + AAA;
14697      } else {
14698
14699         /* [HGM] write true castling rights */
14700         if( nrCastlingRights == 6 ) {
14701             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14702                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14703             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14704                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14705             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14706                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14707             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14708                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14709         }
14710      }
14711      if (q == p) *p++ = '-'; /* No castling rights */
14712      *p++ = ' ';
14713   }
14714
14715   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14716      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14717     /* En passant target square */
14718     if (move > backwardMostMove) {
14719         fromX = moveList[move - 1][0] - AAA;
14720         fromY = moveList[move - 1][1] - ONE;
14721         toX = moveList[move - 1][2] - AAA;
14722         toY = moveList[move - 1][3] - ONE;
14723         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14724             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14725             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14726             fromX == toX) {
14727             /* 2-square pawn move just happened */
14728             *p++ = toX + AAA;
14729             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14730         } else {
14731             *p++ = '-';
14732         }
14733     } else if(move == backwardMostMove) {
14734         // [HGM] perhaps we should always do it like this, and forget the above?
14735         if((signed char)boards[move][EP_STATUS] >= 0) {
14736             *p++ = boards[move][EP_STATUS] + AAA;
14737             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14738         } else {
14739             *p++ = '-';
14740         }
14741     } else {
14742         *p++ = '-';
14743     }
14744     *p++ = ' ';
14745   }
14746   }
14747
14748     /* [HGM] find reversible plies */
14749     {   int i = 0, j=move;
14750
14751         if (appData.debugMode) { int k;
14752             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14753             for(k=backwardMostMove; k<=forwardMostMove; k++)
14754                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14755
14756         }
14757
14758         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14759         if( j == backwardMostMove ) i += initialRulePlies;
14760         sprintf(p, "%d ", i);
14761         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14762     }
14763     /* Fullmove number */
14764     sprintf(p, "%d", (move / 2) + 1);
14765     
14766     return StrSave(buf);
14767 }
14768
14769 Boolean
14770 ParseFEN(board, blackPlaysFirst, fen)
14771     Board board;
14772      int *blackPlaysFirst;
14773      char *fen;
14774 {
14775     int i, j;
14776     char *p, c;
14777     int emptycount;
14778     ChessSquare piece;
14779
14780     p = fen;
14781
14782     /* [HGM] by default clear Crazyhouse holdings, if present */
14783     if(gameInfo.holdingsWidth) {
14784        for(i=0; i<BOARD_HEIGHT; i++) {
14785            board[i][0]             = EmptySquare; /* black holdings */
14786            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14787            board[i][1]             = (ChessSquare) 0; /* black counts */
14788            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14789        }
14790     }
14791
14792     /* Piece placement data */
14793     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14794         j = 0;
14795         for (;;) {
14796             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14797                 if (*p == '/') p++;
14798                 emptycount = gameInfo.boardWidth - j;
14799                 while (emptycount--)
14800                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14801                 break;
14802 #if(BOARD_FILES >= 10)
14803             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14804                 p++; emptycount=10;
14805                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14806                 while (emptycount--)
14807                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14808 #endif
14809             } else if (isdigit(*p)) {
14810                 emptycount = *p++ - '0';
14811                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14812                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14813                 while (emptycount--)
14814                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14815             } else if (*p == '+' || isalpha(*p)) {
14816                 if (j >= gameInfo.boardWidth) return FALSE;
14817                 if(*p=='+') {
14818                     piece = CharToPiece(*++p);
14819                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14820                     piece = (ChessSquare) (PROMOTED piece ); p++;
14821                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14822                 } else piece = CharToPiece(*p++);
14823
14824                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14825                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14826                     piece = (ChessSquare) (PROMOTED piece);
14827                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14828                     p++;
14829                 }
14830                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14831             } else {
14832                 return FALSE;
14833             }
14834         }
14835     }
14836     while (*p == '/' || *p == ' ') p++;
14837
14838     /* [HGM] look for Crazyhouse holdings here */
14839     while(*p==' ') p++;
14840     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14841         if(*p == '[') p++;
14842         if(*p == '-' ) *p++; /* empty holdings */ else {
14843             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14844             /* if we would allow FEN reading to set board size, we would   */
14845             /* have to add holdings and shift the board read so far here   */
14846             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14847                 *p++;
14848                 if((int) piece >= (int) BlackPawn ) {
14849                     i = (int)piece - (int)BlackPawn;
14850                     i = PieceToNumber((ChessSquare)i);
14851                     if( i >= gameInfo.holdingsSize ) return FALSE;
14852                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14853                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14854                 } else {
14855                     i = (int)piece - (int)WhitePawn;
14856                     i = PieceToNumber((ChessSquare)i);
14857                     if( i >= gameInfo.holdingsSize ) return FALSE;
14858                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14859                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14860                 }
14861             }
14862         }
14863         if(*p == ']') *p++;
14864     }
14865
14866     while(*p == ' ') p++;
14867
14868     /* Active color */
14869     c = *p++;
14870     if(appData.colorNickNames) {
14871       if( c == appData.colorNickNames[0] ) c = 'w'; else
14872       if( c == appData.colorNickNames[1] ) c = 'b';
14873     }
14874     switch (c) {
14875       case 'w':
14876         *blackPlaysFirst = FALSE;
14877         break;
14878       case 'b': 
14879         *blackPlaysFirst = TRUE;
14880         break;
14881       default:
14882         return FALSE;
14883     }
14884
14885     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14886     /* return the extra info in global variiables             */
14887
14888     /* set defaults in case FEN is incomplete */
14889     board[EP_STATUS] = EP_UNKNOWN;
14890     for(i=0; i<nrCastlingRights; i++ ) {
14891         board[CASTLING][i] =
14892             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14893     }   /* assume possible unless obviously impossible */
14894     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14895     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14896     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14897                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14898     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14899     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14900     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14901                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14902     FENrulePlies = 0;
14903
14904     while(*p==' ') p++;
14905     if(nrCastlingRights) {
14906       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14907           /* castling indicator present, so default becomes no castlings */
14908           for(i=0; i<nrCastlingRights; i++ ) {
14909                  board[CASTLING][i] = NoRights;
14910           }
14911       }
14912       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14913              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14914              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14915              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14916         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14917
14918         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14919             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14920             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14921         }
14922         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14923             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14924         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14925                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14926         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14927                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14928         switch(c) {
14929           case'K':
14930               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14931               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14932               board[CASTLING][2] = whiteKingFile;
14933               break;
14934           case'Q':
14935               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14936               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14937               board[CASTLING][2] = whiteKingFile;
14938               break;
14939           case'k':
14940               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14941               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14942               board[CASTLING][5] = blackKingFile;
14943               break;
14944           case'q':
14945               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14946               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14947               board[CASTLING][5] = blackKingFile;
14948           case '-':
14949               break;
14950           default: /* FRC castlings */
14951               if(c >= 'a') { /* black rights */
14952                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14953                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14954                   if(i == BOARD_RGHT) break;
14955                   board[CASTLING][5] = i;
14956                   c -= AAA;
14957                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14958                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14959                   if(c > i)
14960                       board[CASTLING][3] = c;
14961                   else
14962                       board[CASTLING][4] = c;
14963               } else { /* white rights */
14964                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14965                     if(board[0][i] == WhiteKing) break;
14966                   if(i == BOARD_RGHT) break;
14967                   board[CASTLING][2] = i;
14968                   c -= AAA - 'a' + 'A';
14969                   if(board[0][c] >= WhiteKing) break;
14970                   if(c > i)
14971                       board[CASTLING][0] = c;
14972                   else
14973                       board[CASTLING][1] = c;
14974               }
14975         }
14976       }
14977       for(i=0; i<nrCastlingRights; i++)
14978         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14979     if (appData.debugMode) {
14980         fprintf(debugFP, "FEN castling rights:");
14981         for(i=0; i<nrCastlingRights; i++)
14982         fprintf(debugFP, " %d", board[CASTLING][i]);
14983         fprintf(debugFP, "\n");
14984     }
14985
14986       while(*p==' ') p++;
14987     }
14988
14989     /* read e.p. field in games that know e.p. capture */
14990     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14991        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14992       if(*p=='-') {
14993         p++; board[EP_STATUS] = EP_NONE;
14994       } else {
14995          char c = *p++ - AAA;
14996
14997          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14998          if(*p >= '0' && *p <='9') *p++;
14999          board[EP_STATUS] = c;
15000       }
15001     }
15002
15003
15004     if(sscanf(p, "%d", &i) == 1) {
15005         FENrulePlies = i; /* 50-move ply counter */
15006         /* (The move number is still ignored)    */
15007     }
15008
15009     return TRUE;
15010 }
15011       
15012 void
15013 EditPositionPasteFEN(char *fen)
15014 {
15015   if (fen != NULL) {
15016     Board initial_position;
15017
15018     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15019       DisplayError(_("Bad FEN position in clipboard"), 0);
15020       return ;
15021     } else {
15022       int savedBlackPlaysFirst = blackPlaysFirst;
15023       EditPositionEvent();
15024       blackPlaysFirst = savedBlackPlaysFirst;
15025       CopyBoard(boards[0], initial_position);
15026       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15027       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15028       DisplayBothClocks();
15029       DrawPosition(FALSE, boards[currentMove]);
15030     }
15031   }
15032 }
15033
15034 static char cseq[12] = "\\   ";
15035
15036 Boolean set_cont_sequence(char *new_seq)
15037 {
15038     int len;
15039     Boolean ret;
15040
15041     // handle bad attempts to set the sequence
15042         if (!new_seq)
15043                 return 0; // acceptable error - no debug
15044
15045     len = strlen(new_seq);
15046     ret = (len > 0) && (len < sizeof(cseq));
15047     if (ret)
15048         strcpy(cseq, new_seq);
15049     else if (appData.debugMode)
15050         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15051     return ret;
15052 }
15053
15054 /*
15055     reformat a source message so words don't cross the width boundary.  internal
15056     newlines are not removed.  returns the wrapped size (no null character unless
15057     included in source message).  If dest is NULL, only calculate the size required
15058     for the dest buffer.  lp argument indicats line position upon entry, and it's
15059     passed back upon exit.
15060 */
15061 int wrap(char *dest, char *src, int count, int width, int *lp)
15062 {
15063     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15064
15065     cseq_len = strlen(cseq);
15066     old_line = line = *lp;
15067     ansi = len = clen = 0;
15068
15069     for (i=0; i < count; i++)
15070     {
15071         if (src[i] == '\033')
15072             ansi = 1;
15073
15074         // if we hit the width, back up
15075         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15076         {
15077             // store i & len in case the word is too long
15078             old_i = i, old_len = len;
15079
15080             // find the end of the last word
15081             while (i && src[i] != ' ' && src[i] != '\n')
15082             {
15083                 i--;
15084                 len--;
15085             }
15086
15087             // word too long?  restore i & len before splitting it
15088             if ((old_i-i+clen) >= width)
15089             {
15090                 i = old_i;
15091                 len = old_len;
15092             }
15093
15094             // extra space?
15095             if (i && src[i-1] == ' ')
15096                 len--;
15097
15098             if (src[i] != ' ' && src[i] != '\n')
15099             {
15100                 i--;
15101                 if (len)
15102                     len--;
15103             }
15104
15105             // now append the newline and continuation sequence
15106             if (dest)
15107                 dest[len] = '\n';
15108             len++;
15109             if (dest)
15110                 strncpy(dest+len, cseq, cseq_len);
15111             len += cseq_len;
15112             line = cseq_len;
15113             clen = cseq_len;
15114             continue;
15115         }
15116
15117         if (dest)
15118             dest[len] = src[i];
15119         len++;
15120         if (!ansi)
15121             line++;
15122         if (src[i] == '\n')
15123             line = 0;
15124         if (src[i] == 'm')
15125             ansi = 0;
15126     }
15127     if (dest && appData.debugMode)
15128     {
15129         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15130             count, width, line, len, *lp);
15131         show_bytes(debugFP, src, count);
15132         fprintf(debugFP, "\ndest: ");
15133         show_bytes(debugFP, dest, len);
15134         fprintf(debugFP, "\n");
15135     }
15136     *lp = dest ? line : old_line;
15137
15138     return len;
15139 }
15140
15141 // [HGM] vari: routines for shelving variations
15142
15143 void 
15144 PushTail(int firstMove, int lastMove)
15145 {
15146         int i, j, nrMoves = lastMove - firstMove;
15147
15148         if(appData.icsActive) { // only in local mode
15149                 forwardMostMove = currentMove; // mimic old ICS behavior
15150                 return;
15151         }
15152         if(storedGames >= MAX_VARIATIONS-1) return;
15153
15154         // push current tail of game on stack
15155         savedResult[storedGames] = gameInfo.result;
15156         savedDetails[storedGames] = gameInfo.resultDetails;
15157         gameInfo.resultDetails = NULL;
15158         savedFirst[storedGames] = firstMove;
15159         savedLast [storedGames] = lastMove;
15160         savedFramePtr[storedGames] = framePtr;
15161         framePtr -= nrMoves; // reserve space for the boards
15162         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15163             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15164             for(j=0; j<MOVE_LEN; j++)
15165                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15166             for(j=0; j<2*MOVE_LEN; j++)
15167                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15168             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15169             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15170             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15171             pvInfoList[firstMove+i-1].depth = 0;
15172             commentList[framePtr+i] = commentList[firstMove+i];
15173             commentList[firstMove+i] = NULL;
15174         }
15175
15176         storedGames++;
15177         forwardMostMove = firstMove; // truncate game so we can start variation
15178         if(storedGames == 1) GreyRevert(FALSE);
15179 }
15180
15181 Boolean
15182 PopTail(Boolean annotate)
15183 {
15184         int i, j, nrMoves;
15185         char buf[8000], moveBuf[20];
15186
15187         if(appData.icsActive) return FALSE; // only in local mode
15188         if(!storedGames) return FALSE; // sanity
15189         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15190
15191         storedGames--;
15192         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15193         nrMoves = savedLast[storedGames] - currentMove;
15194         if(annotate) {
15195                 int cnt = 10;
15196                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15197                 else strcpy(buf, "(");
15198                 for(i=currentMove; i<forwardMostMove; i++) {
15199                         if(WhiteOnMove(i))
15200                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15201                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15202                         strcat(buf, moveBuf);
15203                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15204                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15205                 }
15206                 strcat(buf, ")");
15207         }
15208         for(i=1; i<=nrMoves; i++) { // copy last variation back
15209             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15210             for(j=0; j<MOVE_LEN; j++)
15211                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15212             for(j=0; j<2*MOVE_LEN; j++)
15213                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15214             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15215             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15216             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15217             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15218             commentList[currentMove+i] = commentList[framePtr+i];
15219             commentList[framePtr+i] = NULL;
15220         }
15221         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15222         framePtr = savedFramePtr[storedGames];
15223         gameInfo.result = savedResult[storedGames];
15224         if(gameInfo.resultDetails != NULL) {
15225             free(gameInfo.resultDetails);
15226       }
15227         gameInfo.resultDetails = savedDetails[storedGames];
15228         forwardMostMove = currentMove + nrMoves;
15229         if(storedGames == 0) GreyRevert(TRUE);
15230         return TRUE;
15231 }
15232
15233 void 
15234 CleanupTail()
15235 {       // remove all shelved variations
15236         int i;
15237         for(i=0; i<storedGames; i++) {
15238             if(savedDetails[i])
15239                 free(savedDetails[i]);
15240             savedDetails[i] = NULL;
15241         }
15242         for(i=framePtr; i<MAX_MOVES; i++) {
15243                 if(commentList[i]) free(commentList[i]);
15244                 commentList[i] = NULL;
15245         }
15246         framePtr = MAX_MOVES-1;
15247         storedGames = 0;
15248 }
15249
15250 void
15251 LoadVariation(int index, char *text)
15252 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15253         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15254         int level = 0, move;
15255
15256         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15257         // first find outermost bracketing variation
15258         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15259             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15260                 if(*p == '{') wait = '}'; else
15261                 if(*p == '[') wait = ']'; else
15262                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15263                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15264             }
15265             if(*p == wait) wait = NULLCHAR; // closing ]} found
15266             p++;
15267         }
15268         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15269         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15270         end[1] = NULLCHAR; // clip off comment beyond variation
15271         ToNrEvent(currentMove-1);
15272         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15273         // kludge: use ParsePV() to append variation to game
15274         move = currentMove;
15275         ParsePV(start, TRUE);
15276         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15277         ClearPremoveHighlights();
15278         CommentPopDown();
15279         ToNrEvent(currentMove+1);
15280 }
15281